Open
Description
See: #918 (comment)
after.always
has some issues when used as a cleanup task. Specifically, it won't run if:
- There are test failures and
--fail-fast
is used. - There are uncaught exceptions thrown.
I've advocated using .before
or .beforeEach
to ensure state is clean before running, but that means state is left on disk after the test run. It's easy enough to get around that:
function cleanup() {
if (temDirExists()) {
removeTempDir();
}
}
test.before(cleanup);
test.after.always(cleanup);
Still, it might be nicer if we had a modifier that allowed you to do it a little cleaner:
// runs as before and after
test.cleanup(cleanupFn);
// runs as beforeEach and after (not sure afterEach makes much sense?)
test.cleanupEach(cleanupFn);
Or maybe we introduce a .and
modifier:
test.before.and.after(cleanupFn);
test.beforeEach.and.after(cleanupFn);
test.beforeEach.and.afterEach(cleanupFn);
I think the second gives you a little more flexibility and is clearer without reading the docs. The first is probably simpler to implement (though I don't think the second would be very hard)
There is a $82.00 open bounty on this issue. Add more on Issuehunt.
- Checkout the Issuehunt explorer to discover more funded issues.
- Need some help from other developers? Add your repositories on Issuehunt to raise funds.
Metadata
Metadata
Assignees
Type
Projects
Milestone
Relationships
Development
No branches or pull requests
Activity
novemberborn commentedon Jun 20, 2016
cleanup
better communicates intended use..and.
feels overly verbose.Agreed that
afterEach
doesn't make sense for.cleanupEach
.sotojuan commentedon Jun 22, 2016
Another vote for
cleanup
..and.
reminds me of Mocha/Chai—cleanup
just looks for AVA-ish if that makes sense.sindresorhus commentedon Jun 22, 2016
I prefer
cleanup
too.Can we make it cleanup even on
uncaughtException
? I'd like cleanup to work no matter what happens.jamestalmage commentedon Jun 22, 2016
I lean toward
and
, though I see problems with it as well.The only reason is that I think it makes it more obvious what is going on without reading the AVA docs. The notion that cleanup is going to happen both before and after is going to surprise people. They are going to write
rmdir
code without checking to make sure the directory exists, and be surprised when it fails. I thinkand
better optimizes for test readability and being a self documenting API.cleanup
seems only to optimize for characters typed.My concern with
and
is that it might suggest combinations we don't want to support.We can try. There is no guarantee it would work.
The only way to guarantee cleanup works would be to kill the first process immediately, then relaunch and run just the cleanup method. That falls apart if you use something like
unique-temp-dir
and store a reference for cleanup (not a good example - no need to clean up temp).vadimdemedes commentedon Jun 23, 2016
Hmm, wasn't
.always
supposed to serve this exact same purpose? To always run the clean up task, regardless of previous failure? Perhaps it's worth fixing the existing modifier, instead of introducing a new one. How would we explain the difference between the two to the user?sindresorhus commentedon Jun 25, 2016
@vdemedes I would prefer that too, but I remember there being some good points about why
.always
can't actually be always. Can't remember where we discussed it. @jamestalmage @novemberborn ?sindresorhus commentedon Jun 25, 2016
Not just that, but now we're introducing combinatory methods, making the test syntax harder to understand. Starting to get bit too DSL'y.
novemberborn commentedon Jun 25, 2016
That was our assumption, yes. The question though is how
--fail-fast
is supposed to behave. The fastest way to fail is to forcibly exit the process. Trying to run just thealways
hook of the failing test whilst other asynchronous tests are active is tricky. For debugging purposes it can be useful if test state remains after a failure,--fail-fast
would provide that behavior.After an uncaught exception there are no guarantees as to what code may still be able to run. Here too we end up forcibly exiting the process.
Note that in both cases we first do IPC with the main process before actually exiting. I'm not quite sure how much code still has a chance to run. It's possible
always.after
is entered but if it does anything asynchronous then that gets interrupted. This needs some research.We should decide what guarantees we want from
--fail-fast
anduncaughtException
.Also, our understanding of "test cleanup" has evolved to the point where we know see the best strategy of ensuring a clean test environment is to clean up before you run your test, and then clean up after to avoid leaving unnecessary files and test databases lying around. Hence the proposal for a
.cleanup
hook.Note that
.always.after
is still useful in other scenarios, e.g. #840.sindresorhus commentedon Jun 29, 2016
Maybe we could rerun the process and only run the
.always.after
hooks.jamestalmage commentedon Jun 29, 2016
See #928 (comment)
catdad commentedon Jul 11, 2016
Just my two cents, since I was welcomed to comment. I have only used ava for one project so far, but I immediately ran into this issue and it confused me very much as a user and avid code tester.
My expectation, when splitting my code up into a
before
,test
, andafter
, is that I am doing build-up and tear-down that needs to happen in order to test a small piece of functionality, but does not otherwise relate to thetest
code itself. In order for my tests to be predictable, I expect that these blocks always run, no matter what. From an end-user perspective, having anafter
,after.always
, andcleanup
is just confusing and redundant. Of course I want to clean up, and of course I want that to always happen. What are the use cases for anything else? (Yes, I understand technical debt and backwards compatibility, but they should only be considerations in design, not driving forces.)Also, I see some bad advice and rhetoric happening here and in #918, suggesting that cleanup should happen before and after, allowing you to re-run tests even in a dirty environment. While I don't disagree that it is a good idea to check that your environment is exactly as desired before a test, there has to be a stick in the ground saying that unit tests must not permanently alter the environment, even if there are other tools in the toolchain (such as
.gitignore
) which will handle that for you. And to the extent that that is the fault of the test framework, it should be treated as an egregious and urgent bug. As an end-user, I should not have to choose between speed and sane, repeatable tests.With that said, I would support the option of a
.cleanup
hook that ava transparently runs both before and after (and always run it after), provided that the use cases are fully considered. There might potentially be issues with running unexpected cleanup code before the tests, as that is rather unorthodox among the other test frameworks.novemberborn commentedon Sep 23, 2016
@catdad
A crash due to a bug in AVA would be something we'd fix, yes. But what if the crash is caused by the code being tested? There is no 100% reliable way to recover from that and undo changes to the environment. Similarly the
--fail-fast
feature is designed to leave the environment in the state in which the failure occurred.In other words, unless your tests always pass, there will be moments where a test run leaves the environment in a different state. AVA can't know that's the case since it doesn't understand
before
/after
code. At least thecleanup
hook makes it easier to sanitize your environment before test runs (in case it was left in a dirty state), and after test runs (because cleaning up is nice).We should clearly document the intent of the hook. But AVA is not afraid of being unorthodox 😉
21 remaining items
novemberborn commentedon Jul 15, 2019
Tests start running asynchronously so that won't work unfortunately.
sholladay commentedon Jul 15, 2019
I wonder if
test()
could return aPromise
and then when we get top-levelawait
you could wrap all tests in atry
/catch
? That would be interesting!novemberborn commentedon Jul 18, 2019
You'd have to assign all those promises to an array and await them at the end, though, since AVA requires you to declare all tests at once. It also means errors are attributed to "well the process had an exception" rather than a specific cleanup task.
ulken commentedon Apr 27, 2020
@novemberborn Due to the age of this issue and the introduction of
t.teardown()
, is this still desired?novemberborn commentedon Apr 27, 2020
I've been thinking about this for a while now. I think as part of #2435 I'd like to have a "set-up" lifecycle thing which can return a teardown function. It's a different use case from "run this before" and "run this after".
I've assigned this to myself for now.
mikob commentedon Mar 9, 2022
@novemberborn Is this still relevant? Curious why it was removed from priorities. I'm considering doing a PR with a
cleanup
method for the API.I'm still struggling to have cleanup code run consistently. I would like cleanup to be run on test success, test failure, uncaught exceptions, timeout, and SIGINT (ctrl-c) - and ideally all the other kill signals. I have tried after.always, afterEach.always and t.teardown. My use case is closing webdriverio clients (that eat lots of ram) when tests aren't running.
I've considered doing a temporary workaround by catching the node process exit signal, but how would I get access to the ava context from there which has the webdriverio client instances?
I think this is unnecessary as it could be done trivially in user-space with an if statement in the
cleanup
hook that checks a user-defined env. var e.g. SKIP_CLEANUP.novemberborn commentedon Mar 11, 2022
I don't know @mikob, that was 3 years ago! 😄
I think this is the tricky bit. At some point the worker process/thread needs to exit, especially if there's been a fatal error. Within the API that AVA provides there'll always be ways in which cleanup cannot occur.
I wonder if AVA 4's shared workers feature could be used for this. It can track the comings and goings of test workers and runs separate from the tests. The tricky thing may be to expose the clients to the tests.
Could you hook that up when you create the clients?
mikob commentedon Mar 11, 2022
Haha, fair enough!
I don't think we need a 100% guarantee. It's more a convenience and responsibility thing. I feel it's fairly common for a test to throw unhandled exceptions, after all, they are testing. And SIGINT when we quit tests prematurely.
It would make more sense to have the setup/cleanup handled by AVA apis symmetrically IMO. Also I'm doing 1 client per test. Since I create a webdriverio client in beforeEach, a cleanup or afterEach should tear it down.
I'm talking about process.on("SIGINT" and the clients are created in beforeEach (need one per test) not sure how to access all the workers contexts from within the process.on callback.
novemberborn commentedon Mar 14, 2022
@mikob re-reading the conversation here I think #928 (comment) sums up a direction we can take this.
But I don't think that would solve your problem.
Do you get PIDs for the clients? You could send those to a shared worker that makes sure they get shut down, yet still instantiate within the test worker itself.
bitjson commentedon Jan 11, 2023
To add another case here: these end to end tests spawn multiple instances of the tested software to test how they interact. There's significant setup, and tests are all serial.
If a test times out, the test process ends but leaves these spawned processes running (which might continue logging to files, holding ports open, and otherwise interfering unpredictably with future test runs) – this happens even though Execa should be cleaning them up – I think because AVA is killing the timed-out tests with
SIGKILL
?Even disabling
--fail-fast
and attempting to manually kill the processes usingafter.always
doesn't get rid of them:The current behavior is pretty surprising – I've thought these were unrelated issues with the tested software for a while now, and I only just now realized it's all related to this AVA issue. A more predictable behavior here would be much appreciated 🙏
novemberborn commentedon Jan 15, 2023
Child processes are killed with
SIGTERM
, worker threads (the default behavior) are terminated… and I'm not sure what / how that impacts Execa.Clean-ups when tests timeout are pretty tricky since the worker thread itself may be locked up.
gorbak25 commentedon Jun 2, 2023