// Copyright 2017 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_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/base/platform.dart'; import 'package:flutter_tools/src/base/process.dart'; import 'package:flutter_tools/src/base/terminal.dart'; import 'package:mockito/mockito.dart'; import 'package:process/process.dart'; import '../../src/common.dart'; import '../../src/context.dart'; import '../../src/mocks.dart' show MockProcess, MockProcessManager, flakyProcessFactory; void main() { group('process exceptions', () { ProcessManager mockProcessManager; setUp(() { mockProcessManager = PlainMockProcessManager(); }); testUsingContext('runAsync throwOnError: exceptions should be ProcessException objects', () async { when(mockProcessManager.run(['false'])).thenAnswer( (Invocation invocation) => Future.value(ProcessResult(0, 1, '', ''))); expect(() async => await processUtils.run(['false'], throwOnError: true), throwsA(isInstanceOf())); }, overrides: {ProcessManager: () => mockProcessManager}); }); group('shutdownHooks', () { testUsingContext('runInExpectedOrder', () async { int i = 1; int serializeRecording1; int serializeRecording2; int postProcessRecording; int cleanup; addShutdownHook(() async { serializeRecording1 = i++; }, ShutdownStage.SERIALIZE_RECORDING); addShutdownHook(() async { cleanup = i++; }, ShutdownStage.CLEANUP); addShutdownHook(() async { postProcessRecording = i++; }, ShutdownStage.POST_PROCESS_RECORDING); addShutdownHook(() async { serializeRecording2 = i++; }, ShutdownStage.SERIALIZE_RECORDING); await runShutdownHooks(); expect(serializeRecording1, lessThanOrEqualTo(2)); expect(serializeRecording2, lessThanOrEqualTo(2)); expect(postProcessRecording, 3); expect(cleanup, 4); }); }); group('output formatting', () { MockProcessManager mockProcessManager; BufferLogger mockLogger; setUp(() { mockProcessManager = MockProcessManager(); mockLogger = BufferLogger(); }); MockProcess Function(List) processMetaFactory(List stdout, { List stderr = const [] }) { final Stream> stdoutStream = Stream>.fromIterable(stdout.map>((String s) => s.codeUnits)); final Stream> stderrStream = Stream>.fromIterable(stderr.map>((String s) => s.codeUnits)); return (List command) => MockProcess(stdout: stdoutStream, stderr: stderrStream); } testUsingContext('Command output is not wrapped.', () async { final List testString = ['0123456789' * 10]; mockProcessManager.processFactory = processMetaFactory(testString, stderr: testString); await processUtils.stream(['command']); expect(mockLogger.statusText, equals('${testString[0]}\n')); expect(mockLogger.errorText, equals('${testString[0]}\n')); }, overrides: { Logger: () => mockLogger, ProcessManager: () => mockProcessManager, OutputPreferences: () => OutputPreferences(wrapText: true, wrapColumn: 40), Platform: () => FakePlatform.fromPlatform(const LocalPlatform())..stdoutSupportsAnsi = false, }); }); group('run', () { const Duration delay = Duration(seconds: 2); MockProcessManager flakyProcessManager; ProcessManager mockProcessManager; setUp(() { // MockProcessManager has an implementation of start() that returns the // result of processFactory. flakyProcessManager = MockProcessManager(); mockProcessManager = MockProcessManager(); }); testUsingContext(' succeeds on success', () async { when(mockProcessManager.run(['whoohoo'])).thenAnswer((_) { return Future.value(ProcessResult(0, 0, '', '')); }); expect((await processUtils.run(['whoohoo'])).exitCode, 0); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' fails on failure', () async { when(mockProcessManager.run(['boohoo'])).thenAnswer((_) { return Future.value(ProcessResult(0, 1, '', '')); }); expect((await processUtils.run(['boohoo'])).exitCode, 1); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' throws on failure with throwOnError', () async { when(mockProcessManager.run(['kaboom'])).thenAnswer((_) { return Future.value(ProcessResult(0, 1, '', '')); }); expect(() => processUtils.run(['kaboom'], throwOnError: true), throwsA(isA())); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' does not throw on failure with whitelist', () async { when(mockProcessManager.run(['kaboom'])).thenAnswer((_) { return Future.value(ProcessResult(0, 1, '', '')); }); expect( (await processUtils.run( ['kaboom'], throwOnError: true, whiteListFailures: (int c) => c == 1, )).exitCode, 1); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' throws on failure when not in whitelist', () async { when(mockProcessManager.run(['kaboom'])).thenAnswer((_) { return Future.value(ProcessResult(0, 2, '', '')); }); expect( () => processUtils.run( ['kaboom'], throwOnError: true, whiteListFailures: (int c) => c == 1, ), throwsA(isA())); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' flaky process fails without retry', () async { flakyProcessManager.processFactory = flakyProcessFactory( flakes: 1, delay: delay, ); final RunResult result = await processUtils.run( ['dummy'], timeout: delay + const Duration(seconds: 1), ); expect(result.exitCode, -9); }, overrides: { ProcessManager: () => flakyProcessManager, }); testUsingContext(' flaky process succeeds with retry', () async { flakyProcessManager.processFactory = flakyProcessFactory( flakes: 1, delay: delay, ); final RunResult result = await processUtils.run( ['dummy'], timeout: delay - const Duration(milliseconds: 500), timeoutRetries: 1, ); expect(result.exitCode, 0); }, overrides: { ProcessManager: () => flakyProcessManager, }); testUsingContext(' flaky process generates ProcessException on timeout', () async { final Completer> flakyStderr = Completer>(); final Completer> flakyStdout = Completer>(); flakyProcessManager.processFactory = flakyProcessFactory( flakes: 1, delay: delay, stderr: () => Stream>.fromFuture(flakyStderr.future), stdout: () => Stream>.fromFuture(flakyStdout.future), ); when(flakyProcessManager.killPid(any)).thenAnswer((_) { // Don't let the stderr stream stop until the process is killed. This // ensures that runAsync() does not delay killing the process until // stdout and stderr are drained (which won't happen). flakyStderr.complete([]); flakyStdout.complete([]); return true; }); expect(() => processUtils.run( ['dummy'], timeout: delay - const Duration(milliseconds: 500), timeoutRetries: 0, ), throwsA(isInstanceOf())); }, overrides: { ProcessManager: () => flakyProcessManager, }); }); group('runSync', () { ProcessManager mockProcessManager; setUp(() { mockProcessManager = MockProcessManager(); }); testUsingContext(' succeeds on success', () async { when(mockProcessManager.runSync(['whoohoo'])).thenReturn( ProcessResult(0, 0, '', '') ); expect(processUtils.runSync(['whoohoo']).exitCode, 0); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' fails on failure', () async { when(mockProcessManager.runSync(['boohoo'])).thenReturn( ProcessResult(0, 1, '', '') ); expect(processUtils.runSync(['boohoo']).exitCode, 1); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' throws on failure with throwOnError', () async { when(mockProcessManager.runSync(['kaboom'])).thenReturn( ProcessResult(0, 1, '', '') ); expect(() => processUtils.runSync(['kaboom'], throwOnError: true), throwsA(isA())); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' does not throw on failure with whitelist', () async { when(mockProcessManager.runSync(['kaboom'])).thenReturn( ProcessResult(0, 1, '', '') ); expect( processUtils.runSync( ['kaboom'], throwOnError: true, whiteListFailures: (int c) => c == 1, ).exitCode, 1); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' throws on failure when not in whitelist', () async { when(mockProcessManager.runSync(['kaboom'])).thenReturn( ProcessResult(0, 2, '', '') ); expect( () => processUtils.runSync( ['kaboom'], throwOnError: true, whiteListFailures: (int c) => c == 1, ), throwsA(isA())); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' prints stdout and stderr to trace on success', () async { when(mockProcessManager.runSync(['whoohoo'])).thenReturn( ProcessResult(0, 0, 'stdout', 'stderr') ); expect(processUtils.runSync(['whoohoo']).exitCode, 0); expect(testLogger.traceText, contains('stdout')); expect(testLogger.traceText, contains('stderr')); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' prints stdout to status and stderr to error on failure with throwOnError', () async { when(mockProcessManager.runSync(['kaboom'])).thenReturn( ProcessResult(0, 1, 'stdout', 'stderr') ); expect(() => processUtils.runSync(['kaboom'], throwOnError: true), throwsA(isA())); expect(testLogger.statusText, contains('stdout')); expect(testLogger.errorText, contains('stderr')); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' does not print stdout with hideStdout', () async { when(mockProcessManager.runSync(['whoohoo'])).thenReturn( ProcessResult(0, 0, 'stdout', 'stderr') ); expect(processUtils.runSync(['whoohoo'], hideStdout: true).exitCode, 0); expect(testLogger.traceText.contains('stdout'), isFalse); expect(testLogger.traceText, contains('stderr')); }, overrides: { ProcessManager: () => mockProcessManager, }); }); group('exitsHappySync', () { ProcessManager mockProcessManager; setUp(() { mockProcessManager = MockProcessManager(); }); testUsingContext(' succeeds on success', () async { when(mockProcessManager.runSync(['whoohoo'])).thenReturn( ProcessResult(0, 0, '', '') ); expect(processUtils.exitsHappySync(['whoohoo']), isTrue); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' fails on failure', () async { when(mockProcessManager.runSync(['boohoo'])).thenReturn( ProcessResult(0, 1, '', '') ); expect(processUtils.exitsHappySync(['boohoo']), isFalse); }, overrides: { ProcessManager: () => mockProcessManager, }); }); group('exitsHappy', () { ProcessManager mockProcessManager; setUp(() { mockProcessManager = MockProcessManager(); }); testUsingContext(' succeeds on success', () async { when(mockProcessManager.run(['whoohoo'])).thenAnswer((_) { return Future.value(ProcessResult(0, 0, '', '')); }); expect(await processUtils.exitsHappy(['whoohoo']), isTrue); }, overrides: { ProcessManager: () => mockProcessManager, }); testUsingContext(' fails on failure', () async { when(mockProcessManager.run(['boohoo'])).thenAnswer((_) { return Future.value(ProcessResult(0, 1, '', '')); }); expect(await processUtils.exitsHappy(['boohoo']), isFalse); }, overrides: { ProcessManager: () => mockProcessManager, }); }); } class PlainMockProcessManager extends Mock implements ProcessManager {}