Skip to content

Commit df8f1eb

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 a49959d commit df8f1eb

File tree

6 files changed

+145
-18
lines changed

6 files changed

+145
-18
lines changed

Gruntfile.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@ module.exports = function( grunt ) {
166166
"test/callbacks.html",
167167
"test/callbacks-promises.html",
168168
"test/events.html",
169+
"test/events-filters.html",
169170
"test/events-in-test.html",
170171
"test/logs.html",
171172
"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: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ Test.prototype = {
208208

209209
const runHook = () => {
210210
if ( hookName === "before" ) {
211-
if ( hookOwner.unskippedTestsRun !== 0 ) {
211+
if ( hookOwner.testsRun !== 0 ) {
212212
return;
213213
}
214214

@@ -218,7 +218,7 @@ Test.prototype = {
218218
// The 'after' hook should only execute when there are not tests left and
219219
// when the 'after' and 'finish' tasks are the only tasks left to process
220220
if ( hookName === "after" &&
221-
hookOwner.unskippedTestsRun !== numberOfUnskippedTests( hookOwner ) - 1 &&
221+
!lastTestWithinModuleExecuted( hookOwner ) &&
222222
( config.queue.length > 0 || ProcessingQueue.taskCount() > 2 ) ) {
223223
return;
224224
}
@@ -310,7 +310,11 @@ Test.prototype = {
310310
}
311311
}
312312

313-
notifyTestsRan( module, skipped );
313+
if ( skipped ) {
314+
incrementTestsIgnored( module );
315+
} else {
316+
incrementTestsRun( module );
317+
}
314318

315319
// Store result when possible
316320
if ( storage ) {
@@ -345,13 +349,13 @@ Test.prototype = {
345349
// generating stack trace is expensive, so using a getter will help defer this until we need it
346350
get source() { return test.stack; }
347351
} ).then( function() {
348-
if ( module.testsRun === numberOfTests( module ) ) {
352+
if ( allTestsExecuted( module ) ) {
349353
const completedModules = [ module ];
350354

351355
// Check if the parent modules, iteratively, are done. If that the case,
352356
// we emit the `suiteEnd` event and trigger `moduleDone` callback.
353357
let parent = module.parentModule;
354-
while ( parent && parent.testsRun === numberOfTests( parent ) ) {
358+
while ( parent && allTestsExecuted( parent ) ) {
355359
completedModules.push( parent );
356360
parent = parent.parentModule;
357361
}
@@ -395,6 +399,7 @@ Test.prototype = {
395399
const test = this;
396400

397401
if ( !this.valid() ) {
402+
incrementTestsIgnored( this.module );
398403
return;
399404
}
400405

@@ -856,23 +861,24 @@ function collectTests( module ) {
856861
return tests;
857862
}
858863

859-
function numberOfTests( module ) {
860-
return collectTests( module ).length;
864+
function allTestsExecuted( module ) {
865+
return module.testsRun + module.testsIgnored === collectTests( module ).length;
861866
}
862867

863-
function numberOfUnskippedTests( module ) {
864-
return collectTests( module ).filter( test => !test.skip ).length;
868+
function lastTestWithinModuleExecuted( module ) {
869+
return module.testsRun + module.testsIgnored === collectTests( module ).length - 1;
865870
}
866871

867-
function notifyTestsRan( module, skipped ) {
872+
function incrementTestsRun( module ) {
868873
module.testsRun++;
869-
if ( !skipped ) {
870-
module.unskippedTestsRun++;
871-
}
872874
while ( ( module = module.parentModule ) ) {
873875
module.testsRun++;
874-
if ( !skipped ) {
875-
module.unskippedTestsRun++;
876-
}
876+
}
877+
}
878+
879+
function incrementTestsIgnored( module ) {
880+
module.testsIgnored++;
881+
while ( ( module = module.parentModule ) ) {
882+
module.testsIgnored++;
877883
}
878884
}

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)