Skip to content

AbortSignal in tests on run cancel #7647

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

Open
4 tasks done
enisdenjo opened this issue Mar 10, 2025 · 8 comments · May be fixed by #7878
Open
4 tasks done

AbortSignal in tests on run cancel #7647

enisdenjo opened this issue Mar 10, 2025 · 8 comments · May be fixed by #7878
Labels
enhancement: pending triage p2-to-be-discussed Enhancement under consideration (priority)

Comments

@enisdenjo
Copy link

enisdenjo commented Mar 10, 2025

Clear and concise description of the problem

When tests that depend on other processes, like during loadtesting or long-running tests, tests that have a teardown process (using afterAll or afterEach) - it would be great to have an AbortSignal in the test itself that would abort when the run is cancelled (by pressing Q or CTRL+C) that would be used to abort the dependant processes.

Suggested solution

Roughly

import { it, expect } from 'vitest';
import { startServer, loadtest } from './my-tools';

it('should pass this long running test', async (ctx) => {
  const server  = await startServer();

  const highestMemoryUsage = await loadtest({
    server,
    // the loadtest runs for a longer period of time, it'll use this signal to stop loadtesting earlier on run cancel
    signal: ctx.signal,
  });

  expect(highestMemoryUsage).toBeLessThan(100);
});

Alternative

import { it, expect } from 'vitest';
import { onCancel } from 'vitest/runners';
import { startServer, loadtest } from './my-tools';

const ctrl = new AbortController();
onCancel(() => ctrl.abort());

it('should pass this long running test', async (ctx) => {
  const server  = await startServer();

  const highestMemoryUsage = await loadtest({
    server,
    // the loadtest runs for a longer period of time, it'll use this signal to stop loadtesting earlier on run cancel
    signal: ctx.signal,
  });

  expect(highestMemoryUsage).toBeLessThan(100);
});

Additional context

If terminating abruptly (double CTRL+C), the teardown wont happen leaving processes or services (like Docker containers) running after the tests end.

Validations

@hi-ogawa
Copy link
Contributor

hi-ogawa commented Mar 11, 2025

Can you provide a full code or anything runnable? There might be already a hidden magic global for you to try it out:

const ctrl = new AbortController();
__vitest_worker__.onCancel.then(() => ctrl.abort())

@enisdenjo
Copy link
Author

enisdenjo commented Mar 11, 2025

The code I'd need to use this is a bit complex; but in essence, the example in the suggested solution maps well to what I have - a test that spawns a k6 process to loadtest (of varying durations, but often long running) a server. Sometimes, setting up the test, requires running Docker containers that are cleaned up in afterAll hooks and if you terminate the run (double CTRL+C), the containers are left running after the run ends.

Now, if there's an option where I can listen on run cancellation requests, I could gracefully kill the k6 process, releasing the "wait" of the loadtest early allowing for the afterAll hooks to run.

BTW, thanks for the hint! I'll try that one out. 👍

@sheremet-va
Copy link
Member

I like the idea of providing a signal, but I am not sure if it will help with your use case. Double CTRL+C always kills the process and everything that was hanging will continue hanging even if we provide a signal (which internally will work just like @hi-ogawa proposed).

In any case, I think we should expose a signal to tests.

@enisdenjo
Copy link
Author

The idea is to not use the double CTRL+C, but use q or a single CTRL+C to gracefully stop long-running tests.

@hi-ogawa hi-ogawa moved this to P2 - 2 in Team Board Mar 26, 2025
@hi-ogawa hi-ogawa added the p2-to-be-discussed Enhancement under consideration (priority) label Mar 26, 2025
@hi-ogawa
Copy link
Contributor

@enisdenjo Can you confirm whether #7647 (comment) works for you just in case? It would be unfortunate if there's some others factor to it and something not working after implemented builtin. Also, it's still nice to have a simplified repro so we can add it as test for what users do.

@enisdenjo
Copy link
Author

Sorry, I forgot to leave a reply - yes #7647 (comment) does what I want!

Regarding the repro, I can add the test itself - just point me to the test file. 😄

@sheremet-va
Copy link
Member

As a note, it would also be nice to abort the signal if test times out. This is how it works in node test runner, for example:

import { test } from 'node:test'
import { setTimeout } from 'timers/promises'

test('example', { timeout: 100 }, async t => {
  t.signal.addEventListener('abort', (e) => {
    console.log('aborted', e) // Event
  })
  await setTimeout(1000)
})

@polRk
Copy link

polRk commented Apr 23, 2025

@sheremet-va +1, I also need an abort signal in each test, like node:test.

	tc.test('reuse token', async (tc) => {
		let provider = new StaticCredentialsProvider({ username, password }, cs.origin, cf)

		let token = await provider.getToken(false, tc.signal)
		let token2 = await provider.getToken(false, tc.signal)

		assert.strictEqual(token, token2, 'Token is the same')
		assert.strictEqual(calls, 1, 'Only one call was made')
	})

The main task is to be able to interrupt promises, async and background processes.
In my example, getToken can do a lot more background work after the promise is resolved, and when using signal, it can be interrupted.

@sheremet-va sheremet-va linked a pull request Apr 23, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement: pending triage p2-to-be-discussed Enhancement under consideration (priority)
Projects
Status: P2 - 2
Development

Successfully merging a pull request may close this issue.

4 participants