// 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:mockito/mockito.dart'; import '../../src/common.dart'; void main() { group('Signals', () { Signals signals; MockIoProcessSignal mockSignal; ProcessSignal signalUnderTest; StreamController<io.ProcessSignal> controller; setUp(() { signals = Signals.test(); mockSignal = MockIoProcessSignal(); signalUnderTest = ProcessSignal(mockSignal); controller = StreamController<io.ProcessSignal>(); when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream); }); testWithoutContext('signal handler runs', () async { final Completer<void> completer = Completer<void>(); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); completer.complete(); }); controller.add(mockSignal); 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(); }); controller.add(mockSignal); await completer.future; }); testWithoutContext('signal handler error goes on error stream', () async { final Exception exn = Exception('Error'); signals.addHandler(signalUnderTest, (ProcessSignal s) { 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(); }, ); controller.add(mockSignal); 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) { 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); }, ); controller.add(mockSignal); 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) { 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); }, ); controller.add(mockSignal); await completer.future; await errSub.cancel(); expect(errList, isEmpty); }); testWithoutContext('only handlers for the correct signal run', () async { final MockIoProcessSignal mockSignal2 = MockIoProcessSignal(); final StreamController<io.ProcessSignal> controller2 = StreamController<io.ProcessSignal>(); final ProcessSignal otherSignal = ProcessSignal(mockSignal2); when(mockSignal2.watch()).thenAnswer((Invocation invocation) => controller2.stream); final Completer<void> completer = Completer<void>(); signals.addHandler(signalUnderTest, (ProcessSignal s) { expect(s, signalUnderTest); completer.complete(); }); signals.addHandler(otherSignal, (ProcessSignal s) { fail('Wrong signal!.'); }); final List<Object> errList = <Object>[]; final StreamSubscription<Object> errSub = signals.errors.listen( (Object err) { errList.add(err); }, ); controller.add(mockSignal); 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; }); controller.add(mockSignal); await completer.future; }); }); } class MockIoProcessSignal extends Mock implements io.ProcessSignal {}