Deprecate multiple calls to done callbacks

This commit is contained in:
Steve Gravrock
2021-09-08 20:44:27 -07:00
parent 7944250290
commit be23836c9d
12 changed files with 553 additions and 74 deletions

View File

@@ -891,7 +891,8 @@ getJasmineRequireObj().Env = function(j$) {
*/
'specDone'
],
queueRunnerFactory
queueRunnerFactory,
self.deprecated
);
/**
@@ -1306,6 +1307,7 @@ getJasmineRequireObj().Env = function(j$) {
beforeAndAfterFns: beforeAndAfterFns(suite),
expectationFactory: expectationFactory,
asyncExpectationFactory: specAsyncExpectationFactory,
deprecated: self.deprecated,
resultCallback: specResultCallback,
getSpecName: function(spec) {
return getSpecName(spec, suite);

View File

@@ -5,10 +5,14 @@ getJasmineRequireObj().QueueRunner = function(j$) {
StopExecutionError.prototype = new Error();
j$.StopExecutionError = StopExecutionError;
function once(fn) {
function once(fn, onTwice) {
var called = false;
return function(arg) {
if (!called) {
if (called) {
if (onTwice) {
onTwice();
}
} else {
called = true;
// Direct call using single parameter, because cleanup/next does not need more
fn(arg);
@@ -17,6 +21,16 @@ getJasmineRequireObj().QueueRunner = function(j$) {
};
}
function fallbackOnMultipleDone() {
console.error(
new Error(
"An asynchronous function called its 'done' " +
'callback more than once, in a QueueRunner without a onMultipleDone ' +
'handler.'
)
);
}
function emptyFn() {}
function QueueRunner(attrs) {
@@ -31,6 +45,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
fn();
};
this.onException = attrs.onException || emptyFn;
this.onMultipleDone = attrs.onMultipleDone || fallbackOnMultipleDone;
this.userContext = attrs.userContext || new j$.UserContext();
this.timeout = attrs.timeout || {
setTimeout: setTimeout,
@@ -88,6 +103,8 @@ getJasmineRequireObj().QueueRunner = function(j$) {
var self = this,
completedSynchronously = true,
handleError = function handleError(error) {
// TODO probably shouldn't next() right away here.
// That makes debugging async failures much more confusing.
onException(error);
next(error);
},
@@ -97,37 +114,52 @@ getJasmineRequireObj().QueueRunner = function(j$) {
}
self.globalErrors.popListener(handleError);
}),
next = once(function next(err) {
cleanup();
next = once(
function next(err) {
cleanup();
if (j$.isError_(err)) {
if (!(err instanceof StopExecutionError) && !err.jasmineMessage) {
self.fail(err);
if (j$.isError_(err)) {
if (!(err instanceof StopExecutionError) && !err.jasmineMessage) {
self.fail(err);
}
self.errored = errored = true;
} else if (typeof err !== 'undefined' && !self.errored) {
self.deprecated(
'Any argument passed to a done callback will be treated as an ' +
'error in a future release. Call the done callback without ' +
"arguments if you don't want to trigger a spec failure."
);
}
self.errored = errored = true;
} else if (typeof err !== 'undefined' && !self.errored) {
self.deprecated(
'Any argument passed to a done callback will be treated as an ' +
'error in a future release. Call the done callback without ' +
"arguments if you don't want to trigger a spec failure."
);
}
function runNext() {
if (self.completeOnFirstError && errored) {
self.skipToCleanup(iterativeIndex);
function runNext() {
if (self.completeOnFirstError && errored) {
self.skipToCleanup(iterativeIndex);
} else {
self.run(iterativeIndex + 1);
}
}
if (completedSynchronously) {
self.setTimeout(runNext);
} else {
self.run(iterativeIndex + 1);
runNext();
}
},
function() {
try {
if (!timedOut) {
self.onMultipleDone();
}
} catch (error) {
// Any error we catch here is probably due to a bug in Jasmine,
// and it's not likely to end up anywhere useful if we let it
// propagate. Log it so it can at least show up when debugging.
console.error(error);
}
}
if (completedSynchronously) {
self.setTimeout(runNext);
} else {
runNext();
}
}),
),
errored = false,
timedOut = false,
queueableFn = self.queueableFns[iterativeIndex],
timeoutId,
maybeThenable;
@@ -143,6 +175,7 @@ getJasmineRequireObj().QueueRunner = function(j$) {
if (queueableFn.timeout !== undefined) {
var timeoutInterval = queueableFn.timeout || j$.DEFAULT_TIMEOUT_INTERVAL;
timeoutId = self.setTimeout(function() {
timedOut = true;
var error = new Error(
'Timeout - Async function did not complete within ' +
timeoutInterval +
@@ -151,6 +184,9 @@ getJasmineRequireObj().QueueRunner = function(j$) {
? '(custom timeout)'
: '(set by jasmine.DEFAULT_TIMEOUT_INTERVAL)')
);
// TODO Need to decide what to do about a successful completion after a
// timeout. That should probably not be a deprecation, and maybe not
// an error in 4.0. (But a diagnostic of some sort might be helpful.)
onException(error);
next();
}, timeoutInterval);

View File

@@ -1,5 +1,5 @@
getJasmineRequireObj().ReportDispatcher = function(j$) {
function ReportDispatcher(methods, queueRunnerFactory) {
function ReportDispatcher(methods, queueRunnerFactory, deprecated) {
var dispatchedMethods = methods || [];
for (var i = 0; i < dispatchedMethods.length; i++) {
@@ -43,7 +43,15 @@ getJasmineRequireObj().ReportDispatcher = function(j$) {
queueRunnerFactory({
queueableFns: fns,
onComplete: onComplete,
isReporter: true
isReporter: true,
onMultipleDone: function() {
deprecated(
"An asynchronous reporter callback called its 'done' callback " +
'more than once. This is a bug in the reporter callback in ' +
'question. This will be treated as an error in a future version.',
{ ignoreRunnable: true }
);
}
});
}

View File

@@ -40,6 +40,7 @@ getJasmineRequireObj().Spec = function(j$) {
};
this.expectationResultFactory =
attrs.expectationResultFactory || function() {};
this.deprecated = attrs.deprecated || function() {};
this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
this.catchingExceptions =
attrs.catchingExceptions ||
@@ -134,6 +135,21 @@ getJasmineRequireObj().Spec = function(j$) {
onException: function() {
self.onException.apply(self, arguments);
},
onMultipleDone: function() {
// Issue a deprecation. Include the context ourselves and pass
// ignoreRunnable: true, since getting here always means that we've already
// moved on and the current runnable isn't the one that caused the problem.
self.deprecated(
"An asynchronous function called its 'done' " +
'callback more than once. This is a bug in the spec, beforeAll, ' +
'beforeEach, afterAll, or afterEach function in question. This will ' +
'be treated as an error in a future version.\n' +
'(in spec: ' +
self.getFullName() +
')',
{ ignoreRunnable: true }
);
},
onComplete: function() {
if (self.result.status === 'failed') {
onComplete(new j$.StopExecutionError('spec failed'));
@@ -141,7 +157,8 @@ getJasmineRequireObj().Spec = function(j$) {
onComplete();
}
},
userContext: this.userContext()
userContext: this.userContext(),
runnableName: this.getFullName.bind(this)
};
if (this.markedPending || excluded === true) {

View File

@@ -201,6 +201,32 @@ getJasmineRequireObj().Suite = function(j$) {
this.result.failedExpectations.push(failedExpectation);
};
Suite.prototype.onMultipleDone = function() {
var msg;
// Issue a deprecation. Include the context ourselves and pass
// ignoreRunnable: true, since getting here always means that we've already
// moved on and the current runnable isn't the one that caused the problem.
if (this.parentSuite) {
msg =
"An asynchronous function called its 'done' callback more than " +
'once. This is a bug in the spec, beforeAll, beforeEach, afterAll, ' +
'or afterEach function in question. This will be treated as an error ' +
'in a future version.\n' +
'(in suite: ' +
this.getFullName() +
')';
} else {
msg =
'A top-level beforeAll or afterAll function called its ' +
"'done' callback more than once. This is a bug in the beforeAll " +
'or afterAll function in question. This will be treated as an ' +
'error in a future version.';
}
this.env.deprecated(msg, { ignoreRunnable: true });
};
Suite.prototype.addExpectationResult = function() {
if (isFailure(arguments)) {
var data = arguments[1];

View File

@@ -44,7 +44,10 @@ getJasmineRequireObj().TreeProcessor = function() {
onException: function() {
tree.onException.apply(tree, arguments);
},
onComplete: done
onComplete: done,
onMultipleDone: tree.onMultipleDone
? tree.onMultipleDone.bind(tree)
: null
});
};
@@ -216,7 +219,10 @@ getJasmineRequireObj().TreeProcessor = function() {
userContext: node.sharedUserContext(),
onException: function() {
node.onException.apply(node, arguments);
}
},
onMultipleDone: node.onMultipleDone
? node.onMultipleDone.bind(node)
: null
});
}
};