diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index cc918da0..ab0c4966 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -65,6 +65,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.StringMatching = jRequire.StringMatching(j$); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.matchers = jRequire.requireMatchers(jRequire, j$); @@ -330,12 +331,15 @@ getJasmineRequireObj().Spec = function(j$) { return this.expectationFactory(actual, this); }; - Spec.prototype.execute = function(onComplete) { + Spec.prototype.execute = function(onComplete, enabled) { var self = this; + if (enabled === false && !this.markedPending) { + this.disable(); + } this.onStart(this); - if (this.markedPending || this.disabled) { + if (!this.isExecutable()) { complete(); return; } @@ -386,6 +390,11 @@ getJasmineRequireObj().Spec = function(j$) { } }; + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + Spec.prototype.status = function() { if (this.disabled) { return 'disabled'; @@ -533,27 +542,21 @@ getJasmineRequireObj().Env = function(j$) { delete runnableResources[id]; }; - var beforeAndAfterFns = function(suite, runnablesExplictlySet) { + var beforeAndAfterFns = function(suite) { return function() { var befores = [], - afters = [], - beforeAlls = [], - afterAlls = []; + afters = []; while(suite) { befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); - if (runnablesExplictlySet()) { - beforeAlls = beforeAlls.concat(suite.beforeAllFns); - afterAlls = afterAlls.concat(suite.afterAllFns); - } - suite = suite.parentSuite; } + return { - befores: beforeAlls.reverse().concat(befores.reverse()), - afters: afters.concat(afterAlls) + befores: befores.reverse(), + afters: afters }; }; }; @@ -623,26 +626,40 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { - if(runnablesToRun) { - runnablesExplictlySet = true; - } else if (focusedRunnables.length) { - runnablesExplictlySet = true; - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [topSuite.id]; + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } } + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + } + }); - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); + processor.execute(reporter.jasmineDone); }; this.addReporter = function(reporterToAdd) { @@ -666,28 +683,12 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: description, parentSuite: currentDeclarationSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, expectationFactory: expectationFactory, - expectationResultFactory: expectationResultFactory, - runnablesExplictlySetGetter: runnablesExplictlySetGetter, - resultCallback: function(attrs) { - if (!suite.disabled) { - clearResourcesForRunnable(suite.id); - } - currentlyExecutingSuites.pop(); - reporter.suiteDone(attrs); - } + expectationResultFactory: expectationResultFactory }); runnableLookupTable[suite.id] = suite; return suite; - - function suiteStarted(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); - } }; this.describe = function(description, specDefinitions) { @@ -759,17 +760,11 @@ getJasmineRequireObj().Env = function(j$) { } } - var runnablesExplictlySet = false; - - var runnablesExplictlySetGetter = function(){ - return runnablesExplictlySet; - }; - var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; var spec = new j$.Spec({ id: getNextSpecId(), - beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), + beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { @@ -1965,18 +1960,13 @@ getJasmineRequireObj().Suite = function() { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; this.expectationFactory = attrs.expectationFactory; this.expectationResultFactory = attrs.expectationResultFactory; - this.runnablesExplictlySetGetter = attrs.runnablesExplictlySetGetter || function() {}; this.beforeFns = []; this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; - this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; this.children = []; @@ -2039,51 +2029,17 @@ getJasmineRequireObj().Suite = function() { } }; - Suite.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.disabled) { - complete(); - return; - } - - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); - } - - if (this.isExecutable()) { - allFns = this.beforeAllFns.concat(allFns); - allFns = allFns.concat(this.afterAllFns); - } - - this.queueRunner({ - queueableFns: allFns, - onComplete: complete, - userContext: this.sharedUserContext(), - onException: function() { self.onException.apply(self, arguments); } - }); - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - - function wrapChildAsAsync(child) { - return { fn: function(done) { child.execute(done); } }; - } + Suite.prototype.isExecutable = function() { + return !this.disabled; }; - Suite.prototype.isExecutable = function() { - var runnablesExplicitlySet = this.runnablesExplictlySetGetter(); - return !runnablesExplicitlySet && hasExecutableChild(this.children); + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; }; Suite.prototype.sharedUserContext = function() { @@ -2136,17 +2092,6 @@ getJasmineRequireObj().Suite = function() { return !args[0]; } - function hasExecutableChild(children) { - var foundActive = false; - for (var i = 0; i < children.length; i++) { - if (children[i].isExecutable()) { - foundActive = true; - break; - } - } - return foundActive; - } - function clone(obj) { var clonedObj = {}; for (var prop in obj) { @@ -2188,6 +2133,214 @@ getJasmineRequireObj().Timer = function() { return Timer; }; +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild, + }; + + segmentChildren(node, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren(node, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(node.children); + + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; + + if (lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1) { + currentSegment = { index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMin }; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(array) { + var result = []; + + for (var i = 0; i < array.length; i++) { + var child = array[i], + segments = stats[child.id].segments; + + for (var j = 0; j < segments.length; j++) { + result.push(segments[j]); + } + } + + result.sort(function(a, b) { + if (a.min === null) { + return b.min === null ? 0 : 1; + } + + if (b.min === null) { + return -1; + } + + return a.min - b.min; + }); + + return result; + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } + + function minValue(array) { + return Math.min.apply(null, array); + } + + function maxValue(array) { + return Math.max.apply(null, array); + } + + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + + return TreeProcessor; +}; + getJasmineRequireObj().Any = function() { function Any(expectedObject) { diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 3bb0fcb0..5ace439f 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -148,6 +148,29 @@ describe("Spec", function() { expect(resultCallback).toHaveBeenCalled(); }); + it("can be disabled at execution time by a parent", function() { + var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), + startCallback = jasmine.createSpy('startCallback'), + specBody = jasmine.createSpy('specBody'), + resultCallback = jasmine.createSpy('resultCallback'), + spec = new j$.Spec({ + onStart:startCallback, + queueableFn: { fn: specBody }, + resultCallback: resultCallback, + queueRunnerFactory: fakeQueueRunner + }); + + spec.execute(undefined, false); + + expect(spec.result.status).toBe('disabled'); + + expect(fakeQueueRunner).not.toHaveBeenCalled(); + expect(specBody).not.toHaveBeenCalled(); + + expect(startCallback).toHaveBeenCalled(); + expect(resultCallback).toHaveBeenCalled(); + }); + it("can be marked pending, but still calls callbacks when executed", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), startCallback = jasmine.createSpy('startCallback'), @@ -244,7 +267,7 @@ describe("Spec", function() { expect(specNameSpy.calls.mostRecent().args[0].id).toEqual(spec.id); }); - describe("when a spec is marked pending during execution", function() { + describe("when a spec is marked pending during execution", function() { it("should mark the spec as pending", function() { var fakeQueueRunner = function(opts) { opts.onException(new Error(j$.Spec.pendingSpecExceptionMessage)); @@ -279,4 +302,50 @@ describe("Spec", function() { expect(spec.result.pendingReason).toEqual('custom message'); }); }); + + it("retrieves a result with updated status", function() { + var spec = new j$.Spec({ queueableFn: { fn: function() {} } }); + + expect(spec.getResult().status).toBe('passed'); + }); + + it("retrives a result with disabled status", function() { + var spec = new j$.Spec({ queueableFn: { fn: function() {} } }); + spec.disable(); + + expect(spec.getResult().status).toBe('disabled'); + }); + + it("retrives a result with pending status", function() { + var spec = new j$.Spec({ queueableFn: { fn: function() {} } }); + spec.pend(); + + expect(spec.getResult().status).toBe('pending'); + }); + + it("should not be executable when disabled", function() { + var spec = new j$.Spec({ + queueableFn: { fn: function() {} } + }); + spec.disable(); + + expect(spec.isExecutable()).toBe(false); + }); + + it("should not be executable when pending", function() { + var spec = new j$.Spec({ + queueableFn: { fn: function() {} } + }); + spec.pend(); + + expect(spec.isExecutable()).toBe(false); + }); + + it("should be executable when not disabled or pending", function() { + var spec = new j$.Spec({ + queueableFn: { fn: function() {} } + }); + + expect(spec.isExecutable()).toBe(true); + }); }); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 60ce8a1d..6093f848 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -52,31 +52,6 @@ describe("Suite", function() { expect(suite.beforeFns).toEqual([innerBefore, outerBefore]); }); - it("runs beforeAll functions in order of needed execution", function() { - var env = new j$.Env(), - fakeQueueRunner = jasmine.createSpy('fake queue runner'), - suite = new j$.Suite({ - env: env, - description: "I am a suite", - queueRunner: fakeQueueRunner - }), - firstBefore = jasmine.createSpy('outerBeforeAll'), - lastBefore = jasmine.createSpy('insideBeforeAll'), - fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; - - suite.beforeAll(firstBefore); - suite.beforeAll(lastBefore); - suite.addChild(fakeIt); - - suite.execute(); - var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; - - suiteFns[0](); - expect(firstBefore).toHaveBeenCalled(); - suiteFns[1](); - expect(lastBefore).toHaveBeenCalled(); - }); - it("adds after functions in order of needed execution", function() { var env = new j$.Env(), suite = new j$.Suite({ @@ -92,244 +67,6 @@ describe("Suite", function() { expect(suite.afterFns).toEqual([innerAfter, outerAfter]); }); - it("runs afterAll functions in order of needed execution", function() { - var env = new j$.Env(), - fakeQueueRunner = jasmine.createSpy('fake queue runner'), - suite = new j$.Suite({ - env: env, - description: "I am a suite", - queueRunner: fakeQueueRunner - }), - firstAfter = jasmine.createSpy('outerAfterAll'), - lastAfter = jasmine.createSpy('insideAfterAll'), - fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; - - suite.afterAll(firstAfter); - suite.afterAll(lastAfter); - suite.addChild(fakeIt); - - suite.execute(); - var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; - - suiteFns[1](); - expect(firstAfter).toHaveBeenCalled(); - suiteFns[2](); - expect(lastAfter).toHaveBeenCalled(); - }); - - it("does not run *All functions if runnables are explicitly set", function(){ - var env = new j$.Env(), - fakeQueueRunner = jasmine.createSpy('fake queue runner'), - suite = new j$.Suite({ - env: env, - description: "I am a suite", - queueRunner: fakeQueueRunner, - runnablesExplictlySetGetter: function(){return true;} - }), - beforeAll = jasmine.createSpy('beforeAll'), - afterAll = jasmine.createSpy('afterAll'), - fakeIt = {execute: jasmine.createSpy('it'), isExecutable: function() { return true; } }; - - suite.beforeAll(beforeAll); - suite.afterAll(afterAll); - suite.addChild(fakeIt); - - suite.execute(); - var suiteFns = fakeQueueRunner.calls.mostRecent().args[0].queueableFns; - - expect(suite.isExecutable()).toBeFalsy(); - expect(suiteFns.length).toEqual(1); - expect(beforeAll).not.toHaveBeenCalled(); - expect(afterAll).not.toHaveBeenCalled(); - }); - - it("can be disabled, but still calls callbacks", function() { - var env = new j$.Env(), - fakeQueueRunner = jasmine.createSpy('fake queue runner'), - onStart = jasmine.createSpy('onStart'), - resultCallback = jasmine.createSpy('resultCallback'), - onComplete = jasmine.createSpy('onComplete'), - suite = new j$.Suite({ - env: env, - description: "with a child suite", - onStart: onStart, - resultCallback: resultCallback, - queueRunner: fakeQueueRunner - }); - - suite.disable(); - - expect(suite.disabled).toBe(true); - - suite.execute(onComplete); - - expect(fakeQueueRunner).not.toHaveBeenCalled(); - expect(onStart).toHaveBeenCalled(); - expect(resultCallback).toHaveBeenCalled(); - expect(onComplete).toHaveBeenCalled(); - }); - - it("delegates execution of its specs, suites, beforeAlls, and afterAlls", function() { - var env = new j$.Env(), - parentSuiteDone = jasmine.createSpy('parent suite done'), - fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), - parentSuite = new j$.Suite({ - env: env, - description: "I am a parent suite", - queueRunner: fakeQueueRunnerForParent - }), - fakeQueueRunner = jasmine.createSpy('fake queue runner'), - suite = new j$.Suite({ - env: env, - description: "with a child suite", - queueRunner: fakeQueueRunner - }), - fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1'), - isExecutable: function() { return true; } - }, - beforeAllFn = { fn: jasmine.createSpy('beforeAll') }, - afterAllFn = { fn: jasmine.createSpy('afterAll') }; - - spyOn(suite, "execute"); - - parentSuite.addChild(fakeSpec1); - parentSuite.addChild(suite); - parentSuite.beforeAll(beforeAllFn); - parentSuite.afterAll(afterAllFn); - - parentSuite.execute(parentSuiteDone); - - var parentSuiteFns = fakeQueueRunnerForParent.calls.mostRecent().args[0].queueableFns; - - parentSuiteFns[0].fn(); - expect(beforeAllFn.fn).toHaveBeenCalled(); - parentSuiteFns[1].fn(); - expect(fakeSpec1.execute).toHaveBeenCalled(); - parentSuiteFns[2].fn(); - expect(suite.execute).toHaveBeenCalled(); - parentSuiteFns[3].fn(); - expect(afterAllFn.fn).toHaveBeenCalled(); - }); - - it("does not run beforeAll or afterAll if there are no executable child specs", function() { - var env = new j$.Env(), - fakeQueueRunnerForParent = jasmine.createSpy('fake parent queue runner'), - fakeQueueRunnerForChild = jasmine.createSpy('fake child queue runner'), - parentSuite = new j$.Suite({ - env: env, - description: "I am a suite", - queueRunner: fakeQueueRunnerForParent - }), - childSuite = new j$.Suite({ - env: env, - description: "I am a suite", - queueRunner: fakeQueueRunnerForChild, - parentSuite: parentSuite - }), - beforeAllFn = jasmine.createSpy('beforeAll'), - afterAllFn = jasmine.createSpy('afterAll'); - - parentSuite.addChild(childSuite); - parentSuite.beforeAll(beforeAllFn); - parentSuite.afterAll(afterAllFn); - - parentSuite.execute(); - expect(fakeQueueRunnerForParent).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{ fn: jasmine.any(Function) }] - })); - }); - - it("calls a provided onStart callback when starting", function() { - var env = new j$.Env(), - suiteStarted = jasmine.createSpy('suiteStarted'), - fakeQueueRunner = function(attrs) { attrs.onComplete(); }, - suite = new j$.Suite({ - env: env, - description: "with a child suite", - onStart: suiteStarted, - queueRunner: fakeQueueRunner - }), - fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1'), - isExecutable: function() { return true; } - }; - - suite.execute(); - - expect(suiteStarted).toHaveBeenCalledWith(suite); - }); - - it("calls a provided onComplete callback when done", function() { - var env = new j$.Env(), - suiteCompleted = jasmine.createSpy('parent suite done'), - fakeQueueRunner = function(attrs) { attrs.onComplete(); }, - suite = new j$.Suite({ - env: env, - description: "with a child suite", - queueRunner: fakeQueueRunner - }), - fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') - }; - - suite.execute(suiteCompleted); - - expect(suiteCompleted).toHaveBeenCalled(); - }); - - it("calls a provided result callback when done", function() { - var env = new j$.Env(), - suiteResultsCallback = jasmine.createSpy('suite result callback'), - fakeQueueRunner = function(attrs) { attrs.onComplete(); }, - suite = new j$.Suite({ - env: env, - description: "with a child suite", - queueRunner: fakeQueueRunner, - resultCallback: suiteResultsCallback - }), - fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') - }; - - suite.execute(); - - expect(suiteResultsCallback).toHaveBeenCalledWith({ - id: suite.id, - status: 'finished', - description: "with a child suite", - fullName: "with a child suite", - failedExpectations: [] - }); - }); - - it("calls a provided result callback with status being disabled when disabled and done", function() { - var env = new j$.Env(), - suiteResultsCallback = jasmine.createSpy('suite result callback'), - fakeQueueRunner = function(attrs) { attrs.onComplete(); }, - suite = new j$.Suite({ - env: env, - description: "with a child suite", - queueRunner: fakeQueueRunner, - resultCallback: suiteResultsCallback - }), - fakeSpec1 = { - execute: jasmine.createSpy('fakeSpec1') - }; - - suite.disable(); - - suite.execute(); - - expect(suiteResultsCallback).toHaveBeenCalledWith({ - id: suite.id, - status: 'disabled', - description: "with a child suite", - fullName: "with a child suite", - failedExpectations: [] - }); - }); - it('has a status of failed if any afterAll expectations have failed', function() { var suite = new j$.Suite({ expectationResultFactory: function() { return 'hi'; } @@ -339,4 +76,30 @@ describe("Suite", function() { suite.addExpectationResult(false); expect(suite.status()).toBe('failed'); }); + + it("retrieves a result with updated status", function() { + var suite = new j$.Suite({}); + + expect(suite.getResult().status).toBe('finished'); + }); + + it("retrives a result with disabled status", function() { + var suite = new j$.Suite({}); + suite.disable(); + + expect(suite.getResult().status).toBe('disabled'); + }); + + it("is executable if not disabled", function() { + var suite = new j$.Suite({}); + + expect(suite.isExecutable()).toBe(true); + }); + + it("is not executable if disabled", function() { + var suite = new j$.Suite({}); + suite.disable(); + + expect(suite.isExecutable()).toBe(false); + }); }); diff --git a/spec/core/TreeProcessorSpec.js b/spec/core/TreeProcessorSpec.js new file mode 100644 index 00000000..e2badd5d --- /dev/null +++ b/spec/core/TreeProcessorSpec.js @@ -0,0 +1,565 @@ +describe("TreeProcessor", function() { + var nodeNumber = 0, leafNumber = 0; + + function Node(attrs) { + attrs = attrs || {}; + this.id = 'node' + nodeNumber++; + this.children = attrs.children || []; + this.canBeReentered = function() { + return !attrs.noReenter; + }; + this.isExecutable = function() { + return attrs.executable !== false; + }; + this.sharedUserContext = function() { + return attrs.userContext || {}; + }; + this.getResult = jasmine.createSpy(this.id + '#execute'); + this.beforeAllFns = attrs.beforeAllFns || []; + this.afterAllFns = attrs.afterAllFns || []; + } + + function Leaf(attrs) { + attrs = attrs || {}; + this.id = 'leaf' + leafNumber++; + this.isExecutable = function() { + return attrs.executable !== false; + }; + this.execute = jasmine.createSpy(this.id + '#execute'); + } + + it("processes a single executable leaf", function() { + var leaf = new Leaf(), + processor = new j$.TreeProcessor({ tree: leaf, runnableIds: [leaf.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[leaf.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + }); + + it("processes a single non-executable leaf", function() { + var leaf = new Leaf({ executable: false }), + processor = new j$.TreeProcessor({ tree: leaf, runnableIds: [leaf.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[leaf.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + }); + + it("processes a single non-specified leaf", function() { + var leaf = new Leaf(), + processor = new j$.TreeProcessor({ tree: leaf, runnableIds: [] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[leaf.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + }); + + it("processes a tree with a single leaf with the root specified", function() { + var leaf = new Leaf(), + parent = new Node({ children: [leaf] }), + processor = new j$.TreeProcessor({ tree: parent, runnableIds: [parent.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[parent.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + + expect(result[leaf.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + }); + + it("processes a tree with a single non-executable leaf, with the root specified", function() { + var leaf = new Leaf({ executable: false }), + parent = new Node({ children: [leaf] }), + processor = new j$.TreeProcessor({ tree: parent, runnableIds: [parent.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[parent.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + + expect(result[leaf.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + }); + + it("processes a complicated tree with the root specified", function() { + var nonExecutable = new Leaf({ executable: false }), + executable = new Leaf({ executable: true }), + parent = new Node({ children: [nonExecutable, executable] }), + childless = new Node(), + childOfDisabled = new Leaf({ executable: true }), + disabledNode = new Node({ executable: false, children: [childOfDisabled] }), + root = new Node({ children: [parent, childless, disabledNode] }), + processor = new j$.TreeProcessor({ tree: root, runnableIds: [root.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[root.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + + expect(result[childless.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + + expect(result[nonExecutable.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + + expect(result[executable.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + + expect(result[parent.id]).toEqual({ + executable: true, + segments: jasmine.any(Array) + }); + + expect(result[disabledNode.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + + expect(result[childOfDisabled.id]).toEqual({ + executable: false, + segments: jasmine.any(Array) + }); + }); + + it("marks the run order invalid if it would re-enter a node that does not allow re-entry", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + leaf3 = new Leaf(), + reentered = new Node({ noReenter: true, children: [leaf1, leaf2] }), + root = new Node({ children: [reentered, leaf3] }), + processor = new j$.TreeProcessor({ tree: root, runnableIds: [leaf1.id, leaf3.id, leaf2.id] }), + result = processor.processTree(); + + expect(result).toEqual({ valid: false }); + }); + + it("marks the run order valid if a node being re-entered allows re-entry", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + leaf3 = new Leaf(), + reentered = new Node({ children: [leaf1, leaf2] }), + root = new Node({ children: [reentered, leaf3] }), + processor = new j$.TreeProcessor({ tree: root, runnableIds: [leaf1.id, leaf3.id, leaf2.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + }); + + it("marks the run order valid if a node which can't be re-entered is only entered once", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + leaf3 = new Leaf(), + noReentry = new Node({ noReenter: true }), + root = new Node({ children: [noReentry] }), + processor = new j$.TreeProcessor({ tree: root, runnableIds: [leaf2.id, leaf1.id, leaf3.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + }); + + it("marks the run order valid if a node which can't be re-entered is run directly", function() { + var leaf1 = new Leaf(), + noReentry = new Node({ noReenter: true }), + root = new Node({ children: [noReentry] }), + processor = new j$.TreeProcessor({ tree: root, runnableIds: [root.id] }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + }); + + it("runs a single leaf", function() { + var leaf = new Leaf(), + node = new Node({ children: [leaf] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ tree: node, runnableIds: [leaf.id], queueRunnerFactory: queueRunner }), + treeComplete = jasmine.createSpy('treeComplete'); + + processor.execute(treeComplete); + + expect(queueRunner).toHaveBeenCalledWith({ + onComplete: treeComplete, + onException: jasmine.any(Function), + queueableFns: [{ fn: jasmine.any(Function) }] + }); + + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); + + expect(leaf.execute).toHaveBeenCalledWith('foo', true); + }); + + it("runs a node with no children", function() { + var node = new Node({ userContext: { node: 'context' } }), + root = new Node({ children: [node] }), + nodeStart = jasmine.createSpy('nodeStart'), + nodeComplete = jasmine.createSpy('nodeComplete'), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + nodeStart: nodeStart, + nodeComplete: nodeComplete, + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + + expect(queueRunner).toHaveBeenCalledWith({ + onComplete: treeComplete, + onException: jasmine.any(Function), + queueableFns: [{ fn: jasmine.any(Function) }] + }); + + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(nodeDone); + + expect(nodeStart).toHaveBeenCalledWith(node); + expect(queueRunner).toHaveBeenCalledWith({ + onComplete: jasmine.any(Function), + queueableFns: [], + userContext: { node: 'context' }, + onException: jasmine.any(Function) + }); + + node.getResult.and.returnValue({ my: 'result' }); + + queueRunner.calls.mostRecent().args[0].onComplete(); + expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }); + expect(nodeDone).toHaveBeenCalled(); + }); + + it("runs a node with children", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + node = new Node({ children: [leaf1, leaf2] }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + expect(queueableFns.length).toBe(2); + + queueableFns[0].fn('foo'); + expect(leaf1.execute).toHaveBeenCalledWith('foo', true); + + queueableFns[1].fn('bar'); + expect(leaf2.execute).toHaveBeenCalledWith('bar', true); + }); + + it("runs a disabled node", function() { + var leaf1 = new Leaf(), + node = new Node({ children: [leaf1], executable: false }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + nodeStart = jasmine.createSpy('nodeStart'), + nodeComplete = jasmine.createSpy('nodeComplete'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner, + nodeStart: nodeStart, + nodeComplete: nodeComplete + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + expect(nodeStart).toHaveBeenCalledWith(node); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + expect(queueableFns.length).toBe(1); + + queueableFns[0].fn('foo'); + expect(leaf1.execute).toHaveBeenCalledWith('foo', false); + + node.getResult.and.returnValue({ im: 'disabled' }); + + queueRunner.calls.mostRecent().args[0].onComplete(); + expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }); + }); + + it("runs beforeAlls for a node with children", function() { + var leaf = new Leaf(), + node = new Node({ + children: [leaf], + beforeAllFns: ['beforeAll1', 'beforeAll2'] + }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + + expect(queueableFns).toEqual(['beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]); + }); + + it("runs afterAlls for a node with children", function() { + var leaf = new Leaf(), + node = new Node({ + children: [leaf], + afterAllFns: ['afterAll1', 'afterAll2'] + }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']); + }); + + it("does not run beforeAlls or afterAlls for a node with no children", function() { + var node = new Node({ + beforeAllFns: ['before'], + afterAllFns: ['after'] + }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + + expect(queueableFns).toEqual([]); + }); + + it("does not run beforeAlls or afterAlls for a disabled node", function() { + var leaf = new Leaf(), + node = new Node({ + children: [leaf], + beforeAllFns: ['before'], + afterAllFns: ['after'], + executable: false + }), + root = new Node({ children: [node] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [node.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'), + nodeDone = jasmine.createSpy('nodeDone'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(nodeDone); + + queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }]); + }); + + it("runs leaves in the order specified", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + root = new Node({ children: [leaf1, leaf2] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [leaf2.id, leaf1.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(); + + expect(leaf1.execute).not.toHaveBeenCalled(); + expect(leaf2.execute).toHaveBeenCalled(); + + queueableFns[1].fn(); + + expect(leaf1.execute).toHaveBeenCalled(); + }); + + it("runs specified leaves before non-specified leaves", function() { + var specified = new Leaf(), + nonSpecified = new Leaf(), + root = new Node({ children: [nonSpecified, specified] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [specified.id], + queueRunnerFactory: queueRunner + }), + treeComplete = jasmine.createSpy('treeComplete'); + + processor.execute(treeComplete); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(); + + expect(nonSpecified.execute).not.toHaveBeenCalled(); + expect(specified.execute).toHaveBeenCalledWith(undefined, true); + + queueableFns[1].fn(); + + expect(nonSpecified.execute).toHaveBeenCalledWith(undefined, false); + }); + + it("runs nodes and leaves with a specified order", function() { + var specifiedLeaf = new Leaf(), + childLeaf = new Leaf(), + specifiedNode = new Node({ children: [childLeaf] }), + root = new Node({ children: [specifiedLeaf, specifiedNode] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [specifiedNode.id, specifiedLeaf.id], + queueRunnerFactory: queueRunner + }); + + processor.execute(); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + queueableFns[0].fn(); + + expect(specifiedLeaf.execute).not.toHaveBeenCalled(); + var nodeQueueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + nodeQueueableFns[0].fn(); + + expect(childLeaf.execute).toHaveBeenCalled(); + + queueableFns[1].fn(); + + expect(specifiedLeaf.execute).toHaveBeenCalled(); + }); + + it("runs a node twice if the order specified leaves and re-enters it", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + leaf3 = new Leaf(), + reentered = new Node({ children: [leaf1, leaf2] }), + root = new Node({ children: [reentered, leaf3] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [leaf1.id, leaf3.id, leaf2.id], + queueRunnerFactory: queueRunner + }); + + processor.execute(); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + expect(queueableFns.length).toBe(3); + + queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(leaf1.execute).toHaveBeenCalled(); + + queueableFns[1].fn(); + expect(leaf3.execute).toHaveBeenCalled(); + + queueableFns[2].fn(); + expect(queueRunner.calls.count()).toBe(3); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(leaf2.execute).toHaveBeenCalled(); + }); + + it("runs nodes in the order they were declared", function() { + var leaf1 = new Leaf(), + leaf2 = new Leaf(), + leaf3 = new Leaf(), + parent = new Node({ children: [leaf2, leaf3] }), + root = new Node({ children: [leaf1, parent] }), + queueRunner = jasmine.createSpy('queueRunner'), + processor = new j$.TreeProcessor({ + tree: root, + runnableIds: [root.id], + queueRunnerFactory: queueRunner + }); + + processor.execute(); + var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; + expect(queueableFns.length).toBe(2); + + queueableFns[0].fn(); + expect(leaf1.execute).toHaveBeenCalled(); + + queueableFns[1].fn(); + + var childFns = queueRunner.calls.mostRecent().args[0].queueableFns; + expect(childFns.length).toBe(2); + childFns[0].fn(); + expect(leaf2.execute).toHaveBeenCalled(); + + childFns[1].fn(); + expect(leaf3.execute).toHaveBeenCalled(); + }); +}); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 82c5d09f..de2ed90e 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -612,8 +612,6 @@ describe("Env integration", function() { expect(calls).toEqual([ "before", "first spec", - "after", - "before", "second spec", "after" ]); @@ -1208,7 +1206,7 @@ describe("Env integration", function() { totalSpecsDefined: 1 }); - expect(reporter.specDone).not.toHaveBeenCalled(); + expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({ status: 'disabled' })); expect(reporter.suiteDone.calls.count()).toBe(3); done(); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index d977d451..3b6c731f 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -1,6 +1,5 @@ describe("jasmine spec running", function () { var env; - var fakeTimer; beforeEach(function() { env = new j$.Env(); @@ -61,13 +60,17 @@ describe("jasmine spec running", function () { expect(bar).toEqual(0); expect(baz).toEqual(0); expect(quux).toEqual(0); - nested.execute(function() { + var assertions = function() { expect(foo).toEqual(1); expect(bar).toEqual(1); expect(baz).toEqual(1); expect(quux).toEqual(1); done(); - }); + }; + + env.addReporter({ jasmineDone: assertions }); + + env.execute(); }); it("should permit nested describes", function(done) { @@ -289,7 +292,7 @@ describe("jasmine spec running", function () { env.execute(); }); - it('should run beforeAlls and afterAlls as beforeEachs and afterEachs in the order declared when runnablesToRun is provided', function(done) { + it('should run beforeAlls and afterAlls in the order declared when runnablesToRun is provided', function(done) { var actions = [], spec, spec2; @@ -342,17 +345,13 @@ describe("jasmine spec running", function () { "inner beforeAll", "runner beforeEach", "inner beforeEach", - "it", + "it2", "inner afterEach", "runner afterEach", - "inner afterAll", - "runner afterAll", - "runner beforeAll", - "inner beforeAll", "runner beforeEach", "inner beforeEach", - "it2", + "it", "inner afterEach", "runner afterEach", "inner afterAll", @@ -363,7 +362,7 @@ describe("jasmine spec running", function () { }; env.addReporter({jasmineDone: assertions}); - env.execute([spec.id, spec2.id]); + env.execute([spec2.id, spec.id]); }); it('only runs *Alls once in a focused suite', function(done){ @@ -416,9 +415,7 @@ describe("jasmine spec running", function () { 'beforeEach', 'spec in fdescribe', 'afterEach', - 'afterAll', - 'beforeAll', 'beforeEach', 'focused spec', 'afterEach', @@ -549,10 +546,14 @@ describe("jasmine spec running", function () { pendingSpec = env.it("I am a pending spec"); }); - suite.execute(function() { + var assertions = function() { expect(pendingSpec.status()).toBe("pending"); done(); - }); + }; + + env.addReporter({jasmineDone: assertions}); + + env.execute(); }); // TODO: is this useful? It doesn't catch syntax errors @@ -603,4 +604,96 @@ describe("jasmine spec running", function () { )); }); + + it("re-enters suites that have no *Alls", function(done) { + var actions = [], + spec1, spec2, 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"); + }); + + env.addReporter({ + jasmineDone: function() { + expect(actions).toEqual(["spec2", "spec3", "spec1"]); + done(); + } + }); + + env.execute([spec2.id, spec3.id, spec1.id]); + }); + + it("refuses to re-enter suites with a beforeAll", function() { + var actions = [], + spec1, spec2, spec3; + + env.describe("top", function() { + env.beforeAll(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"); + }); + + env.addReporter({ + jasmineDone: function() { + expect(actions).toEqual([]); + done(); + } + }); + + expect(function() { + env.execute([spec2.id, spec3.id, spec1.id]); + }).toThrowError(/beforeAll/); + }); + + it("refuses to re-enter suites with a afterAll", function() { + var actions = [], + spec1, spec2, 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"); + }); + + env.addReporter({ + jasmineDone: function() { + expect(actions).toEqual([]); + done(); + } + }); + + expect(function() { + env.execute([spec2.id, spec3.id, spec1.id]); + }).toThrowError(/afterAll/); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 0bd0d1d8..f57f2f79 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -100,27 +100,21 @@ getJasmineRequireObj().Env = function(j$) { delete runnableResources[id]; }; - var beforeAndAfterFns = function(suite, runnablesExplictlySet) { + var beforeAndAfterFns = function(suite) { return function() { var befores = [], - afters = [], - beforeAlls = [], - afterAlls = []; + afters = []; while(suite) { befores = befores.concat(suite.beforeFns); afters = afters.concat(suite.afterFns); - if (runnablesExplictlySet()) { - beforeAlls = beforeAlls.concat(suite.beforeAllFns); - afterAlls = afterAlls.concat(suite.afterAllFns); - } - suite = suite.parentSuite; } + return { - befores: beforeAlls.reverse().concat(befores.reverse()), - afters: afters.concat(afterAlls) + befores: befores.reverse(), + afters: afters }; }; }; @@ -190,26 +184,40 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { - if(runnablesToRun) { - runnablesExplictlySet = true; - } else if (focusedRunnables.length) { - runnablesExplictlySet = true; - runnablesToRun = focusedRunnables; - } else { - runnablesToRun = [topSuite.id]; + if(!runnablesToRun) { + if (focusedRunnables.length) { + runnablesToRun = focusedRunnables; + } else { + runnablesToRun = [topSuite.id]; + } } + var processor = new j$.TreeProcessor({ + tree: topSuite, + runnableIds: runnablesToRun, + queueRunnerFactory: queueRunnerFactory, + nodeStart: function(suite) { + currentlyExecutingSuites.push(suite); + defaultResourcesForRunnable(suite.id, suite.parentSuite.id); + reporter.suiteStarted(suite.result); + }, + nodeComplete: function(suite, result) { + if (!suite.disabled) { + clearResourcesForRunnable(suite.id); + } + currentlyExecutingSuites.pop(); + reporter.suiteDone(result); + } + }); - var allFns = []; - for(var i = 0; i < runnablesToRun.length; i++) { - var runnable = runnableLookupTable[runnablesToRun[i]]; - allFns.push((function(runnable) { return { fn: function(done) { runnable.execute(done); } }; })(runnable)); + if(!processor.processTree().valid) { + throw new Error('Invalid order: would cause a beforeAll or afterAll to be run multiple times'); } reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined }); - queueRunnerFactory({queueableFns: allFns, onComplete: reporter.jasmineDone}); + processor.execute(reporter.jasmineDone); }; this.addReporter = function(reporterToAdd) { @@ -233,28 +241,12 @@ getJasmineRequireObj().Env = function(j$) { id: getNextSuiteId(), description: description, parentSuite: currentDeclarationSuite, - queueRunner: queueRunnerFactory, - onStart: suiteStarted, expectationFactory: expectationFactory, - expectationResultFactory: expectationResultFactory, - runnablesExplictlySetGetter: runnablesExplictlySetGetter, - resultCallback: function(attrs) { - if (!suite.disabled) { - clearResourcesForRunnable(suite.id); - } - currentlyExecutingSuites.pop(); - reporter.suiteDone(attrs); - } + expectationResultFactory: expectationResultFactory }); runnableLookupTable[suite.id] = suite; return suite; - - function suiteStarted(suite) { - currentlyExecutingSuites.push(suite); - defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); - } }; this.describe = function(description, specDefinitions) { @@ -326,17 +318,11 @@ getJasmineRequireObj().Env = function(j$) { } } - var runnablesExplictlySet = false; - - var runnablesExplictlySetGetter = function(){ - return runnablesExplictlySet; - }; - var specFactory = function(description, fn, suite, timeout) { totalSpecsDefined++; var spec = new j$.Spec({ id: getNextSpecId(), - beforeAndAfterFns: beforeAndAfterFns(suite, runnablesExplictlySetGetter), + beforeAndAfterFns: beforeAndAfterFns(suite), expectationFactory: expectationFactory, resultCallback: specResultCallback, getSpecName: function(spec) { diff --git a/src/core/Spec.js b/src/core/Spec.js index 093c582b..88dcd84e 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -40,13 +40,13 @@ getJasmineRequireObj().Spec = function(j$) { return this.expectationFactory(actual, this); }; - Spec.prototype.execute = function(onComplete) { + Spec.prototype.execute = function(onComplete, enabled) { var self = this; this.onStart(this); - if (this.markedPending || this.disabled) { - complete(); + if (!this.isExecutable() || enabled === false) { + complete(enabled); return; } @@ -60,8 +60,8 @@ getJasmineRequireObj().Spec = function(j$) { userContext: this.userContext() }); - function complete() { - self.result.status = self.status(); + function complete(enabledAgain) { + self.result.status = self.status(enabledAgain); self.resultCallback(self.result); if (onComplete) { @@ -96,8 +96,13 @@ getJasmineRequireObj().Spec = function(j$) { } }; - Spec.prototype.status = function() { - if (this.disabled) { + Spec.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; + }; + + Spec.prototype.status = function(enabled) { + if (this.disabled || enabled === false) { return 'disabled'; } diff --git a/src/core/Suite.js b/src/core/Suite.js index 8a1ba98a..5f34c911 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -4,18 +4,13 @@ getJasmineRequireObj().Suite = function() { this.id = attrs.id; this.parentSuite = attrs.parentSuite; this.description = attrs.description; - this.onStart = attrs.onStart || function() {}; - this.resultCallback = attrs.resultCallback || function() {}; - this.clearStack = attrs.clearStack || function(fn) {fn();}; this.expectationFactory = attrs.expectationFactory; this.expectationResultFactory = attrs.expectationResultFactory; - this.runnablesExplictlySetGetter = attrs.runnablesExplictlySetGetter || function() {}; this.beforeFns = []; this.afterFns = []; this.beforeAllFns = []; this.afterAllFns = []; - this.queueRunner = attrs.queueRunner || function() {}; this.disabled = false; this.children = []; @@ -78,51 +73,17 @@ getJasmineRequireObj().Suite = function() { } }; - Suite.prototype.execute = function(onComplete) { - var self = this; - - this.onStart(this); - - if (this.disabled) { - complete(); - return; - } - - var allFns = []; - - for (var i = 0; i < this.children.length; i++) { - allFns.push(wrapChildAsAsync(this.children[i])); - } - - if (this.isExecutable()) { - allFns = this.beforeAllFns.concat(allFns); - allFns = allFns.concat(this.afterAllFns); - } - - this.queueRunner({ - queueableFns: allFns, - onComplete: complete, - userContext: this.sharedUserContext(), - onException: function() { self.onException.apply(self, arguments); } - }); - - function complete() { - self.result.status = self.status(); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } - - function wrapChildAsAsync(child) { - return { fn: function(done) { child.execute(done); } }; - } + Suite.prototype.isExecutable = function() { + return !this.disabled; }; - Suite.prototype.isExecutable = function() { - var runnablesExplicitlySet = this.runnablesExplictlySetGetter(); - return !runnablesExplicitlySet && hasExecutableChild(this.children); + Suite.prototype.canBeReentered = function() { + return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; + }; + + Suite.prototype.getResult = function() { + this.result.status = this.status(); + return this.result; }; Suite.prototype.sharedUserContext = function() { @@ -175,17 +136,6 @@ getJasmineRequireObj().Suite = function() { return !args[0]; } - function hasExecutableChild(children) { - var foundActive = false; - for (var i = 0; i < children.length; i++) { - if (children[i].isExecutable()) { - foundActive = true; - break; - } - } - return foundActive; - } - function clone(obj) { var clonedObj = {}; for (var prop in obj) { diff --git a/src/core/TreeProcessor.js b/src/core/TreeProcessor.js new file mode 100644 index 00000000..c3bf8f7b --- /dev/null +++ b/src/core/TreeProcessor.js @@ -0,0 +1,203 @@ +getJasmineRequireObj().TreeProcessor = function() { + function TreeProcessor(attrs) { + var tree = attrs.tree, + runnableIds = attrs.runnableIds, + queueRunnerFactory = attrs.queueRunnerFactory, + nodeStart = attrs.nodeStart || function() {}, + nodeComplete = attrs.nodeComplete || function() {}, + stats = { valid: true }, + processed = false, + defaultMin = Infinity, + defaultMax = 1 - Infinity; + + this.processTree = function() { + processNode(tree, false); + processed = true; + return stats; + }; + + this.execute = function(done) { + if (!processed) { + this.processTree(); + } + + if (!stats.valid) { + throw 'invalid order'; + } + + var childFns = wrapChildren(tree, 0); + + queueRunnerFactory({ + queueableFns: childFns, + onException: function() { + tree.onException.apply(tree, arguments); + }, + onComplete: done + }); + }; + + function runnableIndex(id) { + for (var i = 0; i < runnableIds.length; i++) { + if (runnableIds[i] === id) { + return i; + } + } + } + + function processNode(node, parentEnabled) { + var executableIndex = runnableIndex(node.id); + + if (executableIndex !== undefined) { + parentEnabled = true; + } + + parentEnabled = parentEnabled && node.isExecutable(); + + if (!node.children) { + stats[node.id] = { + executable: parentEnabled && node.isExecutable(), + segments: [{ + index: 0, + owner: node, + nodes: [node], + min: startingMin(executableIndex), + max: startingMax(executableIndex) + }] + }; + } else { + var hasExecutableChild = false; + + for (var i = 0; i < node.children.length; i++) { + var child = node.children[i]; + + processNode(child, parentEnabled); + + if (!stats.valid) { + return; + } + + var childStats = stats[child.id]; + + hasExecutableChild = hasExecutableChild || childStats.executable; + } + + stats[node.id] = { + executable: hasExecutableChild + }; + + segmentChildren(node, stats[node.id], executableIndex); + + if (!node.canBeReentered() && stats[node.id].segments.length > 1) { + stats = { valid: false }; + } + } + } + + function startingMin(executableIndex) { + return executableIndex === undefined ? defaultMin : executableIndex; + } + + function startingMax(executableIndex) { + return executableIndex === undefined ? defaultMax : executableIndex; + } + + function segmentChildren(node, nodeStats, executableIndex) { + var currentSegment = { index: 0, owner: node, nodes: [], min: startingMin(executableIndex), max: startingMax(executableIndex) }, + result = [currentSegment], + lastMax = defaultMax, + orderedChildSegments = orderChildSegments(node.children); + + function isSegmentBoundary(minIndex) { + return lastMax !== defaultMax && minIndex !== defaultMin && lastMax < minIndex - 1; + } + + for (var i = 0; i < orderedChildSegments.length; i++) { + var childSegment = orderedChildSegments[i], + maxIndex = childSegment.max, + minIndex = childSegment.min; + + if (isSegmentBoundary(minIndex)) { + currentSegment = {index: result.length, owner: node, nodes: [], min: defaultMin, max: defaultMin}; + result.push(currentSegment); + } + + currentSegment.nodes.push(childSegment); + currentSegment.min = Math.min(currentSegment.min, minIndex); + currentSegment.max = Math.max(currentSegment.max, maxIndex); + lastMax = maxIndex; + } + + nodeStats.segments = result; + } + + function orderChildSegments(array) { + var result = []; + + for (var i = 0; i < array.length; i++) { + var child = array[i], + segments = stats[child.id].segments; + + for (var j = 0; j < segments.length; j++) { + result.push(segments[j]); + } + } + + result.sort(function(a, b) { + if (a.min === null) { + return b.min === null ? 0 : 1; + } + + if (b.min === null) { + return -1; + } + + return a.min - b.min; + }); + + return result; + } + + function executeNode(node, segmentNumber) { + if (node.children) { + return { + fn: function(done) { + nodeStart(node); + + queueRunnerFactory({ + onComplete: function() { + nodeComplete(node, node.getResult()); + done(); + }, + queueableFns: wrapChildren(node, segmentNumber), + userContext: node.sharedUserContext(), + onException: function() { + node.onException.apply(node, arguments); + } + }); + } + }; + } else { + return { + fn: function(done) { node.execute(done, stats[node.id].executable); } + }; + } + } + + function wrapChildren(node, segmentNumber) { + var result = [], + segmentChildren = stats[node.id].segments[segmentNumber].nodes; + + for (var i = 0; i < segmentChildren.length; i++) { + result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); + } + + if (!stats[node.id].executable) { + return result; + } + + return node.beforeAllFns.concat(result).concat(node.afterAllFns); + } + } + + return TreeProcessor; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 384bc6eb..98613409 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -43,6 +43,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.StringMatching = jRequire.StringMatching(j$); j$.Suite = jRequire.Suite(); j$.Timer = jRequire.Timer(); + j$.TreeProcessor = jRequire.TreeProcessor(); j$.version = jRequire.version(); j$.matchers = jRequire.requireMatchers(jRequire, j$);