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

Support Custom Signal Listeners or a Global afterAll Hook in deno test #27681

Open
ezdac opened this issue Jan 15, 2025 · 2 comments
Open

Support Custom Signal Listeners or a Global afterAll Hook in deno test #27681

ezdac opened this issue Jan 15, 2025 · 2 comments
Labels
testing related to deno test and coverage

Comments

@ezdac
Copy link

ezdac commented Jan 15, 2025

Description

It appears that when defining tests with Deno.test() and running them using deno test, adding custom signal listeners (e.g. Deno.addSignalListener) does not work as expected. The deno test runner seems to ignore these listeners, as the signals are only handled internally within the test runner's Rust implementation.

In my use case, I am building a test wrapper that awaits a global asynchronous teardown function during a final test step. While this works well during normal test execution, I want the teardown function to be triggered even when the test suite exits unexpectedly, such as when a SIGINT or SIGTERM signal is received.

Here is a simplified example:

Deno.addSignalListener("SIGINT", () => {
  console.log("Initiating cleanup before exiting...");
  // Perform teardown actions, then exit
});

// Calls Deno.test() internally and registers teardown logic within a test-step
await runTestSuiteAndRegisterTests();

However, when running the test suite with deno test, the signal listener is never triggered. Instead, the deno test runner uses its own signal handler and outputs the following when interrupted:

SIGINT The following tests were pending:

run mytopleveltest => ./packages/mypackage/src/runner.ts:42:42

This behavior prevents custom signal listeners from being used to handle cleanup tasks or shutdown hooks effectively.

Feature Request

To make deno test more flexible and customizable for advanced test scenarios, I propose one of the following enhancements:

  1. Support for Custom Signal Listeners:

    • Allow users to register their own signal listeners (Deno.addSignalListener) in addition to the test runner's default handlers.
    • This would enable users to handle signals like SIGINT or SIGTERM and execute custom logic, such as cleanup functions, before the process exits.
  2. A Global afterAll Hook:

    • Introduce a global afterAll hook that is executed after all tests complete, regardless of how the test suite terminates (e.g., normal exit or signal interruption).
    • The afterAll hook would allow users to register teardown logic that ensures proper cleanup before the process exits.
    • the hook should allow passing in synchronous functions as well as asynchronous functions that will be awaited

Expected Behavior

  • If a Deno.addSignalListener is registered, the test runner should invoke the user's listener(s) in addition to its own internal signal handling.
  • Alternatively, a global afterAll hook would provide a standardized way to execute cleanup logic at the end of the test suite, regardless of how the suite terminates.

Use Case

This feature would enable advanced test suite behaviors, such as:

  • Cleaning up global resources (e.g. databases, external services, or temporary files) when the suite exits.
  • Ensuring all test lifecycle hooks (setup, execution, teardown) are handled gracefully, even in interrupted scenarios.

Current Behavior

Currently, deno test:

  • Ignores user-defined signal listeners and only uses its internal Rust signal handling.
  • Outputs pending tests on signal interruption but does not allow for custom cleanup logic to run.

Conclusion

Adding support for custom signal listeners or a global afterAll hook would greatly enhance the flexibility and usability of the deno test runner, especially for users with complex test suite requirements.

Thank you for considering this feature request! I'm happy to provide further details or examples if needed.

@bartlomieju
Copy link
Member

You might have some luck using BDD-style tests from https://jsr.io/@std/testing/doc/bdd.

@bartlomieju bartlomieju added the testing related to deno test and coverage label Jan 20, 2025
@ezdac
Copy link
Author

ezdac commented Jan 21, 2025

You might have some luck using BDD-style tests from https://jsr.io/@std/testing/doc/bdd.

I also looked into that, but it seems like the afterAll function is implemented internally in a similar way than what I did with
the test-steps.

import { sleep } from "@my-utils/wait";
import { afterAll, beforeAll, describe, it } from "@std/testing/bdd";
import { assertEquals } from "@std/assert";

beforeAll(async () => {
  console.log("will wait on setup");
  await sleep(1000);
  console.log("beforeAll");
});
afterAll(async () => {
  console.log("will wait on teardown");
  await sleep(10000);
  console.log("afterAll");
});

const branchA = describe("branch a");

const subbranchone = describe({
  name: "subbranch 1",
  suite: branchA,
  beforeEach(this: { user: string }) {
    this.user = "Kyle";
  },

  afterEach() {
    console.log("cleared user");
  },
});
it(subbranchone, "check user", async function () {
  const { user } = this;
  console.log("will wait in test");
  await sleep(10000);
  console.log("waited in test");
  assertEquals(user, "Kyle");
});

So sending a SIGINT to this test while waiting in the setup or test will similarly result in not executing the afterAll function:

running 1 test from ./testbdd.test.ts
global ...
------- output -------
will wait on setup
beforeAll
----- output end -----
  branch a ...
    subbranch 1 ...
      check user ...
------- output -------
will wait in test
^C
SIGINT The following tests were pending:

global => https://jsr.io/@std/testing/1.0.9/_test_suite.ts:256:10
global ... branch a => https://jsr.io/@std/testing/1.0.9/_test_suite.ts:393:15
global ... branch a ... subbranch 1 => https://jsr.io/@std/testing/1.0.9/_test_suite.ts:393:15
global ... branch a ... subbranch 1 ... check user => https://jsr.io/@std/testing/1.0.9/_test_suite.ts:393:15

So I'd argue that implementing the custom Signal-Listener registration would also benefit thebdd framework
so that it can implement better teardown functionality.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
testing related to deno test and coverage
Projects
None yet
Development

No branches or pull requests

2 participants