Skip to content

Draft: list 'pending' tests #1596

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

Closed
wants to merge 4 commits into from
Closed

Draft: list 'pending' tests #1596

wants to merge 4 commits into from

Conversation

ace-n
Copy link

@ace-n ace-n commented Nov 22, 2017

Fixes #583.

N.B: I haven't added any new tests yet - but I plan to once the implementation details are nailed down.

Example test.js

const test = require(`ava`);

test('instant', (t) => {
	t.pass()
})

test.cb('2 seconds', (t) => {
	setTimeout(() => {
		t.pass()
		t.end()
	}, 2000);
})

test.cb('6 seconds', (t) => {
	setTimeout(() => {
		t.pass()
		t.end()
	}, 6000);
})

test.cb('12 seconds', (t) => {
	setTimeout(() => {
		t.pass()
		t.end()
	}, 12000);
})
$ ava test.js -T 3s
  2 passed
  2 pending

  These tests did not finish:

   6 seconds

   12 seconds

  1 exception

  ✖ Exited because no new tests completed within the last 3000ms of inactivity

@novemberborn
Copy link
Member

Nice work @ace-n! Test state jumps through a lot of hoops on its way to the main process so well done following it all.

I'd like to suggest a slightly different approach though, one that is a bit more explicit and requires fewer low-level changes. See the inline comments below. Let me know what you think!

lib/runner.js Outdated
@@ -146,17 +147,22 @@ class Runner extends EventEmitter {
}

buildStats() {
this.tests.finalize();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this work given that TestCollection#finalize() does the finalizing via setImmediate()?

I think it'd be better to get an array of pending tests from TestCollection and then iterate through it, calling addTestResult(). I'd then modify addTestResult() to take a second pending argument and provide the correct value here and in run() below. That way Test doesn't have to be aware of pending at all.

TestCollection#finalize() can then be removed. For clarity I think buildStats() should be renamed to exit().

Copy link
Author

@ace-n ace-n Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed and done - though this does burrow a bit into the TestCollection, I feel like adding such a loop at the top of buildStats() (or exit(), if we were to rename it) is a cleaner way to do this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like adding such a loop at the top of buildStats() (or exit(), if we were to rename it) is a cleaner way to do this.

Which is what you've done, right?

this.skipCount = sum(this.stats, 'skipCount');
this.todoCount = sum(this.stats, 'todoCount');
this.failCount = sum(this.stats, 'failCount');
this.remainingCount = this.testCount - this.passCount - this.failCount - this.skipCount - this.todoCount - this.knownFailureCount;
this.remainingCount = this.testCount - this.passCount - this.failCount - this.skipCount - this.todoCount - this.knownFailureCount - this.pendingCount;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At first blush you'd think this is now always 0, but that wouldn't be the case with --fail-fast not emitting the pending tests. Perhaps a comment would help clarify that possibility.

Copy link
Author

@ace-n ace-n Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See follow-up above.

const title = test.title;
status += '\n ' + colors.title(title) + '\n';
// TODO: Output description with link
// status += colors.stack(description);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't apply to pending tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

lib/cli.js Outdated
@@ -207,4 +207,7 @@ exports.run = () => {
});
});
}

// Trap SIGINT to prevent AVA quitting immediately
process.on('SIGINT', () => {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This relies on the signal still being propagated to the worker processes, right? I think we need to take a more explicit approach. For one the API needs to know not to start new workers when concurrency is being limited. Since the API is managing the worker pool it could shut down the workers, and the workers could then emit their pending tests. See https://github.com/avajs/ava/blob/e401bd151b1a81f79ccb106f034b2e18715ee125/api.js#L245:L247

(Though looking at that code I have a suspicion timeouts don't prevent new workers from being started!)

We'd need to make sure the signal isn't propagated, or otherwise is ignored in the workers.

Copy link
Author

@ace-n ace-n Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The teardown process is initiated by the SIGINT handler added to lib/main.js - which then calls the exit() function in that same file (which I'm relying on to handle any required tear-down tasks).

If you disable the SIGINT handler in lib/main.js while keeping this one, AVA (from what I can see through the CLI) basically ignores SIGINTs. If you disable this SIGINT handler however, lib/main.js doesn't have enough time to gracefully exit.

This was largely based on experimenting with the code though - so it's definitely possible that I'm initiating the wrong teardown call/event.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to make sure we try intercepting SIGINT in the main process, and then using the Api instance to explicitly exit all workers. We may then need to trap SIGINT in the workers to ensure they don't exit before they're told to do so.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - any files that haven't started running when a SIGINT is received will trigger an AvaError stating that they weren't run. I assume that works?

@@ -91,6 +91,7 @@ class VerboseReporter {
' ' + colors.error(runStatus.failCount, plur('test', runStatus.failCount), 'failed') :
' ' + colors.pass(runStatus.passCount, plur('test', runStatus.passCount), 'passed'),
runStatus.knownFailureCount > 0 ? ' ' + colors.error(runStatus.knownFailureCount, plur('known failure', runStatus.knownFailureCount)) : '',
runStatus.pendingCount > 0 ? ' ' + colors.todo(runStatus.pendingCount, plur('test', runStatus.pendingCount), 'pending') : '',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it's on your list, but this still needs to print the titles of these tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I missed this originally - good catch!

@@ -27,6 +27,9 @@ Error.stackTraceLimit = Infinity;

function test(props) {
if (isFailed) {
if (props.pending) {
adapter.send('test', props);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only applies to when --fail-fast is used. I don't think we don't need to print pending tests in that circumstance.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine people want to be able to identify any non-passing tests with --fail-fast. That's doable without this PR if all tests are serial (--> any tests after the failed one are pending), but harder to reason about if test order is nondeterministic or involves multiple files executing concurrently.

Thoughts?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine people want to be able to identify any non-passing tests with --fail-fast.

No, all tests should stop running as soon as a single failure occurs. See #1158.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misspoke - sorry about that 😄 s/non-passing/pending/g

That being said - I see two distinct use cases for --fail-fast:

  1. as a simple yes/no "Do my tests pass?" check? In this case, I'd agree that people don't really care about which tests are pending.
  2. iterative development/debugging. Personally, I'd find knowing which tests are pending useful in this case - especially if I'm trying to troubleshoot why said tests are pending.

If Option 1 is more important, I have no objection to hiding pending tests on --fail-fast - but I'd vote to keep things the way they are if we're prioritizing Option 2.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iterative development/debugging. Personally, I'd find knowing which tests are pending useful in this case - especially if I'm trying to troubleshoot why said tests are pending.

It would show tests that haven't yet completed when another test fails. I don't think that's what we're trying to solve with this PR.


finalize() {
globals.setImmediate(() => {
Array.from(this.pendingTestInstances).forEach(test => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use a for-of loop instead of forEach.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

lib/runner.js Outdated
result: test
});
}
this.tests.pendingTestInstances.clear(); // Pending tests must be cleared
Copy link
Member

@novemberborn novemberborn Dec 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why must they be cleared?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spurious repeated results in some tests otherwise - namely here. This is a bit of a band-aid fix though, so let me know if further investigation or a more proper fix is required.

lib/cli.js Outdated
@@ -207,4 +207,7 @@ exports.run = () => {
});
});
}

// Trap SIGINT to prevent AVA quitting immediately
process.on('SIGINT', () => {});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to make sure we try intercepting SIGINT in the main process, and then using the Api instance to explicitly exit all workers. We may then need to trap SIGINT in the workers to ensure they don't exit before they're told to do so.

Copy link
Author

@ace-n ace-n left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another side-question: do we want to report pending after() calls? e.g. if all your tests pass, but AVA doesn't terminate automatically - should we tell users which after() call is pending?

Perhaps this should be part of another issue altogether? I've personally ran into this in the past, but I don't know whether this feature would be useful for others.

@@ -27,6 +27,9 @@ Error.stackTraceLimit = Infinity;

function test(props) {
if (isFailed) {
if (props.pending) {
adapter.send('test', props);
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I misspoke - sorry about that 😄 s/non-passing/pending/g

That being said - I see two distinct use cases for --fail-fast:

  1. as a simple yes/no "Do my tests pass?" check? In this case, I'd agree that people don't really care about which tests are pending.
  2. iterative development/debugging. Personally, I'd find knowing which tests are pending useful in this case - especially if I'm trying to troubleshoot why said tests are pending.

If Option 1 is more important, I have no objection to hiding pending tests on --fail-fast - but I'd vote to keep things the way they are if we're prioritizing Option 2.

lib/runner.js Outdated
result: test
});
}
this.tests.pendingTestInstances.clear(); // Pending tests must be cleared
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are spurious repeated results in some tests otherwise - namely here. This is a bit of a band-aid fix though, so let me know if further investigation or a more proper fix is required.

lib/cli.js Outdated
@@ -207,4 +207,7 @@ exports.run = () => {
});
});
}

// Trap SIGINT to prevent AVA quitting immediately
process.on('SIGINT', () => {});
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done - any files that haven't started running when a SIGINT is received will trigger an AvaError stating that they weren't run. I assume that works?

const forkArgs = execArgvList[index];
if (interrupted) {
const relFile = path.relative('.', file);
return reject(new AvaError(`File ${relFile} was not run.`));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AVA prints this as an uncaught exception, which isn't ideal. Do you think this even needs to be printed, or can these test files be skipped silently?

const test = this._runFile(file, runStatus, forkArgs);
tests.push(test);

process.on('SIGINT', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will trigger "memory leak" warnings when there are more than 10 test files. Each test is pushed onto the tests array though so you could use that to set up one SIGINT listener and exit each active test.

@@ -27,6 +27,9 @@ Error.stackTraceLimit = Infinity;

function test(props) {
if (isFailed) {
if (props.pending) {
adapter.send('test', props);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

iterative development/debugging. Personally, I'd find knowing which tests are pending useful in this case - especially if I'm trying to troubleshoot why said tests are pending.

It would show tests that haven't yet completed when another test fails. I don't think that's what we're trying to solve with this PR.

@novemberborn
Copy link
Member

Another side-question: do we want to report pending after() calls? e.g. if all your tests pass, but AVA doesn't terminate automatically - should we tell users which after() call is pending?

Perhaps this should be part of another issue altogether? I've personally ran into this in the past, but I don't know whether this feature would be useful for others.

That would be useful, yes, but it could be done in a separate issue.

@novemberborn
Copy link
Member

There's been a lot of changes recently that impact this PR. I'm also looking to refactor how test results are collected and logged. That'll make this feature easier to implement.

Closing this for now. @ace-n if you want to pick this up again, please check in first to see if I've actually managed to do that refactoring 👼 Meanwhile thank you for putting in so much effort!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants