Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test_runner: global after not run if handles are open #49056

Closed
mcollina opened this issue Aug 7, 2023 · 4 comments · Fixed by #49059
Closed

test_runner: global after not run if handles are open #49056

mcollina opened this issue Aug 7, 2023 · 4 comments · Fixed by #49059
Labels
confirmed-bug Issues with confirmed bugs. test_runner Issues and PRs related to the test runner subsystem.

Comments

@mcollina
Copy link
Member

mcollina commented Aug 7, 2023

Version

v20.5.0

Platform

mac

Subsystem

test_runner

What steps will reproduce the bug?

Considder the following test:

const { before, after, test } = require('node:test')
const { createServer } = require('http')

let server

before(async () => {
  console.log('before');
  server = createServer((req, res) => {
    res.end('hello')
  })

  await new Promise((resolve, reject) => {
    server.listen(0, (err) => {
      if (err) reject(err)
      else resolve()
    })
  })
})

after(() => {
  console.log('after');
  server.close()
})

test('something', () => {
  console.log('test');
})

We are trying to dispose of a server (or a connection to a DB) inside a global after but the global after is never run.

How often does it reproduce? Is there a required condition?

all the times

What is the expected behavior? Why is that the expected behavior?

for the test to pass / the after hook to be executed

What do you see instead?

the after hook is not executed and therefore the test never terminates

Additional information

No response

@cjihrig
Copy link
Contributor

cjihrig commented Aug 7, 2023

I'm guessing this is because the test runner uses the beforeExit event, and the ref'ed handle prevents that from being emitted. We'll need to move to a different mechanism for detecting when all of the tests have run.

@cjihrig cjihrig added confirmed-bug Issues with confirmed bugs. test_runner Issues and PRs related to the test runner subsystem. labels Aug 7, 2023
@mcollina
Copy link
Member Author

mcollina commented Aug 7, 2023

I guessed as much, however the above pattern is really good.

@cjihrig
Copy link
Contributor

cjihrig commented Aug 7, 2023

I looked at this really quickly. The good news is that it's pretty simple to fix this case:

diff --git a/lib/internal/test_runner/harness.js b/lib/internal/test_runner/harness.js
index 36c36f2de1..e9076eb378 100644
--- a/lib/internal/test_runner/harness.js
+++ b/lib/internal/test_runner/harness.js
@@ -158,7 +158,6 @@ function setup(root) {
 
   process.on('uncaughtException', exceptionHandler);
   process.on('unhandledRejection', rejectionHandler);
-  process.on('beforeExit', exitHandler);
   // TODO(MoLow): Make it configurable to hook when isTestRunner === false.
   if (globalOptions.isTestRunner) {
     process.on('SIGINT', terminationHandler);
@@ -180,6 +179,7 @@ function setup(root) {
       topLevel: 0,
       suites: 0,
     },
+    exitHandler,
     shouldColorizeTestFiles: false,
   };
   root.startTime = hrtime();
diff --git a/lib/internal/test_runner/test.js b/lib/internal/test_runner/test.js
index cc7c81cad8..fd22433a3d 100644
--- a/lib/internal/test_runner/test.js
+++ b/lib/internal/test_runner/test.js
@@ -687,6 +687,13 @@ class Test extends AsyncResource {
       this.parent.addReadySubtest(this);
       this.parent.processReadySubtestRange(false);
       this.parent.processPendingSubtests();
+
+      if (this.parent === this.root &&
+          this.root.activeSubtests === 0 &&
+          this.root.pendingSubtests.length === 0 &&
+          this.root.readySubtests.size === 0) {
+        this.root.harness.exitHandler();
+      }
     } else if (!this.reported) {
       if (!this.passed && failed === 0 && this.error) {
         this.reporter.fail(0, kFilename, this.subtests.length + 1, kFilename, {

The bad news is that a couple tests are failing. I think it's just related to handling asynchronous activity after the tests finish running like:

test('extraneous async activity test', () => {
  setTimeout(() => { throw new Error(); }, 100);
});

This is kind of expected since the test runner finishes ASAP now instead of once the process is getting ready to exit. I'll have to keep looking into how to best handle this case.

cjihrig added a commit to cjihrig/node that referenced this issue Aug 7, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

Fixes: nodejs#49056
cjihrig added a commit to cjihrig/node that referenced this issue Aug 10, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

Fixes: nodejs#49056
@MoLow
Copy link
Member

MoLow commented Aug 11, 2023

CC @giltayar we might have discussed this use-case or a similar one in Node.TLV

martenrichter pushed a commit to martenrichter/node that referenced this issue Aug 13, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: nodejs#49059
Fixes: nodejs#49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Ceres6 pushed a commit to Ceres6/node that referenced this issue Aug 14, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: nodejs#49059
Fixes: nodejs#49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Ceres6 pushed a commit to Ceres6/node that referenced this issue Aug 14, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: nodejs#49059
Fixes: nodejs#49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
RafaelGSS pushed a commit that referenced this issue Aug 15, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: #49059
Fixes: #49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
RafaelGSS pushed a commit to RafaelGSS/node that referenced this issue Aug 15, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: nodejs#49059
Fixes: nodejs#49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
rluvaton pushed a commit to rluvaton/node that referenced this issue Aug 15, 2023
This commit moves the global after() hook execution from the
'beforeExit' event to the point where all tests have finished
running. This gives the global after() a chance to clean up
handles that would otherwise prevent the 'beforeExit' event
from being emitted.

PR-URL: nodejs#49059
Fixes: nodejs#49056
Reviewed-By: Chemi Atlow <[email protected]>
Reviewed-By: Moshe Atlow <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
Reviewed-By: Benjamin Gruenbaum <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
confirmed-bug Issues with confirmed bugs. test_runner Issues and PRs related to the test runner subsystem.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants