Allow reporter callbacks to be asynchronous

[finishes #154673961]
- Fixes #842

Signed-off-by: Elenore Bastian <ebastian@pivotal.io>
This commit is contained in:
Gregg Van Hove
2018-01-26 18:00:46 -08:00
parent 341c6df6ea
commit 50cd6fdd68
10 changed files with 434 additions and 272 deletions

View File

@@ -5,64 +5,42 @@ describe('Exceptions:', function() {
env = new jasmineUnderTest.Env();
});
describe('with break on exception', function() {
it('should not catch the exception', function() {
env.catchExceptions(false);
env.describe('suite for break on exceptions', function() {
env.it('should break when an exception is thrown', function() {
throw new Error('I should hit a breakpoint!');
});
it('should handle exceptions thrown, but continue', function(done) {
var secondTest = jasmine.createSpy('second test');
env.describe('Suite for handles exceptions', function () {
env.it('should be a test that fails because it throws an exception', function() {
throw new Error();
});
var spy = jasmine.createSpy('spy');
try {
env.execute();
spy();
}
catch (e) {}
expect(spy).not.toHaveBeenCalled();
env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest);
});
var expectations = function() {
expect(secondTest).toHaveBeenCalled();
done();
};
env.addReporter({ jasmineDone: expectations });
env.execute();
});
describe("with catch on exception", function() {
it('should handle exceptions thrown, but continue', function(done) {
var secondTest = jasmine.createSpy('second test');
env.describe('Suite for handles exceptions', function () {
env.it('should be a test that fails because it throws an exception', function() {
throw new Error();
});
env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest);
it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) {
var secondDescribe = jasmine.createSpy("second describe");
env.describe("a suite that throws an exception", function () {
env.it("is a test that should pass", function () {
this.expect(true).toEqual(true);
});
var expectations = function() {
expect(secondTest).toHaveBeenCalled();
done();
};
env.addReporter({ jasmineDone: expectations });
env.execute();
throw new Error("top level error");
});
env.describe("a suite that doesn't throw an exception", secondDescribe);
it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) {
var secondDescribe = jasmine.createSpy("second describe");
env.describe("a suite that throws an exception", function () {
env.it("is a test that should pass", function () {
this.expect(true).toEqual(true);
});
var expectations = function() {
expect(secondDescribe).toHaveBeenCalled();
done();
};
throw new Error("top level error");
});
env.describe("a suite that doesn't throw an exception", secondDescribe);
var expectations = function() {
expect(secondDescribe).toHaveBeenCalled();
done();
};
env.addReporter({ jasmineDone: expectations });
env.execute();
});
env.addReporter({ jasmineDone: expectations });
env.execute();
});
});

View File

@@ -9,70 +9,125 @@ describe("ReportDispatcher", function() {
});
it("dispatches requested methods to added reporters", function() {
var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']),
var queueRunnerFactory = jasmine.createSpy('queueRunner'),
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
completeCallback = jasmine.createSpy('complete');
dispatcher.addReporter(reporter);
dispatcher.addReporter(anotherReporter);
dispatcher.foo(123, 456);
dispatcher.foo(123, 456, completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}]
}));
var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
expect(reporter.foo.calls.mostRecent().object).toBe(reporter);
fns[1].fn();
expect(anotherReporter.foo).toHaveBeenCalledWith(123, 456);
expect(anotherReporter.foo.calls.mostRecent().object).toBe(anotherReporter);
dispatcher.bar('a', 'b');
queueRunnerFactory.calls.reset();
dispatcher.bar('a', 'b', completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}]
}));
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter.bar).toHaveBeenCalledWith('a', 'b');
fns[1].fn();
expect(anotherReporter.bar).toHaveBeenCalledWith('a', 'b');
});
it("does not dispatch to a reporter if the reporter doesn't accept the method", function() {
var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo']),
var queueRunnerFactory = jasmine.createSpy('queueRunner'),
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo'], queueRunnerFactory),
reporter = jasmine.createSpyObj('reporter', ['baz']);
dispatcher.addReporter(reporter);
expect(function() {
dispatcher.foo(123, 456);
}).not.toThrow();
dispatcher.foo(123, 456);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: []
}));
});
it("allows providing a fallback reporter in case there's no other report", function() {
var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']);
it("allows providing a fallback reporter in case there's no other reporter", function() {
var queueRunnerFactory = jasmine.createSpy('queueRunner'),
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
completeCallback = jasmine.createSpy('complete');
dispatcher.provideFallbackReporter(reporter);
dispatcher.foo(123, 456);
dispatcher.foo(123, 456, completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}]
}));
var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
});
it("does not call fallback reporting methods when another report is provided", function() {
var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']),
it("does not call fallback reporting methods when another reporter is provided", function() {
var queueRunnerFactory = jasmine.createSpy('queueRunner'),
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory),
reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']),
fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']);
fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']),
completeCallback = jasmine.createSpy('complete');
dispatcher.provideFallbackReporter(fallbackReporter);
dispatcher.addReporter(reporter);
dispatcher.foo(123, 456);
dispatcher.foo(123, 456, completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}]
}));
var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter.foo).toHaveBeenCalledWith(123, 456);
expect(fallbackReporter.foo).not.toHaveBeenCalledWith(123, 456);
});
it("allows registered reporters to be cleared", function() {
var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']),
var queueRunnerFactory = jasmine.createSpy('queueRunner'),
dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory),
reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']),
reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']);
reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']),
completeCallback = jasmine.createSpy('complete');
dispatcher.addReporter(reporter1);
dispatcher.foo(123);
dispatcher.foo(123, completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}]
}));
var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter1.foo).toHaveBeenCalledWith(123);
dispatcher.clearReporters();
dispatcher.addReporter(reporter2);
dispatcher.bar(456);
dispatcher.bar(456, completeCallback);
expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({
queueableFns: [{fn: jasmine.any(Function)}]
}));
fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns;
fns[0].fn();
expect(reporter1.bar).not.toHaveBeenCalled();
expect(reporter2.bar).toHaveBeenCalledWith(456);
});

View File

@@ -55,6 +55,7 @@ describe("Spec", function() {
spec.execute();
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
// TODO: due to some issue with the Pretty Printer, this line fails, but the other two pass.
// This means toHaveBeenCalledWith on IE8 will always be broken.
@@ -82,6 +83,7 @@ describe("Spec", function() {
spec.execute();
fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
expect(startCallback).toHaveBeenCalled();
});
@@ -104,8 +106,8 @@ describe("Spec", function() {
spec.execute();
var options = fakeQueueRunner.calls.mostRecent().args[0];
expect(options.queueableFns).toEqual([before, queueableFn]);
expect(options.cleanupFns).toEqual([after]);
expect(options.queueableFns).toEqual([{fn: jasmine.any(Function)}, before, queueableFn]);
expect(options.cleanupFns).toEqual([after, {fn: jasmine.any(Function)}]);
});
it("tells the queue runner that it's a leaf node", function() {
@@ -141,8 +143,7 @@ describe("Spec", function() {
});
it("can be excluded at execution time by a parent", function() {
var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner')
.and.callFake(function(attrs) { attrs.onComplete(); }),
var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
startCallback = jasmine.createSpy('startCallback'),
specBody = jasmine.createSpy('specBody'),
resultCallback = jasmine.createSpy('resultCallback'),
@@ -153,20 +154,26 @@ describe("Spec", function() {
queueRunnerFactory: fakeQueueRunner
});
spec.execute(undefined, true);
spec.execute('cally-back', true);
expect(spec.result.status).toBe('excluded');
expect(fakeQueueRunner).toHaveBeenCalled();
expect(fakeQueueRunner).toHaveBeenCalledWith(jasmine.objectContaining({
onComplete: 'cally-back',
queueableFns: [{fn: jasmine.any(Function)}],
cleanupFns: [{fn: jasmine.any(Function)}]
}));
expect(specBody).not.toHaveBeenCalled();
var args = fakeQueueRunner.calls.mostRecent().args[0];
args.queueableFns[0].fn();
expect(startCallback).toHaveBeenCalled();
args.cleanupFns[0].fn();
expect(resultCallback).toHaveBeenCalled();
expect(spec.result.status).toBe('excluded');
});
it("can be marked pending, but still calls callbacks when executed", function() {
var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner')
.and.callFake(function(attrs) { attrs.onComplete(); }),
var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'),
startCallback = jasmine.createSpy('startCallback'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
@@ -188,7 +195,10 @@ describe("Spec", function() {
expect(fakeQueueRunner).toHaveBeenCalled();
var args = fakeQueueRunner.calls.mostRecent().args[0];
args.queueableFns[0].fn();
expect(startCallback).toHaveBeenCalled();
args.cleanupFns[0].fn('things');
expect(resultCallback).toHaveBeenCalledWith({
id: spec.id,
status: 'pending',
@@ -197,7 +207,7 @@ describe("Spec", function() {
failedExpectations: [],
passedExpectations: [],
pendingReason: ''
});
}, 'things');
});
it("should call the done callback on execution complete", function() {
@@ -233,11 +243,12 @@ describe("Spec", function() {
});
it("keeps track of passed and failed expectations", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
var fakeQueueRunner = jasmine.createSpy('queueRunner'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
queueableFn: { fn: jasmine.createSpy("spec body") },
expectationResultFactory: function (data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
queueRunnerFactory: fakeQueueRunner,
resultCallback: resultCallback
});
spec.addExpectationResult(true, 'expectation1');
@@ -245,19 +256,21 @@ describe("Spec", function() {
spec.execute();
fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn();
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['expectation1']);
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['expectation2']);
});
it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
var fakeQueueRunner = jasmine.createSpy('queueRunner'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
resultCallback: resultCallback,
throwOnExpectationFailure: true
});
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: fakeQueueRunner,
resultCallback: resultCallback,
throwOnExpectationFailure: true
});
spec.addExpectationResult(true, 'passed');
expect(function() {
@@ -266,6 +279,7 @@ describe("Spec", function() {
spec.execute();
fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn();
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['passed']);
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['failed']);
});
@@ -332,17 +346,20 @@ describe("Spec", function() {
});
it("should log a failure when handling an exception", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
var fakeQueueRunner = jasmine.createSpy('queueRunner'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
queueRunnerFactory: fakeQueueRunner,
resultCallback: resultCallback
});
spec.onException('foo');
spec.execute();
var args = fakeQueueRunner.calls.mostRecent().args[0];
args.cleanupFns[0].fn();
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([{
error: 'foo',
matcherName: '',
@@ -353,17 +370,20 @@ describe("Spec", function() {
});
it("should not log an additional failure when handling an ExpectationFailed error", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
var fakeQueueRunner = jasmine.createSpy('queueRunner'),
resultCallback = jasmine.createSpy('resultCallback'),
spec = new jasmineUnderTest.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
queueRunnerFactory: fakeQueueRunner,
resultCallback: resultCallback
});
spec.onException(new jasmineUnderTest.errors.ExpectationFailed());
spec.execute();
var args = fakeQueueRunner.calls.mostRecent().args[0];
args.cleanupFns[0].fn();
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]);
});
});

View File

@@ -284,19 +284,20 @@ describe("TreeProcessor", function() {
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(nodeDone);
expect(nodeStart).toHaveBeenCalledWith(node);
expect(queueRunner).toHaveBeenCalledWith({
onComplete: jasmine.any(Function),
queueableFns: [],
queueableFns: [{ fn: jasmine.any(Function) }],
userContext: { node: 'context' },
onException: jasmine.any(Function)
});
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo');
expect(nodeStart).toHaveBeenCalledWith(node, 'foo');
node.getResult.and.returnValue({ my: 'result' });
queueRunner.calls.mostRecent().args[0].onComplete();
expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' });
expect(nodeDone).toHaveBeenCalled();
expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }, nodeDone);
});
it("runs a node with children", function() {
@@ -318,12 +319,12 @@ describe("TreeProcessor", function() {
queueableFns[0].fn(nodeDone);
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns.length).toBe(2);
expect(queueableFns.length).toBe(3);
queueableFns[0].fn('foo');
queueableFns[1].fn('foo');
expect(leaf1.execute).toHaveBeenCalledWith('foo', false);
queueableFns[1].fn('bar');
queueableFns[2].fn('bar');
expect(leaf2.execute).toHaveBeenCalledWith('bar', false);
});
@@ -348,18 +349,19 @@ describe("TreeProcessor", function() {
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);
expect(queueableFns.length).toBe(2);
queueableFns[0].fn('foo');
queueableFns[0].fn('bar');
expect(nodeStart).toHaveBeenCalledWith(node, 'bar');
queueableFns[1].fn('foo');
expect(leaf1.execute).toHaveBeenCalledWith('foo', true);
node.getResult.and.returnValue({ im: 'disabled' });
queueRunner.calls.mostRecent().args[0].onComplete();
expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' });
expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }, nodeDone);
});
it("runs beforeAlls for a node with children", function() {
@@ -384,7 +386,7 @@ describe("TreeProcessor", function() {
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns).toEqual(['beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]);
expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]);
});
it("runs afterAlls for a node with children", function() {
@@ -409,7 +411,7 @@ describe("TreeProcessor", function() {
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']);
expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']);
});
it("does not run beforeAlls or afterAlls for a node with no children", function() {
@@ -433,7 +435,7 @@ describe("TreeProcessor", function() {
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns).toEqual([]);
expect(queueableFns).toEqual([{fn: jasmine.any(Function)}]);
});
it("does not run beforeAlls or afterAlls for a node with only pending children", function() {
@@ -460,7 +462,7 @@ describe("TreeProcessor", function() {
queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }]);
expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }]);
});
it("runs leaves in the order specified", function() {
@@ -529,7 +531,7 @@ describe("TreeProcessor", function() {
expect(specifiedLeaf.execute).not.toHaveBeenCalled();
var nodeQueueableFns = queueRunner.calls.mostRecent().args[0].queueableFns;
nodeQueueableFns[0].fn();
nodeQueueableFns[1].fn();
expect(childLeaf.execute).toHaveBeenCalled();
@@ -558,8 +560,8 @@ describe("TreeProcessor", function() {
expect(queueableFns.length).toBe(5);
queueableFns[0].fn();
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf1.execute).toHaveBeenCalled();
queueableFns[1].fn();
@@ -567,8 +569,8 @@ describe("TreeProcessor", function() {
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(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf2.execute).toHaveBeenCalled();
queueableFns[3].fn();
@@ -576,8 +578,8 @@ describe("TreeProcessor", function() {
queueableFns[4].fn();
expect(queueRunner.calls.count()).toBe(4);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf3.execute).toHaveBeenCalled();
});
@@ -603,12 +605,12 @@ describe("TreeProcessor", function() {
queueableFns[0].fn();
expect(queueRunner.calls.count()).toBe(2);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(queueRunner.calls.count()).toBe(3);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf1.execute).toHaveBeenCalled();
queueableFns[1].fn();
@@ -616,12 +618,12 @@ describe("TreeProcessor", function() {
queueableFns[2].fn();
expect(queueRunner.calls.count()).toBe(4);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(queueRunner.calls.count()).toBe(5);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf2.execute).toHaveBeenCalled();
queueableFns[3].fn();
@@ -629,12 +631,12 @@ describe("TreeProcessor", function() {
queueableFns[4].fn();
expect(queueRunner.calls.count()).toBe(6);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1);
expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(queueRunner.calls.count()).toBe(7);
queueRunner.calls.mostRecent().args[0].queueableFns[0].fn();
queueRunner.calls.mostRecent().args[0].queueableFns[1].fn();
expect(leaf3.execute).toHaveBeenCalled();
});
@@ -661,11 +663,11 @@ describe("TreeProcessor", function() {
queueableFns[1].fn();
var childFns = queueRunner.calls.mostRecent().args[0].queueableFns;
expect(childFns.length).toBe(2);
childFns[0].fn();
expect(childFns.length).toBe(3);
childFns[1].fn();
expect(leaf2.execute).toHaveBeenCalled();
childFns[1].fn();
childFns[2].fn();
expect(leaf3.execute).toHaveBeenCalled();
});

View File

@@ -412,7 +412,7 @@ describe("Env integration", function() {
suiteDone: jasmine.createSpy('suiteDone').and.callFake(function(result) {
expect(result.failedExpectations[0].globalErrorType).toBeFalsy();
})
}
};
env.addReporter(reporter);
@@ -480,9 +480,11 @@ describe("Env integration", function() {
env.it('fails', function(specDone) {
setTimeout(function() {
specDone();
setTimeout(function() {
setTimeout(function() {
global.onerror('fail');
});
});
});
});
});
@@ -1002,28 +1004,35 @@ describe("Env integration", function() {
});
describe("with a mock clock", function() {
var realSetTimeout;
beforeEach(function() {
this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL;
this.realSetTimeout = setTimeout;
realSetTimeout = setTimeout;
jasmine.clock().install();
});
afterEach(function() {
jasmine.clock().tick(1);
jasmine.clock().tick(1);
jasmine.clock().uninstall();
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeout;
});
it("should wait a specified interval before failing specs haven't called done yet", function(done) {
it("should wait a default interval before failing specs that haven't called done yet", function(done) {
var env = new jasmineUnderTest.Env(),
reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]);
reporter.specDone.and.callFake(function() {
expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({status: 'failed'}));
reporter.specDone.and.callFake(function(result) {
expect(result).toEqual(jasmine.objectContaining({status: 'failed'}));
realSetTimeout(function() {
jasmine.clock().tick(1);
}, 0);
});
reporter.jasmineDone.and.callFake(function() {
expect(reporter.jasmineDone.calls.count()).toEqual(1);
done();
expect(reporter.specDone.calls.count()).toEqual(1);
jasmine.clock().tick(1);
realSetTimeout(done);
});
env.addReporter(reporter);
@@ -1032,6 +1041,7 @@ describe("Env integration", function() {
env.it("async spec that doesn't call done", function(underTestCallback) {
env.expect(true).toBeTruthy();
jasmine.clock().tick(8416);
jasmine.clock().tick(1);
});
env.execute();
@@ -1042,10 +1052,17 @@ describe("Env integration", function() {
reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]),
clock = env.clock;
reporter.specDone.and.callFake(function() {
realSetTimeout(function() {
jasmine.clock().tick(1);
}, 0);
});
reporter.jasmineDone.and.callFake(function() {
expect(reporter.specDone.calls.count()).toEqual(1);
expect(reporter.specDone).toHaveBeenCalledTimes(1);
expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'passed'}));
done();
jasmine.clock().tick(1);
realSetTimeout(done);
});
env.addReporter(reporter);
@@ -1062,8 +1079,7 @@ describe("Env integration", function() {
env.it("spec that should not time out", function(innerDone) {
clock.tick(6);
expect(true).toEqual(true);
innerDone();
jasmine.clock().tick(1);
realSetTimeout(innerDone);
});
env.execute();
@@ -1072,9 +1088,20 @@ describe("Env integration", function() {
it('should wait a custom interval before reporting async functions that fail to call done', function(done) {
var env = new jasmineUnderTest.Env(),
reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']),
realSetTimeout = this.realSetTimeout,
timeoutFailure = (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./);
reporter.specDone.and.callFake(function(r) {
realSetTimeout(function() {
jasmine.clock().tick(1);
}, 0);
});
reporter.suiteDone.and.callFake(function(r) {
realSetTimeout(function() {
jasmine.clock().tick(1);
}, 0);
});
reporter.jasmineDone.and.callFake(function() {
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite beforeAll', [ timeoutFailure ]);
expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite afterAll', [ timeoutFailure ]);
@@ -1082,18 +1109,19 @@ describe("Env integration", function() {
expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite afterEach times out', [ timeoutFailure ]);
expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite it times out', [ timeoutFailure ]);
done();
jasmine.clock().tick(1);
realSetTimeout(done);
});
env.addReporter(reporter);
jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000;
env.describe('suite', function() {
env.afterAll(function() {
realSetTimeout(function() {
jasmine.clock().tick(10);
}, 100);
});
// env.afterAll(function() {
// realSetTimeout(function() {
// jasmine.clock().tick(10);
// }, 100);
// });
env.describe('beforeAll', function() {
env.beforeAll(function(innerDone) {
realSetTimeout(function() {
@@ -1101,7 +1129,12 @@ describe("Env integration", function() {
}, 0);
}, 5000);
env.it('times out', function() {});
env.it('times out', function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1);
innerDone();
}, 0);
});
});
env.describe('afterAll', function() {
@@ -1111,7 +1144,12 @@ describe("Env integration", function() {
}, 0);
}, 2000);
env.it('times out', function() {});
env.it('times out', function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1);
innerDone();
}, 0);
});
});
env.describe('beforeEach', function() {
@@ -1121,7 +1159,12 @@ describe("Env integration", function() {
}, 0);
}, 1000);
env.it('times out', function() {});
env.it('times out', function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1);
innerDone();
}, 0);
});
});
env.describe('afterEach', function() {
@@ -1131,7 +1174,12 @@ describe("Env integration", function() {
}, 0);
}, 4000);
env.it('times out', function() {});
env.it('times out', function(innerDone) {
realSetTimeout(function() {
jasmine.clock().tick(1);
innerDone();
}, 0);
});
});
env.it('it times out', function(innerDone) {
@@ -1148,6 +1196,12 @@ describe("Env integration", function() {
var env = new jasmineUnderTest.Env(),
specDone = jasmine.createSpy('specDone');
specDone.and.callFake(function() {
realSetTimeout(function() {
jasmine.clock().tick(1);
}, 0);
});
env.addReporter({
specDone: specDone,
jasmineDone: function() {
@@ -1175,7 +1229,9 @@ describe("Env integration", function() {
message: 'Failed: error message'
})]
}));
done();
jasmine.clock().tick(1);
realSetTimeout(done);
}
});
@@ -1187,6 +1243,7 @@ describe("Env integration", function() {
}, 1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
});
env.it('specifies a message', function(innerDone) {
@@ -1196,6 +1253,7 @@ describe("Env integration", function() {
}, 1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
});
env.it('fails via the done callback', function(innerDone) {
@@ -1204,6 +1262,7 @@ describe("Env integration", function() {
}, 1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
});
env.it('has a message from an Error', function(innerDone) {
@@ -1213,6 +1272,7 @@ describe("Env integration", function() {
}, 1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
jasmine.clock().tick(1);
});
});

View File

@@ -39,59 +39,6 @@ getJasmineRequireObj().Env = function(j$) {
return currentSpec || currentSuite();
};
/**
* This represents the available reporter callback for an object passed to {@link Env#addReporter}.
* @interface Reporter
*/
var reporter = new j$.ReportDispatcher([
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
*/
'specDone'
]);
var globalErrors = new j$.GlobalErrors();
globalErrors.install();
globalErrors.pushListener(function(message, filename, lineno) {
@@ -260,15 +207,18 @@ getJasmineRequireObj().Env = function(j$) {
handlingLoadErrors = false;
};
var queueRunnerFactory = function(options) {
var queueRunnerFactory = function(options, args) {
options.catchException = catchException;
options.clearStack = options.clearStack || clearStack;
options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout};
options.fail = self.fail;
options.globalErrors = globalErrors;
options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf;
options.onException = options.onException || function(e) {
(currentRunnable() || topSuite).onException(e);
};
new j$.QueueRunner(options).execute();
new j$.QueueRunner(options).execute(args);
};
var topSuite = new j$.Suite({
@@ -285,6 +235,59 @@ getJasmineRequireObj().Env = function(j$) {
return topSuite;
};
/**
* This represents the available reporter callback for an object passed to {@link Env#addReporter}.
* @interface Reporter
*/
var reporter = new j$.ReportDispatcher([
/**
* `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts.
* @function
* @name Reporter#jasmineStarted
* @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run
*/
'jasmineStarted',
/**
* When the entire suite has finished execution `jasmineDone` is called
* @function
* @name Reporter#jasmineDone
* @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running.
*/
'jasmineDone',
/**
* `suiteStarted` is invoked when a `describe` starts to run
* @function
* @name Reporter#suiteStarted
* @param {SuiteResult} result Information about the individual {@link describe} being run
*/
'suiteStarted',
/**
* `suiteDone` is invoked when all of the child specs and suites for a given suite have been run
*
* While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`.
* @function
* @name Reporter#suiteDone
* @param {SuiteResult} result
*/
'suiteDone',
/**
* `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions)
* @function
* @name Reporter#specStarted
* @param {SpecResult} result Information about the individual {@link it} being run
*/
'specStarted',
/**
* `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run.
*
* While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed.
* @function
* @name Reporter#specDone
* @param {SpecResult} result
*/
'specDone'
], queueRunnerFactory);
this.execute = function(runnablesToRun) {
var self = this;
this.suppressLoadErrors();
@@ -306,23 +309,24 @@ getJasmineRequireObj().Env = function(j$) {
tree: topSuite,
runnableIds: runnablesToRun,
queueRunnerFactory: queueRunnerFactory,
nodeStart: function(suite) {
nodeStart: function(suite, next) {
currentlyExecutingSuites.push(suite);
defaultResourcesForRunnable(suite.id, suite.parentSuite.id);
reporter.suiteStarted(suite.result);
reporter.suiteStarted(suite.result, next);
},
nodeComplete: function(suite, result) {
nodeComplete: function(suite, result, next) {
if (suite !== currentSuite()) {
throw new Error('Tried to complete the wrong suite');
}
clearResourcesForRunnable(suite.id);
currentlyExecutingSuites.pop();
reporter.suiteDone(result);
if (result.status === 'failed') {
hasFailures = true;
}
reporter.suiteDone(result, next);
},
orderChildren: function(node) {
return order.sort(node.children);
@@ -345,40 +349,40 @@ getJasmineRequireObj().Env = function(j$) {
reporter.jasmineStarted({
totalSpecsDefined: totalSpecsDefined,
order: order
});
}, function() {
currentlyExecutingSuites.push(topSuite);
currentlyExecutingSuites.push(topSuite);
processor.execute(function () {
clearResourcesForRunnable(topSuite.id);
currentlyExecutingSuites.pop();
var overallStatus, incompleteReason;
processor.execute(function() {
clearResourcesForRunnable(topSuite.id);
currentlyExecutingSuites.pop();
var overallStatus, incompleteReason;
if (hasFailures || topSuite.result.failedExpectations.length > 0) {
overallStatus = 'failed';
} else if (focusedRunnables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
if (hasFailures || topSuite.result.failedExpectations.length > 0) {
overallStatus = 'failed';
} else if (focusedRunnables.length > 0) {
overallStatus = 'incomplete';
incompleteReason = 'fit() or fdescribe() was found';
} else if (totalSpecsDefined === 0) {
overallStatus = 'incomplete';
incompleteReason = 'No specs found';
} else {
overallStatus = 'passed';
}
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'.
* @property {IncompleteReason} - Explanation of why the suite was incimplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
*/
reporter.jasmineDone({
overallStatus: overallStatus,
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations
/**
* Information passed to the {@link Reporter#jasmineDone} event.
* @typedef JasmineDoneInfo
* @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'.
* @property {IncompleteReason} - Explanation of why the suite was incimplete.
* @property {Order} order - Information about the ordering (random or not) of this execution of the suite.
* @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level.
*/
reporter.jasmineDone({
overallStatus: overallStatus,
incompleteReason: incompleteReason,
order: order,
failedExpectations: topSuite.result.failedExpectations
}, function() {});
});
});
};
@@ -581,20 +585,21 @@ getJasmineRequireObj().Env = function(j$) {
return spec;
function specResultCallback(result) {
function specResultCallback(result, next) {
clearResourcesForRunnable(spec.id);
currentSpec = null;
reporter.specDone(result);
if (result.status === 'failed') {
hasFailures = true;
}
reporter.specDone(result, next);
}
function specStarted(spec) {
function specStarted(spec, next) {
currentSpec = spec;
defaultResourcesForRunnable(spec.id, suite.id);
reporter.specStarted(spec.result);
reporter.specStarted(spec.result, next);
}
};

View File

@@ -24,6 +24,10 @@ getJasmineRequireObj().QueueRunner = function(j$) {
this.fail = attrs.fail || function() {};
this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} };
this.completeOnFirstError = !!attrs.completeOnFirstError;
if (typeof(this.onComplete) !== 'function') {
throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete));
}
}
QueueRunner.prototype.execute = function() {
@@ -53,15 +57,15 @@ getJasmineRequireObj().QueueRunner = function(j$) {
QueueRunner.prototype.attempt = function attempt(iterativeIndex) {
var self = this, completedSynchronously = true,
handleError = function(error) {
handleError = function handleError(error) {
onException(error);
next();
},
cleanup = once(function() {
cleanup = once(function cleanup() {
self.clearTimeout(timeoutId);
self.globalErrors.popListener(handleError);
}),
next = once(function () {
next = once(function next() {
cleanup();
function runNext() {
@@ -82,7 +86,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
queueableFn = self.queueableFns[iterativeIndex],
timeoutId;
next.fail = function() {
next.fail = function nextFail() {
self.fail.apply(null, arguments);
errored = true;
next();

View File

@@ -1,5 +1,5 @@
getJasmineRequireObj().ReportDispatcher = function(j$) {
function ReportDispatcher(methods) {
function ReportDispatcher(methods, queueRunnerFactory) {
var dispatchedMethods = methods || [];
@@ -33,11 +33,39 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
if (reporters.length === 0 && fallbackReporter !== null) {
reporters.push(fallbackReporter);
}
var onComplete = args[args.length - 1];
args = j$.util.argsToArray(args).splice(0, args.length - 1);
var fns = [];
for (var i = 0; i < reporters.length; i++) {
var reporter = reporters[i];
if (reporter[method]) {
reporter[method].apply(reporter, j$.util.cloneArgs(args));
}
addFn(fns, reporter, method, args);
}
queueRunnerFactory({
queueableFns: fns,
onComplete: onComplete
});
}
function addFn(fns, reporter, method, args) {
var fn = reporter[method];
if (!fn) {
return;
}
var thisArgs = j$.util.cloneArgs(args);
if (fn.length <= 1) {
fns.push({
fn: function () {
return fn.apply(reporter, thisArgs);
}
});
} else {
fns.push({
fn: function (done) {
return fn.apply(reporter, thisArgs.concat([done]));
}
});
}
}
}

View File

@@ -58,7 +58,19 @@ getJasmineRequireObj().Spec = function(j$) {
Spec.prototype.execute = function(onComplete, excluded) {
var self = this;
this.onStart(this);
var onStart = {
fn: function(done) {
self.onStart(self, done);
}
};
var complete = {
fn: function(done) {
self.queueableFn.fn = null;
self.result.status = self.status(excluded);
self.resultCallback(self.result, done);
}
};
var fns = this.beforeAndAfterFns();
var regularFns = fns.befores.concat(this.queueableFn);
@@ -67,8 +79,10 @@ getJasmineRequireObj().Spec = function(j$) {
isLeaf: true,
queueableFns: regularFns,
cleanupFns: fns.afters,
onException: function() { self.onException.apply(self, arguments); },
onComplete: complete,
onException: function () {
self.onException.apply(self, arguments);
},
onComplete: onComplete,
userContext: this.userContext()
};
@@ -77,17 +91,10 @@ getJasmineRequireObj().Spec = function(j$) {
runnerConfig.cleanupFns = [];
}
runnerConfig.queueableFns.unshift(onStart);
runnerConfig.cleanupFns.push(complete);
this.queueRunnerFactory(runnerConfig);
function complete() {
self.queueableFn.fn = null;
self.result.status = self.status(excluded);
self.resultCallback(self.result);
if (onComplete) {
onComplete();
}
}
};
Spec.prototype.onException = function onException(e) {

View File

@@ -166,17 +166,20 @@ getJasmineRequireObj().TreeProcessor = function() {
if (node.children) {
return {
fn: function(done) {
nodeStart(node);
var onStart = {
fn: function(next) {
nodeStart(node, next);
}
};
queueRunnerFactory({
onComplete: function() {
onComplete: function () {
node.cleanupBeforeAfter();
nodeComplete(node, node.getResult());
done();
nodeComplete(node, node.getResult(), done);
},
queueableFns: wrapChildren(node, segmentNumber),
queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)),
userContext: node.sharedUserContext(),
onException: function() {
onException: function () {
node.onException.apply(node, arguments);
}
});