Skip to content

Commit f9ffdf5

Browse files
step2yeungKrinkle
authored andcommitted
Core: Count tests run so that 'suiteEnd' emits correctly with active filters
Previously, the `suiteEnd` event and the `moduleDone` callback were fired when we've run all the tests, based on counting the number of registered tests in a module and the number of tests actually run. This logic does not hold up when there are active module/test filters used in the runner, because then some of the registered tests will never run. The added tests demonstrate the issue, as they will fail without the src patch. Several of the `suiteEnd` events would not fire. The impact of this bug was that the `suiteEnd` event and `moduleDone` callback don't fire if there were active filters such as `--filters` in the CLI or via the "Filter" field in the HTML Reporter. This in turn meant that reporters such as TAP would sometimes reflect the structure of the test suite in an odd way due to it sometimes missing an "end" event for a group of tests. Fixes #1416. Closes #1417.
1 parent 6b50747 commit f9ffdf5

File tree

6 files changed

+153
-18
lines changed

6 files changed

+153
-18
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ module.exports = function( grunt ) {
119119
"test/callbacks.html",
120120
"test/callbacks-promises.html",
121121
"test/events.html",
122+
"test/events-filters.html",
122123
"test/events-in-test.html",
123124
"test/logs.html",
124125
"test/setTimeout.html",

src/core/config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const config = {
4646
tests: [],
4747
childModules: [],
4848
testsRun: 0,
49-
unskippedTestsRun: 0,
49+
testsIgnored: 0,
5050
hooks: {
5151
before: [],
5252
beforeEach: [],

src/module.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function createModule( name, testEnvironment, modifiers ) {
2828
tests: [],
2929
moduleId: generateHash( moduleName ),
3030
testsRun: 0,
31-
unskippedTestsRun: 0,
31+
testsIgnored: 0,
3232
childModules: [],
3333
suiteReport: new SuiteReport( name, parentSuite ),
3434

src/test.js

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ Test.prototype = {
206206

207207
const runHook = () => {
208208
if ( hookName === "before" ) {
209-
if ( hookOwner.unskippedTestsRun !== 0 ) {
209+
if ( hookOwner.testsRun !== 0 ) {
210210
return;
211211
}
212212

@@ -216,7 +216,7 @@ Test.prototype = {
216216
// The 'after' hook should only execute when there are not tests left and
217217
// when the 'after' and 'finish' tasks are the only tasks left to process
218218
if ( hookName === "after" &&
219-
hookOwner.unskippedTestsRun !== numberOfUnskippedTests( hookOwner ) - 1 &&
219+
!lastTestWithinModuleExecuted( hookOwner ) &&
220220
( config.queue.length > 0 || ProcessingQueue.taskCount() > 2 ) ) {
221221
return;
222222
}
@@ -308,7 +308,11 @@ Test.prototype = {
308308
}
309309
}
310310

311-
notifyTestsRan( module, skipped );
311+
if ( skipped ) {
312+
incrementTestsIgnored( module );
313+
} else {
314+
incrementTestsRun( module );
315+
}
312316

313317
// Store result when possible
314318
if ( storage ) {
@@ -343,13 +347,13 @@ Test.prototype = {
343347
// generating stack trace is expensive, so using a getter will help defer this until we need it
344348
get source() { return test.stack; }
345349
} ).then( function() {
346-
if ( module.testsRun === numberOfTests( module ) ) {
350+
if ( allTestsExecuted( module ) ) {
347351
const completedModules = [ module ];
348352

349353
// Check if the parent modules, iteratively, are done. If that the case,
350354
// we emit the `suiteEnd` event and trigger `moduleDone` callback.
351355
let parent = module.parentModule;
352-
while ( parent && parent.testsRun === numberOfTests( parent ) ) {
356+
while ( parent && allTestsExecuted( parent ) ) {
353357
completedModules.push( parent );
354358
parent = parent.parentModule;
355359
}
@@ -393,6 +397,7 @@ Test.prototype = {
393397
const test = this;
394398

395399
if ( !this.valid() ) {
400+
incrementTestsIgnored( this.module );
396401
return;
397402
}
398403

@@ -852,23 +857,32 @@ function collectTests( module ) {
852857
return tests;
853858
}
854859

855-
function numberOfTests( module ) {
856-
return collectTests( module ).length;
860+
// This returns true after all executable and skippable tests
861+
// in a module have been proccessed, and informs 'suiteEnd'
862+
// and moduleDone().
863+
function allTestsExecuted( module ) {
864+
return module.testsRun + module.testsIgnored === collectTests( module ).length;
857865
}
858866

859-
function numberOfUnskippedTests( module ) {
860-
return collectTests( module ).filter( test => !test.skip ).length;
867+
// This returns true during the last executable non-skipped test
868+
// within a module, and informs the running of the 'after' hook
869+
// for a given module. This runs only once for a given module,
870+
// but must run during the last non-skipped test. When it runs,
871+
// there may be non-zero skipped tests left.
872+
function lastTestWithinModuleExecuted( module ) {
873+
return module.testsRun === collectTests( module ).filter( test => !test.skip ).length - 1;
861874
}
862875

863-
function notifyTestsRan( module, skipped ) {
876+
function incrementTestsRun( module ) {
864877
module.testsRun++;
865-
if ( !skipped ) {
866-
module.unskippedTestsRun++;
867-
}
868878
while ( ( module = module.parentModule ) ) {
869879
module.testsRun++;
870-
if ( !skipped ) {
871-
module.unskippedTestsRun++;
872-
}
880+
}
881+
}
882+
883+
function incrementTestsIgnored( module ) {
884+
module.testsIgnored++;
885+
while ( ( module = module.parentModule ) ) {
886+
module.testsIgnored++;
873887
}
874888
}

test/events-filters.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Events Filters</title>
6+
<link rel="stylesheet" href="../dist/qunit.css">
7+
<script src="../dist/qunit.js"></script>
8+
<script src="events-filters.js"></script>
9+
</head>
10+
<body>
11+
<div id="qunit"></div>
12+
<div id="qunit-fixture"></div>
13+
</body>
14+
</html>

test/events-filters.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* This test file verifies the execution order of events emitted
3+
* by QUnit when there are active module filters and test filters.
4+
*/
5+
6+
// These are "module1", "module3", "module4" and "verify"
7+
QUnit.config.moduleId = [ "1cf055b9", "46b9bb3b", "db9e6dfc", "c056c5ed" ];
8+
QUnit.config.filter = "!SKIPME";
9+
QUnit.config.reorder = false;
10+
11+
var invokedHooks = [],
12+
done = false;
13+
14+
function callback( name ) {
15+
return function( details ) {
16+
if ( done ) {
17+
return;
18+
}
19+
20+
invokedHooks.push( name + ": " + details.name );
21+
};
22+
}
23+
24+
QUnit.on( "suiteStart", callback( "suiteStart" ) );
25+
QUnit.on( "testStart", callback( "testStart" ) );
26+
QUnit.on( "testEnd", callback( "testEnd" ) );
27+
QUnit.on( "suiteEnd", callback( "suiteEnd" ) );
28+
29+
QUnit.on( "suiteEnd", function( details ) {
30+
if ( !done && details.name === "module4" ) {
31+
done = true;
32+
}
33+
} );
34+
35+
36+
// matches module filter
37+
QUnit.module( "module1", function() {
38+
QUnit.test( "test 1.1", function( assert ) {
39+
assert.true( true );
40+
} );
41+
QUnit.test( "test 1.2", function( assert ) {
42+
assert.true( true );
43+
} );
44+
} );
45+
46+
// skipped by module filter
47+
QUnit.module( "module2", function() {
48+
QUnit.test( "test 2.1", function( assert ) {
49+
assert.true( false );
50+
} );
51+
QUnit.test( "test 2.2", function( assert ) {
52+
assert.true( false );
53+
} );
54+
} );
55+
56+
// matches module filter
57+
QUnit.module( "module3", function() {
58+
QUnit.test( "test 3.1", function( assert ) {
59+
assert.true( true );
60+
} );
61+
62+
// skipped by test filter
63+
QUnit.test( "test 3.2 SKIPME", function( assert ) {
64+
assert.true( false );
65+
} );
66+
} );
67+
68+
// matches module filter
69+
QUnit.module( "module4", function() {
70+
QUnit.module( "module4-inner", function() {
71+
QUnit.test( "test 4.1", function( assert ) {
72+
assert.true( true );
73+
} );
74+
75+
// skipped by test filter
76+
QUnit.test( "test 4.2 SKIPME", function( assert ) {
77+
assert.true( false );
78+
} );
79+
} );
80+
} );
81+
82+
// matches module filter
83+
QUnit.module( "verify", function() {
84+
QUnit.test( "events with active filters", function( assert ) {
85+
assert.deepEqual( invokedHooks, [
86+
"suiteStart: module1",
87+
"testStart: test 1.1",
88+
"testEnd: test 1.1",
89+
"testStart: test 1.2",
90+
"testEnd: test 1.2",
91+
"suiteEnd: module1",
92+
93+
"suiteStart: module3",
94+
"testStart: test 3.1",
95+
"testEnd: test 3.1",
96+
"suiteEnd: module3",
97+
98+
"suiteStart: module4",
99+
"suiteStart: module4-inner",
100+
"testStart: test 4.1",
101+
"testEnd: test 4.1",
102+
"suiteEnd: module4-inner",
103+
"suiteEnd: module4"
104+
] );
105+
} );
106+
} );

0 commit comments

Comments
 (0)