// Copyright 2014 The Flutter 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 'dart:io' as io; import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/signals.dart'; import 'package:test/fake.dart'; import '../../src/common.dart'; void main() { group('Signals', () { late Signals signals; late FakeProcessSignal fakeSignal; late ProcessSignal signalUnderTest; setUp(() { signals = Signals.test(); fakeSignal = FakeProcessSignal(); signalUnderTest = ProcessSignal(fakeSignal); }); testWithoutContext('signal handler runs', () async { final Completer<void> completer = Completer<void>(); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); completer.complete(); }); fakeSignal.controller.add(fakeSignal); await completer.future; }); testWithoutContext('signal handlers run in order', () async { final Completer<void> completer = Completer<void>(); bool first = false; signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); first = true; }); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); expect(first, isTrue); completer.complete(); }); fakeSignal.controller.add(fakeSignal); await completer.future; }); testWithoutContext('signal handlers do not cause concurrent modification errors when removing handlers in a signal callback', () async { final Completer<void> completer = Completer<void>(); late Object token; Future<void> handle(ProcessSignal s) async { expect(s, signalUnderTest); expect(await signals.removeHandler(signalUnderTest, token), true); completer.complete(); } token = signals.addHandler(signalUnderTest, handle); fakeSignal.controller.add(fakeSignal); await completer.future; }); testWithoutContext('signal handler error goes on error stream', () async { final Exception exn = Exception('Error'); signals.addHandler(signalUnderTest, (ProcessSignal s) async { throw exn; }); final Completer<void> completer = Completer<void>(); final List<Object> errList = <Object>[]; final StreamSubscription<Object> errSub = signals.errors.listen( (Object err) { errList.add(err); completer.complete(); }, ); fakeSignal.controller.add(fakeSignal); await completer.future; await errSub.cancel(); expect(errList, contains(exn)); }); testWithoutContext('removed signal handler does not run', () async { final Object token = signals.addHandler( signalUnderTest, (ProcessSignal s) async { fail('Signal handler should have been removed.'); }, ); await signals.removeHandler(signalUnderTest, token); final List<Object> errList = <Object>[]; final StreamSubscription<Object> errSub = signals.errors.listen( (Object err) { errList.add(err); }, ); fakeSignal.controller.add(fakeSignal); await errSub.cancel(); expect(errList, isEmpty); }); testWithoutContext('non-removed signal handler still runs', () async { final Completer<void> completer = Completer<void>(); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); completer.complete(); }); final Object token = signals.addHandler( signalUnderTest, (ProcessSignal s) async { fail('Signal handler should have been removed.'); }, ); await signals.removeHandler(signalUnderTest, token); final List<Object> errList = <Object>[]; final StreamSubscription<Object> errSub = signals.errors.listen( (Object err) { errList.add(err); }, ); fakeSignal.controller.add(fakeSignal); await completer.future; await errSub.cancel(); expect(errList, isEmpty); }); testWithoutContext('only handlers for the correct signal run', () async { final FakeProcessSignal mockSignal2 = FakeProcessSignal(); final ProcessSignal otherSignal = ProcessSignal(mockSignal2); final Completer<void> completer = Completer<void>(); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); completer.complete(); }); signals.addHandler(otherSignal, (ProcessSignal s) async { fail('Wrong signal!.'); }); final List<Object> errList = <Object>[]; final StreamSubscription<Object> errSub = signals.errors.listen( (Object err) { errList.add(err); }, ); fakeSignal.controller.add(fakeSignal); await completer.future; await errSub.cancel(); expect(errList, isEmpty); }); testWithoutContext('all handlers for exiting signals are run before exit', () async { final Signals signals = Signals.test( exitSignals: <ProcessSignal>[signalUnderTest], ); final Completer<void> completer = Completer<void>(); bool first = false; bool second = false; setExitFunctionForTests((int exitCode) { // Both handlers have run before exit is called. expect(first, isTrue); expect(second, isTrue); expect(exitCode, 0); restoreExitFunction(); completer.complete(); }); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); expect(first, isFalse); expect(second, isFalse); first = true; }); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); expect(first, isTrue); expect(second, isFalse); second = true; }); fakeSignal.controller.add(fakeSignal); await completer.future; }); }); } class FakeProcessSignal extends Fake implements io.ProcessSignal { final StreamController<io.ProcessSignal> controller = StreamController<io.ProcessSignal>(); @override Stream<io.ProcessSignal> watch() => controller.stream; }