Remove support for excution orders that re-enter suites

This commit is contained in:
Steve Gravrock
2025-09-27 13:33:25 -07:00
parent 4598e4049c
commit c2ce55580c
7 changed files with 42 additions and 272 deletions

View File

@@ -11336,8 +11336,7 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
const defaultMax = 1 - Infinity; const defaultMax = 1 - Infinity;
// Transforms the suite tree into an execution tree, which represents the set // Transforms the suite tree into an execution tree, which represents the set
// of specs and (possibly interleaved) suites to be run in the order they are // of specs and suites to be run in the order they are to be run in.
// to be run in.
class TreeProcessor { class TreeProcessor {
#tree; #tree;
#runnableIds; #runnableIds;
@@ -11419,15 +11418,7 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
segmentChildren(node, orderedChildren, this.#stats, executableIndex); segmentChildren(node, orderedChildren, this.#stats, executableIndex);
if (this.#stats[node.id].segments.length > 1) { if (this.#stats[node.id].segments.length > 1) {
if (node.canBeReentered()) { throw new Error('Invalid order: would split up a suite');
j$.getEnv().deprecated(
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
);
} else {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
} }
} }
} }
@@ -11445,15 +11436,14 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
} }
childrenOfTopSuite() { childrenOfTopSuite() {
return this.childrenOfSuiteSegment(this.topSuite, 0); return this.childrenOfSuite(this.topSuite);
} }
childrenOfSuiteSegment(suite, segmentNumber) { childrenOfSuite(suite) {
const segmentChildren = this.#stats[suite.id].segments[segmentNumber] const segmentChildren = this.#stats[suite.id].segments[0].nodes;
.nodes;
return segmentChildren.map(function(child) { return segmentChildren.map(function(child) {
if (child.owner.children) { if (child.owner.children) {
return { suite: child.owner, segmentNumber: child.index }; return { suite: child.owner };
} else { } else {
return { spec: child.owner }; return { spec: child.owner };
} }
@@ -11574,7 +11564,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
async execute() { async execute() {
this.#hasFailures = false; this.#hasFailures = false;
await new Promise(resolve => { await new Promise(resolve => {
this.#executeSuiteSegment(this.#executionTree.topSuite, 0, resolve); this.#executeSuite(this.#executionTree.topSuite, resolve);
}); });
return { hasFailures: this.#hasFailures }; return { hasFailures: this.#hasFailures };
} }
@@ -11584,7 +11574,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
return { return {
fn: done => { fn: done => {
if (node.suite) { if (node.suite) {
this.#executeSuiteSegment(node.suite, node.segmentNumber, done); this.#executeSuite(node.suite, done);
} else { } else {
this._executeSpec(node.spec, done); this._executeSpec(node.spec, done);
} }
@@ -11674,7 +11664,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
return fns; return fns;
} }
#executeSuiteSegment(suite, segmentNumber, done) { #executeSuite(suite, done) {
const isTopSuite = suite === this.#executionTree.topSuite; const isTopSuite = suite === this.#executionTree.topSuite;
const isExcluded = this.#executionTree.isExcluded(suite); const isExcluded = this.#executionTree.isExcluded(suite);
let befores = []; let befores = [];
@@ -11696,7 +11686,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
const children = isTopSuite const children = isTopSuite
? this.#executionTree.childrenOfTopSuite() ? this.#executionTree.childrenOfTopSuite()
: this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber); : this.#executionTree.childrenOfSuite(suite);
const queueableFns = [ const queueableFns = [
...befores, ...befores,
...this.#wrapNodes(children), ...this.#wrapNodes(children),

View File

@@ -470,137 +470,6 @@ describe('Runner', function() {
await expectAsync(promise).toBePending(); await expectAsync(promise).toBePending();
}); });
it('runs a suite multiple times if the order specified leaves and re-enters it', async function() {
const spec1 = new StubSpec();
const spec2 = new StubSpec();
const spec3 = new StubSpec();
const spec4 = new StubSpec();
const spec5 = new StubSpec();
const reentered = new StubSuite({ children: [spec1, spec2, spec3] });
const topSuite = new StubSuite({ children: [reentered, spec4, spec5] });
const subject = makeRunner(topSuite);
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
const promise = subject.execute([
spec1.id,
spec4.id,
spec2.id,
spec5.id,
spec3.id
]);
await Promise.resolve();
expect(runQueue).toHaveBeenCalledTimes(1);
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
queueableFns[0].fn();
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec1, 'done');
queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec4, 'done');
queueableFns[2].fn();
expect(runQueue.calls.count()).toBe(3);
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec2, 'done');
queueableFns[3].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec5, 'done');
queueableFns[4].fn();
expect(runQueue.calls.count()).toBe(4);
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec3, 'done');
await expectAsync(promise).toBePending();
});
it('runs a parent of a suite with multiple segments correctly', async function() {
const spec1 = new StubSpec();
const spec2 = new StubSpec();
const spec3 = new StubSpec();
const spec4 = new StubSpec();
const spec5 = new StubSpec();
const parent = new StubSuite({ children: [spec1, spec2, spec3] });
const grandparent = new StubSuite({ children: [parent] });
const topSuite = new StubSuite({ children: [grandparent, spec4, spec5] });
const subject = makeRunner(topSuite);
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
const promise = subject.execute([
spec1.id,
spec4.id,
spec2.id,
spec5.id,
spec3.id
]);
await Promise.resolve();
expect(runQueue).toHaveBeenCalledTimes(1);
const queueableFns = runQueue.calls.mostRecent().args[0].queueableFns;
expect(queueableFns.length).toBe(5);
queueableFns[0].fn();
expect(runQueue.calls.count()).toBe(2);
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
expect(runQueue.calls.count()).toBe(3);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec1, 'done');
queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec4, 'done');
queueableFns[2].fn();
expect(runQueue.calls.count()).toBe(4);
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
expect(runQueue.calls.count()).toBe(5);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec2, 'done');
queueableFns[3].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec5, 'done');
queueableFns[4].fn();
expect(runQueue.calls.count()).toBe(6);
expect(runQueue.calls.mostRecent().args[0].queueableFns.length).toBe(2);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn();
expect(runQueue.calls.count()).toBe(7);
runQueue.calls.mostRecent().args[0].queueableFns[1].fn('done');
expect(
privateUnderTest.TreeRunner.prototype._executeSpec
).toHaveBeenCalledWith(spec3, 'done');
await expectAsync(promise).toBePending();
});
it('runs large segments of nodes in the order they were declared', async function() { it('runs large segments of nodes in the order they were declared', async function() {
const specs = []; const specs = [];

View File

@@ -135,11 +135,9 @@ describe('TreeProcessor', function() {
const result = processor.processTree(); const result = processor.processTree();
expect(result.childrenOfTopSuite()).toEqual([ expect(result.childrenOfTopSuite()).toEqual([{ suite: node }]);
{ suite: node, segmentNumber: 0 }
]);
expect(result.isExcluded(node)).toEqual(true); expect(result.isExcluded(node)).toEqual(true);
expect(result.childrenOfSuiteSegment(node, 0)).toEqual([{ spec: leaf1 }]); expect(result.childrenOfSuite(node)).toEqual([{ spec: leaf1 }]);
expect(result.isExcluded(node)).toEqual(true); expect(result.isExcluded(node)).toEqual(true);
}); });
@@ -167,37 +165,37 @@ describe('TreeProcessor', function() {
expect(result.isExcluded(parent)).toEqual(false); expect(result.isExcluded(parent)).toEqual(false);
expect(result.childrenOfTopSuite()).toEqual([ expect(result.childrenOfTopSuite()).toEqual([
{ suite: parent, segmentNumber: 0 }, { suite: parent },
{ suite: parentOfPendings, segmentNumber: 0 } { suite: parentOfPendings }
]); ]);
expect(result.isExcluded(parentOfPendings)).toEqual(true); expect(result.isExcluded(parentOfPendings)).toEqual(true);
expect(result.childrenOfSuiteSegment(parentOfPendings, 0)).toEqual([ expect(result.childrenOfSuite(parentOfPendings)).toEqual([
{ suite: childless, segmentNumber: 0 }, { suite: childless },
{ suite: pendingNode, segmentNumber: 0 } { suite: pendingNode }
]); ]);
expect(result.isExcluded(childless)).toEqual(true); expect(result.isExcluded(childless)).toEqual(true);
expect(result.childrenOfSuiteSegment(childless, 0)).toEqual([]); expect(result.childrenOfSuite(childless)).toEqual([]);
expect(result.isExcluded(pendingLeaf)).toEqual(false); expect(result.isExcluded(pendingLeaf)).toEqual(false);
expect(result.isExcluded(executableLeaf)).toEqual(false); expect(result.isExcluded(executableLeaf)).toEqual(false);
expect(result.isExcluded(parent)).toEqual(false); expect(result.isExcluded(parent)).toEqual(false);
expect(result.childrenOfSuiteSegment(parent, 0)).toEqual([ expect(result.childrenOfSuite(parent)).toEqual([
{ spec: pendingLeaf }, { spec: pendingLeaf },
{ spec: executableLeaf } { spec: executableLeaf }
]); ]);
expect(result.isExcluded(pendingNode)).toEqual(true); expect(result.isExcluded(pendingNode)).toEqual(true);
expect(result.childrenOfSuiteSegment(pendingNode, 0)).toEqual([ expect(result.childrenOfSuite(pendingNode)).toEqual([
{ spec: childOfPending } { spec: childOfPending }
]); ]);
expect(result.isExcluded(childOfPending)).toEqual(false); expect(result.isExcluded(childOfPending)).toEqual(false);
}); });
it('throws if the specified order would re-enter a node that does not allow re-entry', function() { it('throws if the specified order would re-enter a node', function() {
const leaf1 = new Leaf(), const leaf1 = new Leaf(),
leaf2 = new Leaf(), leaf2 = new Leaf(),
leaf3 = new Leaf(), leaf3 = new Leaf(),
@@ -210,29 +208,7 @@ describe('TreeProcessor', function() {
expect(function() { expect(function() {
processor.processTree(); processor.processTree();
}).toThrowError( }).toThrowError('Invalid order: would split up a suite');
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
});
it('does not throw if a node being re-entered allows re-entry', function() {
const leaf1 = new Leaf();
const leaf2 = new Leaf();
const leaf3 = new Leaf();
const reentered = new Node({ children: [leaf1, leaf2] });
const root = new Node({ children: [reentered, leaf3] });
const processor = new privateUnderTest.TreeProcessor({
tree: root,
runnableIds: [leaf1.id, leaf3.id, leaf2.id]
});
const env = jasmineUnderTest.getEnv();
spyOn(env, 'deprecated');
processor.processTree();
expect(env.deprecated).toHaveBeenCalledWith(
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
);
}); });
it("does not throw if a node which can't be re-entered is only entered once", function() { it("does not throw if a node which can't be re-entered is only entered once", function() {

View File

@@ -180,7 +180,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite }]; return [{ suite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return []; return [];
}, },
isExcluded() { isExcluded() {
@@ -240,7 +240,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite: failingSuite }, { suite: passingSuite }]; return [{ suite: failingSuite }, { suite: passingSuite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return []; return [];
}, },
isExcluded() { isExcluded() {
@@ -308,7 +308,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite }]; return [{ suite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return [{ spec }]; return [{ spec }];
}, },
isExcluded() { isExcluded() {
@@ -371,7 +371,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite }]; return [{ suite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return [{ spec }]; return [{ spec }];
}, },
isExcluded() { isExcluded() {
@@ -519,7 +519,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite }]; return [{ suite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return [{ spec }]; return [{ spec }];
}, },
isExcluded() { isExcluded() {
@@ -588,7 +588,7 @@ describe('TreeRunner', function() {
childrenOfTopSuite() { childrenOfTopSuite() {
return [{ suite }]; return [{ suite }];
}, },
childrenOfSuiteSegment() { childrenOfSuite() {
return [{ spec }]; return [{ spec }];
}, },
isExcluded() { isExcluded() {

View File

@@ -601,37 +601,7 @@ describe('spec running', function() {
]); ]);
}); });
it('re-enters suites that have no *Alls', async function() { it('refuses to re-enter suites', async function() {
const actions = [];
let spec1;
let spec2;
let spec3;
env.describe('top', function() {
spec1 = env.it('spec1', function() {
actions.push('spec1');
});
spec2 = env.it('spec2', function() {
actions.push('spec2');
});
});
spec3 = env.it('spec3', function() {
actions.push('spec3');
});
spyOn(jasmineUnderTest.getEnv(), 'deprecated');
await env.execute([spec2.id, spec3.id, spec1.id]);
expect(actions).toEqual(['spec2', 'spec3', 'spec1']);
expect(jasmineUnderTest.getEnv().deprecated).toHaveBeenCalledWith(
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
);
});
it('refuses to re-enter suites with a beforeAll', async function() {
const actions = []; const actions = [];
let spec1; let spec1;
let spec2; let spec2;
@@ -654,34 +624,9 @@ describe('spec running', function() {
}); });
const promise = env.execute([spec2.id, spec3.id, spec1.id]); const promise = env.execute([spec2.id, spec3.id, spec1.id]);
await expectAsync(promise).toBeRejectedWithError(/beforeAll/); await expectAsync(promise).toBeRejectedWithError(
expect(actions).toEqual([]); 'Invalid order: would split up a suite'
}); );
it('refuses to re-enter suites with a afterAll', async function() {
const actions = [];
let spec1;
let spec2;
let spec3;
env.describe('top', function() {
env.afterAll(function() {});
spec1 = env.it('spec1', function() {
actions.push('spec1');
});
spec2 = env.it('spec2', function() {
actions.push('spec2');
});
});
spec3 = env.it('spec3', function() {
actions.push('spec3');
});
const promise = env.execute([spec2.id, spec3.id, spec1.id]);
await expectAsync(promise).toBeRejectedWithError(/afterAll/);
expect(actions).toEqual([]); expect(actions).toEqual([]);
}); });

View File

@@ -3,8 +3,7 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
const defaultMax = 1 - Infinity; const defaultMax = 1 - Infinity;
// Transforms the suite tree into an execution tree, which represents the set // Transforms the suite tree into an execution tree, which represents the set
// of specs and (possibly interleaved) suites to be run in the order they are // of specs and suites to be run in the order they are to be run in.
// to be run in.
class TreeProcessor { class TreeProcessor {
#tree; #tree;
#runnableIds; #runnableIds;
@@ -86,15 +85,7 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
segmentChildren(node, orderedChildren, this.#stats, executableIndex); segmentChildren(node, orderedChildren, this.#stats, executableIndex);
if (this.#stats[node.id].segments.length > 1) { if (this.#stats[node.id].segments.length > 1) {
if (node.canBeReentered()) { throw new Error('Invalid order: would split up a suite');
j$.getEnv().deprecated(
'The specified spec/suite order splits up a suite, running unrelated specs in the middle of it. This will become an error in a future release.'
);
} else {
throw new Error(
'Invalid order: would cause a beforeAll or afterAll to be run multiple times'
);
}
} }
} }
} }
@@ -112,15 +103,14 @@ getJasmineRequireObj().TreeProcessor = function(j$) {
} }
childrenOfTopSuite() { childrenOfTopSuite() {
return this.childrenOfSuiteSegment(this.topSuite, 0); return this.childrenOfSuite(this.topSuite);
} }
childrenOfSuiteSegment(suite, segmentNumber) { childrenOfSuite(suite) {
const segmentChildren = this.#stats[suite.id].segments[segmentNumber] const segmentChildren = this.#stats[suite.id].segments[0].nodes;
.nodes;
return segmentChildren.map(function(child) { return segmentChildren.map(function(child) {
if (child.owner.children) { if (child.owner.children) {
return { suite: child.owner, segmentNumber: child.index }; return { suite: child.owner };
} else { } else {
return { spec: child.owner }; return { spec: child.owner };
} }

View File

@@ -24,7 +24,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
async execute() { async execute() {
this.#hasFailures = false; this.#hasFailures = false;
await new Promise(resolve => { await new Promise(resolve => {
this.#executeSuiteSegment(this.#executionTree.topSuite, 0, resolve); this.#executeSuite(this.#executionTree.topSuite, resolve);
}); });
return { hasFailures: this.#hasFailures }; return { hasFailures: this.#hasFailures };
} }
@@ -34,7 +34,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
return { return {
fn: done => { fn: done => {
if (node.suite) { if (node.suite) {
this.#executeSuiteSegment(node.suite, node.segmentNumber, done); this.#executeSuite(node.suite, done);
} else { } else {
this._executeSpec(node.spec, done); this._executeSpec(node.spec, done);
} }
@@ -124,7 +124,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
return fns; return fns;
} }
#executeSuiteSegment(suite, segmentNumber, done) { #executeSuite(suite, done) {
const isTopSuite = suite === this.#executionTree.topSuite; const isTopSuite = suite === this.#executionTree.topSuite;
const isExcluded = this.#executionTree.isExcluded(suite); const isExcluded = this.#executionTree.isExcluded(suite);
let befores = []; let befores = [];
@@ -146,7 +146,7 @@ getJasmineRequireObj().TreeRunner = function(j$) {
const children = isTopSuite const children = isTopSuite
? this.#executionTree.childrenOfTopSuite() ? this.#executionTree.childrenOfTopSuite()
: this.#executionTree.childrenOfSuiteSegment(suite, segmentNumber); : this.#executionTree.childrenOfSuite(suite);
const queueableFns = [ const queueableFns = [
...befores, ...befores,
...this.#wrapNodes(children), ...this.#wrapNodes(children),