Unverified Commit b2f8d3a6 authored by James D. Lin's avatar James D. Lin Committed by GitHub

Instrument pending timers in tests (#37646)

Flutter widget tests assert if a test completes with timers still
pending.  However, it can be hard to diagnose where a pending timer
came from.  For example, a widget might consume a third-party library
that internally uses a timer.

I added a FakeAsync.pendingTimersDebugInfo getter to quiver
(https://github.com/google/quiver-dart/pull/500).  Make flutter_test
use it.

Additionally modify Flutter's debugPrintStack to take an optional
StackTrace argument instead of always printing StackTrace.current.

Fixes #4237.
parent 9da68fcd
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'package:flutter_test/flutter_test.dart';
void main() {
failingPendingTimerTest();
}
void failingPendingTimerTest() {
testWidgets('flutter_test pending timer - negative', (WidgetTester tester) async {
Timer(const Duration(minutes: 10), () {});
});
}
......@@ -111,6 +111,16 @@ Future<void> _runSmokeTests() async {
printOutput: false,
timeout: _kShortTimeout,
);
await _runFlutterTest(automatedTests,
script: path.join('test_smoke_test', 'pending_timer_fail_test.dart'),
expectFailure: true,
printOutput: false,
outputChecker: (CapturedOutput output) =>
output.stdout.contains('failingPendingTimerTest')
? null
: 'Failed to find the stack trace for the pending Timer.',
timeout: _kShortTimeout,
);
// We run the remaining smoketests in parallel, because they each take some
// time to run (e.g. compiling), so we don't want to run them in series,
// especially on 20-core machines...
......
......@@ -737,20 +737,25 @@ class FlutterError extends Error with DiagnosticableTreeMixin implements Asserti
}
}
/// Dump the current stack to the console using [debugPrint] and
/// Dump the stack to the console using [debugPrint] and
/// [FlutterError.defaultStackFilter].
///
/// The current stack is obtained using [StackTrace.current].
/// If the `stackTrace` parameter is null, the [StackTrace.current] is used to
/// obtain the stack.
///
/// The `maxFrames` argument can be given to limit the stack to the given number
/// of lines. By default, all non-filtered stack lines are shown.
/// of lines before filtering is applied. By default, all stack lines are
/// included.
///
/// The `label` argument, if present, will be printed before the stack.
void debugPrintStack({ String label, int maxFrames }) {
void debugPrintStack({StackTrace stackTrace, String label, int maxFrames}) {
if (label != null)
debugPrint(label);
Iterable<String> lines = StackTrace.current.toString().trimRight().split('\n');
if (kIsWeb) {
stackTrace ??= StackTrace.current;
Iterable<String> lines = stackTrace.toString().trimRight().split('\n');
if ( kIsWeb
&& lines.isNotEmpty
&& lines.first.contains('StackTrace.current')) {
// Remove extra call to StackTrace.current for web platform.
// TODO(ferhat): remove when https://github.com/flutter/flutter/issues/37635
// is addressed.
......
......@@ -1048,14 +1048,29 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override
void _verifyInvariants() {
super._verifyInvariants();
assert(
_currentFakeAsync.periodicTimerCount == 0,
'A periodic Timer is still running even after the widget tree was disposed.'
);
assert(
_currentFakeAsync.nonPeriodicTimerCount == 0,
'A Timer is still pending even after the widget tree was disposed.'
);
assert(() {
if ( _currentFakeAsync.periodicTimerCount == 0
&& _currentFakeAsync.nonPeriodicTimerCount == 0) {
return true;
}
debugPrint('Pending timers:');
for (String timerInfo in _currentFakeAsync.pendingTimersDebugInfo) {
final int firstLineEnd = timerInfo.indexOf('\n');
assert(firstLineEnd != -1);
// No need to include the newline.
final String firstLine = timerInfo.substring(0, firstLineEnd);
final String stackTrace = timerInfo.substring(firstLineEnd + 1);
debugPrint(firstLine);
debugPrintStack(stackTrace: StackTrace.fromString(stackTrace));
debugPrint('');
}
return false;
}(), 'A Timer is still pending even after the widget tree was disposed.');
assert(_currentFakeAsync.microtaskCount == 0); // Shouldn't be possible.
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment