From 166e5f4d6c5018cba21fa68ff4bff329dab4b7e4 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 25 Feb 2023 10:24:14 -0800 Subject: [PATCH] Report the ID of each suite/spec's parent This is intended to support parallel execution, which is planned for a future release of Jasmine. Because the execution of unrelated suites will interleave when run in parallel, reporters will not be able to assume that the most recent `suiteStarted` event identifies the parent of the current suite/spec. By adding this feature now, we allow reporters to support both parallel execution and at least some 4.x versions without having to implement two different ways of finding the parent suite. --- lib/jasmine-core/jasmine-html.js | 2 +- lib/jasmine-core/jasmine.js | 14 ++- spec/core/SpecSpec.js | 2 + spec/core/integration/EnvSpec.js | 185 +++++++++++++++++++++++++++++-- src/core/Spec.js | 3 + src/core/Suite.js | 3 + src/core/SuiteBuilder.js | 8 +- 7 files changed, 202 insertions(+), 15 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 2ebc6d0e..47035410 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2022 Pivotal Labs +Copyright (c) 2008-2023 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 8d4de616..3e77af64 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -742,6 +742,7 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.filename = attrs.filename; + this.parentSuiteId = attrs.parentSuiteId; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = @@ -890,6 +891,7 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} id - The unique id of this spec. * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. + * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). * @property {String} filename - The name of the file the spec was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. @@ -905,6 +907,7 @@ getJasmineRequireObj().Spec = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.parentSuiteId, filename: this.filename, failedExpectations: [], passedExpectations: [], @@ -9533,6 +9536,7 @@ getJasmineRequireObj().Suite = function(j$) { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; + this.reportedParentSuiteId = attrs.reportedParentSuiteId; this.filename = attrs.filename; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; @@ -9639,6 +9643,7 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. + * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). * @property {String} filename - The name of the file the suite was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. @@ -9651,6 +9656,7 @@ getJasmineRequireObj().Suite = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.reportedParentSuiteId, filename: this.filename, failedExpectations: [], deprecationWarnings: [], @@ -10018,11 +10024,15 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { suiteFactory_(description, filename) { const config = this.env_.configuration(); + const parentSuite = this.currentDeclarationSuite_; + const reportedParentSuiteId = + parentSuite === this.topSuite ? null : parentSuite.id; return new j$.Suite({ id: 'suite' + this.nextSuiteId_++, description, filename, - parentSuite: this.currentDeclarationSuite_, + parentSuite, + reportedParentSuiteId, timer: new j$.Timer(), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.suiteAsyncExpectationFactory_, @@ -10058,9 +10068,11 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.totalSpecsDefined++; const config = this.env_.configuration(); const suite = this.currentDeclarationSuite_; + const parentSuiteId = suite === this.topSuite ? null : suite.id; const spec = new j$.Spec({ id: 'spec' + this.nextSpecId_++, filename, + parentSuiteId, beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.specAsyncExpectationFactory_, diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 40e84424..599760ed 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -195,6 +195,7 @@ describe('Spec', function() { onStart: startCallback, resultCallback: resultCallback, description: 'with a spec', + parentSuiteId: 'suite1', filename: 'someSpecFile.js', getSpecName: function() { return 'a suite with a spec'; @@ -220,6 +221,7 @@ describe('Spec', function() { status: 'pending', description: 'with a spec', fullName: 'a suite with a spec', + parentSuiteId: 'suite1', filename: 'someSpecFile.js', failedExpectations: [], passedExpectations: [], diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 4923a9bd..dbcaff2d 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1933,11 +1933,17 @@ describe('Env integration', function() { 'specStarted', 'specDone' ]); + const suiteFullNameToId = {}; + reporter.suiteStarted.and.callFake(function(e) { + suiteFullNameToId[e.fullName] = e.id; + }); env.addReporter(reporter); + env.it('a top level spec', function() {}); + env.describe('A Suite', function() { - env.it('with a top level spec', function() { + env.it('with a spec', function() { env.expect(true).toBe(true); }); env.describe('with a nested suite', function() { @@ -1960,37 +1966,109 @@ describe('Env integration', function() { await env.execute(); expect(reporter.jasmineStarted).toHaveBeenCalledWith({ - totalSpecsDefined: 5, + totalSpecsDefined: 6, order: jasmine.any(jasmineUnderTest.Order) }); - expect(reporter.specDone.calls.count()).toBe(5); + expect(reporter.specStarted.calls.count()).toBe(6); + expect(reporter.specDone.calls.count()).toBe(6); - expect(reporter.specDone).toHaveBeenCalledWith( + expect(reporter.specStarted).toHaveBeenCalledWith( jasmine.objectContaining({ - description: 'with a top level spec', - status: 'passed' + description: 'a top level spec', + parentSuiteId: null }) ); - expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ - description: "with an x'ed spec", - status: 'pending' + description: 'a top level spec', + status: 'passed', + parentSuiteId: null + }) + ); + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); - expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ description: 'with a spec', - status: 'failed' + status: 'passed', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: "with an x'ed spec", + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: "with an x'ed spec", + status: 'pending', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a spec', + status: 'failed', + parentSuiteId: suiteFullNameToId['A Suite with a nested suite'] + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'is pending', + parentSuiteId: + suiteFullNameToId['A Suite with only non-executable specs'] + }) + ); expect(reporter.specDone).toHaveBeenCalledWith( jasmine.objectContaining({ description: 'is pending', - status: 'pending' + status: 'pending', + parentSuiteId: + suiteFullNameToId['A Suite with only non-executable specs'] + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'A Suite', + parentSuiteId: null + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'A Suite', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a nested suite', + parentSuiteId: suiteFullNameToId['A Suite'] + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'with a nested suite', + status: 'passed', + parentSuiteId: suiteFullNameToId['A Suite'] }) ); @@ -2001,6 +2079,89 @@ describe('Env integration', function() { expect(suiteResult.description).toEqual('A Suite'); }); + it('reports focused specs and suites as expected', async function() { + const reporter = jasmine.createSpyObj('fakeReporter', [ + 'suiteStarted', + 'suiteDone', + 'specStarted', + 'specDone' + ]); + const suiteFullNameToId = {}; + reporter.suiteStarted.and.callFake(function(e) { + suiteFullNameToId[e.fullName] = e.id; + }); + + env.fit('a focused top level spec', function() {}); + + env.describe('a suite', function() { + env.fdescribe('a focused suite', function() { + env.fit('a focused spec', function() {}); + }); + }); + + env.addReporter(reporter); + await env.execute(); + + expect(reporter.specStarted).toHaveBeenCalledTimes(2); + expect(reporter.specDone).toHaveBeenCalledTimes(2); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused top level spec', + parentSuiteId: null + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused top level spec', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.specStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused spec', + parentSuiteId: suiteFullNameToId['a suite a focused suite'] + }) + ); + expect(reporter.specDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused spec', + status: 'passed', + parentSuiteId: suiteFullNameToId['a suite a focused suite'] + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a suite', + parentSuiteId: null + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a suite', + status: 'passed', + parentSuiteId: null + }) + ); + + expect(reporter.suiteStarted).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused suite', + parentSuiteId: suiteFullNameToId['a suite'] + }) + ); + expect(reporter.suiteDone).toHaveBeenCalledWith( + jasmine.objectContaining({ + description: 'a focused suite', + status: 'passed', + parentSuiteId: suiteFullNameToId['a suite'] + }) + ); + }); + it('should report the random seed at the beginning and end of execution', async function() { const reporter = jasmine.createSpyObj('fakeReporter', [ 'jasmineStarted', diff --git a/src/core/Spec.js b/src/core/Spec.js index 81544c46..52a24a5d 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -5,6 +5,7 @@ getJasmineRequireObj().Spec = function(j$) { this.resultCallback = attrs.resultCallback || function() {}; this.id = attrs.id; this.filename = attrs.filename; + this.parentSuiteId = attrs.parentSuiteId; this.description = attrs.description || ''; this.queueableFn = attrs.queueableFn; this.beforeAndAfterFns = @@ -153,6 +154,7 @@ getJasmineRequireObj().Spec = function(j$) { * @property {String} id - The unique id of this spec. * @property {String} description - The description passed to the {@link it} that created this spec. * @property {String} fullName - The full description including all ancestors of this spec. + * @property {String|null} parentSuiteId - The ID of the suite containing this spec, or null if this spec is not in a describe(). * @property {String} filename - The name of the file the spec was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed during execution of this spec. * @property {Expectation[]} passedExpectations - The list of expectations that passed during execution of this spec. @@ -168,6 +170,7 @@ getJasmineRequireObj().Spec = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.parentSuiteId, filename: this.filename, failedExpectations: [], passedExpectations: [], diff --git a/src/core/Suite.js b/src/core/Suite.js index 5a1a38a7..55a5227f 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -4,6 +4,7 @@ getJasmineRequireObj().Suite = function(j$) { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; + this.reportedParentSuiteId = attrs.reportedParentSuiteId; this.filename = attrs.filename; this.expectationFactory = attrs.expectationFactory; this.asyncExpectationFactory = attrs.asyncExpectationFactory; @@ -110,6 +111,7 @@ getJasmineRequireObj().Suite = function(j$) { * @property {String} id - The unique id of this suite. * @property {String} description - The description text passed to the {@link describe} that made this suite. * @property {String} fullName - The full description including all ancestors of this suite. + * @property {String|null} parentSuiteId - The ID of the suite containing this suite, or null if this is not in another describe(). * @property {String} filename - The name of the file the suite was defined in. * @property {Expectation[]} failedExpectations - The list of expectations that failed in an {@link afterAll} for this suite. * @property {Expectation[]} deprecationWarnings - The list of deprecation warnings that occurred on this suite. @@ -122,6 +124,7 @@ getJasmineRequireObj().Suite = function(j$) { id: this.id, description: this.description, fullName: this.getFullName(), + parentSuiteId: this.reportedParentSuiteId, filename: this.filename, failedExpectations: [], deprecationWarnings: [], diff --git a/src/core/SuiteBuilder.js b/src/core/SuiteBuilder.js index 50aabeb3..e02955b4 100644 --- a/src/core/SuiteBuilder.js +++ b/src/core/SuiteBuilder.js @@ -161,11 +161,15 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { suiteFactory_(description, filename) { const config = this.env_.configuration(); + const parentSuite = this.currentDeclarationSuite_; + const reportedParentSuiteId = + parentSuite === this.topSuite ? null : parentSuite.id; return new j$.Suite({ id: 'suite' + this.nextSuiteId_++, description, filename, - parentSuite: this.currentDeclarationSuite_, + parentSuite, + reportedParentSuiteId, timer: new j$.Timer(), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.suiteAsyncExpectationFactory_, @@ -201,9 +205,11 @@ getJasmineRequireObj().SuiteBuilder = function(j$) { this.totalSpecsDefined++; const config = this.env_.configuration(); const suite = this.currentDeclarationSuite_; + const parentSuiteId = suite === this.topSuite ? null : suite.id; const spec = new j$.Spec({ id: 'spec' + this.nextSpecId_++, filename, + parentSuiteId, beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: this.expectationFactory_, asyncExpectationFactory: this.specAsyncExpectationFactory_,