Skip to content

Commit 9e829cb

Browse files
authored
Make realAsyncZone run microtasks and timers in the correct zone. (#162731)
Current implementation runs timers and microtask callbacks in the root zone. That assumes that the top-level `scheduleMicrotask` or `Timer` constructors have been used, which have so far wrapped the callback with `runCallbackGuarded` before calling the zone implementation. That means that doing `zone.scheduleMicrotask` directly would not ensure that the microtask was run in the correct zone. If a `run` handler throws, it wouldn't be caught. This change makes the `realAsyncZone` do whatever the root zone would do if its `ZoneDelegate` got called with the intended zone and arguments. That should be consistent with the current behavior, and be compatible with incoming bug-fixes to the platform `Zone` behavior. Prepares Flutter for landing https://dart-review.googlesource.com/c/sdk/+/406961 which is currently blocked (so this indirectly fixes dart-lang/sdk#59913). There are no new tests, the goal is that all existing tests keep running, and that they keep doing so when the Dart CL lands. Currently that CL only breaks one test, the `dev/automated_tests/test_smoke_test/fail_test_on_exception_after_test.dart` test which threw the error-after-test in the root zone instead of the test zone. This change fixes that.
1 parent 8297e44 commit 9e829cb

File tree

2 files changed

+99
-3
lines changed

2 files changed

+99
-3
lines changed

packages/flutter_test/lib/src/binding.dart

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
13611361
final Zone realAsyncZone = Zone.current.fork(
13621362
specification: ZoneSpecification(
13631363
scheduleMicrotask: (Zone self, ZoneDelegate parent, Zone zone, void Function() f) {
1364-
Zone.root.scheduleMicrotask(f);
1364+
_rootDelegate.scheduleMicrotask(zone, f);
13651365
},
13661366
createTimer: (
13671367
Zone self,
@@ -1370,7 +1370,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
13701370
Duration duration,
13711371
void Function() f,
13721372
) {
1373-
return Zone.root.createTimer(duration, f);
1373+
return _rootDelegate.createTimer(zone, duration, f);
13741374
},
13751375
createPeriodicTimer: (
13761376
Zone self,
@@ -1379,7 +1379,7 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
13791379
Duration period,
13801380
void Function(Timer timer) f,
13811381
) {
1382-
return Zone.root.createPeriodicTimer(period, f);
1382+
return _rootDelegate.createPeriodicTimer(zone, period, f);
13831383
},
13841384
),
13851385
);
@@ -1442,6 +1442,26 @@ class AutomatedTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
14421442
_currentFakeAsync!.flushMicrotasks();
14431443
}
14441444

1445+
/// The [ZoneDelegate] for [Zone.root].
1446+
///
1447+
/// Used to schedule (real) microtasks and timers in the root zone,
1448+
/// to be run in the correct zone.
1449+
static final ZoneDelegate _rootDelegate = _captureRootZoneDelegate();
1450+
1451+
/// Hack to extract the [ZoneDelegate] for [Zone.root].
1452+
static ZoneDelegate _captureRootZoneDelegate() {
1453+
final Zone captureZone = Zone.root.fork(
1454+
specification: ZoneSpecification(
1455+
run: <R>(Zone self, ZoneDelegate parent, Zone zone, R Function() f) {
1456+
return parent as R;
1457+
},
1458+
),
1459+
);
1460+
// The `_captureRootZoneDelegate` argument just happens to be a constant
1461+
// function with the necessary type. It's not called recursively.
1462+
return captureZone.run<ZoneDelegate>(_captureRootZoneDelegate);
1463+
}
1464+
14451465
@override
14461466
void scheduleAttachRootWidget(Widget rootWidget) {
14471467
// We override the default version of this so that the application-startup widget tree

packages/flutter_test/test/widget_tester_test.dart

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,82 @@ void main() {
406406
expect(result, isNull);
407407
expect(tester.takeException(), isNotNull);
408408
});
409+
410+
testWidgets('runs in original zone', (WidgetTester tester) async {
411+
final Zone testZone = Zone.current;
412+
Zone? runAsyncZone;
413+
Zone? timerZone;
414+
Zone? periodicTimerZone;
415+
Zone? microtaskZone;
416+
417+
Zone? innerZone;
418+
Zone? innerTimerZone;
419+
Zone? innerPeriodicTimerZone;
420+
Zone? innerMicrotaskZone;
421+
422+
await tester.binding.runAsync<void>(() async {
423+
final Zone currentZone = Zone.current;
424+
runAsyncZone = currentZone;
425+
426+
// Complete a future when all callbacks have completed.
427+
int pendingCallbacks = 6;
428+
final Completer<void> callbacksDone = Completer<void>();
429+
void onCallback() {
430+
if (--pendingCallbacks == 0) {
431+
testZone.run(() {
432+
callbacksDone.complete(null);
433+
});
434+
}
435+
}
436+
437+
// On the runAsync zone itself.
438+
currentZone.createTimer(Duration.zero, () {
439+
timerZone = Zone.current;
440+
onCallback();
441+
});
442+
currentZone.createPeriodicTimer(Duration.zero, (Timer timer) {
443+
timer.cancel();
444+
periodicTimerZone = Zone.current;
445+
onCallback();
446+
});
447+
currentZone.scheduleMicrotask(() {
448+
microtaskZone = Zone.current;
449+
onCallback();
450+
});
451+
452+
// On a nested user-created zone.
453+
final Zone inner = runZoned(() => Zone.current);
454+
innerZone = inner;
455+
inner.createTimer(Duration.zero, () {
456+
innerTimerZone = Zone.current;
457+
onCallback();
458+
});
459+
inner.createPeriodicTimer(Duration.zero, (Timer timer) {
460+
timer.cancel();
461+
innerPeriodicTimerZone = Zone.current;
462+
onCallback();
463+
});
464+
inner.scheduleMicrotask(() {
465+
innerMicrotaskZone = Zone.current;
466+
onCallback();
467+
});
468+
469+
await callbacksDone.future;
470+
});
471+
expect(runAsyncZone, isNotNull);
472+
expect(timerZone, same(runAsyncZone));
473+
expect(periodicTimerZone, same(runAsyncZone));
474+
expect(microtaskZone, same(runAsyncZone));
475+
476+
expect(innerZone, isNotNull);
477+
expect(innerTimerZone, same(innerZone));
478+
expect(innerPeriodicTimerZone, same(innerZone));
479+
expect(innerMicrotaskZone, same(innerZone));
480+
481+
expect(runAsyncZone, isNot(same(testZone)));
482+
expect(runAsyncZone, isNot(same(innerZone)));
483+
expect(innerZone, isNot(same(testZone)));
484+
});
409485
});
410486

411487
group('showKeyboard', () {

0 commit comments

Comments
 (0)