// 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'; import '../../src/context.dart'; void main() { group('Signals', () { MockIoProcessSignal mockSignal; ProcessSignal signalUnderTest; StreamController<io.ProcessSignal> controller; setUp(() { mockSignal = MockIoProcessSignal(); signalUnderTest = ProcessSignal(mockSignal); controller = StreamController<io.ProcessSignal>(); when(mockSignal.watch()).thenAnswer((Invocation invocation) => controller.stream); }); testUsingContext('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; }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('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; }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('signal handler error goes on error stream', () async { signals.addHandler(signalUnderTest, (ProcessSignal s) { throw 'Error'; }); 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, <Object>['Error']); }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('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); }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('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); }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('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); }, overrides: <Type, Generator>{ Signals: () => Signals(), }); testUsingContext('all handlers for exiting signals are run before exit', () async { 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; }, overrides: <Type, Generator>{ Signals: () => Signals(exitSignals: <ProcessSignal>[signalUnderTest]), }); }); } class MockIoProcessSignal extends Mock implements io.ProcessSignal {}