guardSync method
Verifies that there are no guarded methods currently pending (see guard).
If a guarded method is currently pending, and this is not a call nested from inside that method's body (directly or indirectly), then this method will throw a detailed exception.
Implementation
static void guardSync() {
if (_scopeStack.isEmpty) {
// No scopes open, so we must be fine.
return;
}
// Find the current TestAsyncUtils scope zone so we can see if it's the one we expect.
final Zone zone = _currentScopeZone;
if (zone == _scopeStack.last.zone) {
// We're still in the current scope zone. All good.
return;
}
// If we get here, we know we've got a conflict on our hands.
// We got an async barrier, but the current zone isn't the last scope that
// we pushed on the stack.
// Find which scope the conflict happened in, so that we know
// which stack trace to report the conflict as starting from.
//
// For example, if we called an async method A, which ran its body in a
// guarded block, and in its body it ran an async method B, which ran its
// body in a guarded block, but we didn't await B, then in A's block we ran
// an async method C, which ran its body in a guarded block, then we should
// complain about the call to B then the call to C. BUT. If we called an async
// method A, which ran its body in a guarded block, and in its body it ran
// an async method B, which ran its body in a guarded block, but we didn't
// await A, and then at the top level we called a method D, then we should
// complain about the call to A then the call to D.
//
// In both examples, the scope stack would have two scopes. In the first
// example, the current zone would be the zone of the _scopeStack[0] scope,
// and we would want to show _scopeStack[1]'s creationStack. In the second
// example, the current zone would not be in the _scopeStack, and we would
// want to show _scopeStack[0]'s creationStack.
int skipCount = 0;
_AsyncScope candidateScope = _scopeStack.last;
_AsyncScope scope;
do {
skipCount += 1;
scope = candidateScope;
if (skipCount >= _scopeStack.length) {
if (zone == null)
break;
// Some people have reported reaching this point, but it's not clear
// why. For now, just silently return.
// TODO(ianh): If we ever get a test case that shows how we reach
// this point, reduce it and report the error if there is one.
return;
}
candidateScope = _scopeStack[_scopeStack.length - skipCount - 1];
assert(candidateScope != null);
assert(candidateScope.zone != null);
} while (candidateScope.zone != zone);
assert(scope != null);
final StringBuffer message = StringBuffer();
message.writeln('Guarded function conflict. You must use "await" with all Future-returning test APIs.');
final _StackEntry originalGuarder = _findResponsibleMethod(scope.creationStack, 'guard', message);
final _StackEntry collidingGuarder = _findResponsibleMethod(StackTrace.current, 'guardSync', message);
if (originalGuarder != null && collidingGuarder != null) {
String originalName;
if (originalGuarder.className == null) {
originalName = '(${originalGuarder.methodName}) ';
message.writeln(
'The guarded "${originalGuarder.methodName}" function '
'was called from ${originalGuarder.callerFile} '
'on line ${originalGuarder.callerLine}.'
);
} else {
originalName = '(${originalGuarder.className}.${originalGuarder.methodName}) ';
message.writeln(
'The guarded method "${originalGuarder.methodName}" '
'from class ${originalGuarder.className} '
'was called from ${originalGuarder.callerFile} '
'on line ${originalGuarder.callerLine}.'
);
}
final String again = (originalGuarder.callerFile == collidingGuarder.callerFile) &&
(originalGuarder.callerLine == collidingGuarder.callerLine) ?
'again ' : '';
String collidingName;
if ((originalGuarder.className == collidingGuarder.className) &&
(originalGuarder.methodName == collidingGuarder.methodName)) {
originalName = '';
collidingName = '';
message.writeln(
'Then, it '
'was called ${again}from ${collidingGuarder.callerFile} '
'on line ${collidingGuarder.callerLine}.'
);
} else if (collidingGuarder.className == null) {
collidingName = '(${collidingGuarder.methodName}) ';
message.writeln(
'Then, the "${collidingGuarder.methodName}" function '
'was called ${again}from ${collidingGuarder.callerFile} '
'on line ${collidingGuarder.callerLine}.'
);
} else {
collidingName = '(${collidingGuarder.className}.${collidingGuarder.methodName}) ';
message.writeln(
'Then, the "${collidingGuarder.methodName}" method '
'${originalGuarder.className == collidingGuarder.className ? "(also from class ${collidingGuarder.className})"
: "from class ${collidingGuarder.className}"} '
'was called ${again}from ${collidingGuarder.callerFile} '
'on line ${collidingGuarder.callerLine}.'
);
}
message.writeln(
'The first ${originalGuarder.className == null ? "function" : "method"} $originalName'
'had not yet finished executing at the time that '
'the second ${collidingGuarder.className == null ? "function" : "method"} $collidingName'
'was called. Since both are guarded, and the second was not a nested call inside the first, the '
'first must complete its execution before the second can be called. Typically, this is achieved by '
'putting an "await" statement in front of the call to the first.'
);
if (collidingGuarder.className == null && collidingGuarder.methodName == 'expect') {
message.writeln(
'If you are confident that all test APIs are being called using "await", and '
'this expect() call is not being called at the top level but is itself being '
'called from some sort of callback registered before the ${originalGuarder.methodName} '
'method was called, then consider using expectSync() instead.'
);
}
message.writeln(
'\n'
'When the first ${originalGuarder.className == null ? "function" : "method"} '
'$originalName'
'was called, this was the stack:'
);
message.writeln(FlutterError.defaultStackFilter(scope.creationStack.toString().trimRight().split('\n')).join('\n'));
}
throw FlutterError(message.toString().trimRight());
}