diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 836bb125e81..b30d5721bc2 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -184,12 +184,14 @@ class KernelCompiler { @required Artifacts artifacts, @required List fileSystemRoots, @required String fileSystemScheme, + @visibleForTesting StdoutHandler stdoutHandler, }) : _logger = logger, _fileSystem = fileSystem, _artifacts = artifacts, _processManager = processManager, _fileSystemScheme = fileSystemScheme, - _fileSystemRoots = fileSystemRoots; + _fileSystemRoots = fileSystemRoots, + _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger); final FileSystem _fileSystem; final Artifacts _artifacts; @@ -197,6 +199,7 @@ class KernelCompiler { final Logger _logger; final String _fileSystemScheme; final List _fileSystemRoots; + final StdoutHandler _stdoutHandler; Future compile({ String sdkRoot, @@ -310,7 +313,6 @@ class KernelCompiler { _logger.printTrace(command.join(' ')); final Process server = await _processManager.start(command); - final StdoutHandler _stdoutHandler = StdoutHandler(logger: _logger); server.stderr .transform(utf8.decoder) .listen(_logger.printError); @@ -540,11 +542,12 @@ class DefaultResidentCompiler implements ResidentCompiler { this.platformDill, List dartDefines, this.librariesSpec, + @visibleForTesting StdoutHandler stdoutHandler, }) : assert(sdkRoot != null), _logger = logger, _processManager = processManager, _artifacts = artifacts, - _stdoutHandler = StdoutHandler(logger: logger), + _stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger), _platform = platform, dartDefines = dartDefines ?? const [], // This is a URI, not a file path, so the forward slash is correct even on Windows. diff --git a/packages/flutter_tools/test/general.shard/compile_batch_test.dart b/packages/flutter_tools/test/general.shard/compile_batch_test.dart index 0805f51d3ca..7e6ba8aacf9 100644 --- a/packages/flutter_tools/test/general.shard/compile_batch_test.dart +++ b/packages/flutter_tools/test/general.shard/compile_batch_test.dart @@ -8,64 +8,71 @@ import 'dart:async'; import 'package:file/memory.dart'; import 'package:flutter_tools/src/artifacts.dart'; -import 'package:flutter_tools/src/base/io.dart'; import 'package:flutter_tools/src/base/logger.dart'; import 'package:flutter_tools/src/build_info.dart'; import 'package:flutter_tools/src/compile.dart'; -import 'package:flutter_tools/src/convert.dart'; -import 'package:mockito/mockito.dart'; import 'package:package_config/package_config.dart'; -import 'package:process/process.dart'; import '../src/common.dart'; import '../src/context.dart'; -import '../src/mocks.dart'; void main() { - ProcessManager mockProcessManager; - MockProcess mockFrontendServer; - MockStdIn mockFrontendServerStdIn; - MockStream mockFrontendServerStdErr; + testWithoutContext('StdoutHandler can parse output for successful batch compilation', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); - List latestCommand; + stdoutHandler.reset(); + 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0'.split('\n').forEach(stdoutHandler.handler); + final CompilerOutput output = await stdoutHandler.compilerOutput.future; - setUp(() { - mockProcessManager = MockProcessManager(); - mockFrontendServer = MockProcess(); - mockFrontendServerStdIn = MockStdIn(); - mockFrontendServerStdErr = MockStream(); - - when(mockFrontendServer.stderr) - .thenAnswer((Invocation invocation) => mockFrontendServerStdErr); - final StreamController stdErrStreamController = StreamController(); - when(mockFrontendServerStdErr.transform(any)).thenAnswer((_) => stdErrStreamController.stream); - when(mockFrontendServer.stdin).thenReturn(mockFrontendServerStdIn); - when(mockProcessManager.canRun(any)).thenReturn(true); - when(mockProcessManager.start(any)).thenAnswer( - (Invocation invocation) { - latestCommand = invocation.positionalArguments.first as List; - return Future.value(mockFrontendServer); - }); - when(mockFrontendServer.exitCode).thenAnswer((_) async => 0); + expect(logger.errorText, equals('line1\nline2\n')); + expect(output.outputFilename, equals('/path/to/main.dart.dill')); }); - testWithoutContext('batch compile single dart successful compilation', () async { - when(mockFrontendServer.stdout) - .thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value(utf8.encode( - 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0' - )) - )); + testWithoutContext('StdoutHandler can parse output for failed batch compilation', () async { final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + + stdoutHandler.reset(); + 'result abc\nline1\nline2\nabc\nabc'.split('\n').forEach(stdoutHandler.handler); + final CompilerOutput output = await stdoutHandler.compilerOutput.future; + + expect(logger.errorText, equals('line1\nline2\n')); + expect(output, equals(null)); + }); + + testWithoutContext('KernelCompiler passes correct configuration to frontend server process', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + final KernelCompiler kernelCompiler = KernelCompiler( artifacts: Artifacts.test(), fileSystem: MemoryFileSystem.test(), fileSystemRoots: [], fileSystemScheme: '', logger: logger, - processManager: mockProcessManager + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + 'file:///path/to/main.dart' + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, ); - final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', buildMode: BuildMode.debug, trackWidgetCreation: false, @@ -73,28 +80,135 @@ void main() { packageConfig: PackageConfig.empty, packagesPath: '.packages', ); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); - expect(mockFrontendServerStdIn.getAndClear(), isEmpty); - expect(logger.errorText, equals('line1\nline2\n')); - expect(output.outputFilename, equals('/path/to/main.dart.dill')); + expect((await output).outputFilename, ''); }); - testWithoutContext('passes correct AOT config to kernel compiler in aot/profile mode', () async { - when(mockFrontendServer.stdout) - .thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value(utf8.encode( - 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0' - )) - )); + testWithoutContext('KernelCompiler returns null if StdoutHandler returns null', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + final KernelCompiler kernelCompiler = KernelCompiler( artifacts: Artifacts.test(), fileSystem: MemoryFileSystem.test(), fileSystemRoots: [], fileSystemScheme: '', - logger: BufferLogger.test(), - processManager: mockProcessManager + logger: logger, + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + 'file:///path/to/main.dart' + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, ); - await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + mainPath: '/path/to/main.dart', + buildMode: BuildMode.debug, + trackWidgetCreation: false, + dartDefines: const [], + packageConfig: PackageConfig.empty, + packagesPath: '.packages', + ); + stdoutHandler.compilerOutput.complete(null); + completer.complete(); + + expect(await output, null); + }); + + testWithoutContext('KernelCompiler returns null if frontend_server process exits with non-zero code', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + + final KernelCompiler kernelCompiler = KernelCompiler( + artifacts: Artifacts.test(), + fileSystem: MemoryFileSystem.test(), + fileSystemRoots: [], + fileSystemScheme: '', + logger: logger, + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + 'file:///path/to/main.dart' + ], completer: completer, exitCode: 127), + ]), + stdoutHandler: stdoutHandler, + ); + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + mainPath: '/path/to/main.dart', + buildMode: BuildMode.debug, + trackWidgetCreation: false, + dartDefines: const [], + packageConfig: PackageConfig.empty, + packagesPath: '.packages', + ); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); + + expect(await output, null); + }); + + testWithoutContext('KernelCompiler passes correct AOT config to frontend_server in aot/profile mode', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + + final KernelCompiler kernelCompiler = KernelCompiler( + artifacts: Artifacts.test(), + fileSystem: MemoryFileSystem.test(), + fileSystemRoots: [], + fileSystemScheme: '', + logger: logger, + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=true', + '-Ddart.vm.product=false', + '--no-link-platform', + '--aot', + '--tfa', + '--packages', + '.packages', + 'file:///path/to/main.dart' + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, + ); + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', buildMode: BuildMode.profile, trackWidgetCreation: false, @@ -103,33 +217,45 @@ void main() { packageConfig: PackageConfig.empty, packagesPath: '.packages', ); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); - expect(mockFrontendServerStdIn.getAndClear(), isEmpty); - final VerificationResult argVerification = verify(mockProcessManager.start(captureAny)); - expect(argVerification.captured.single, containsAll([ - '--aot', - '--tfa', - '-Ddart.vm.profile=true', - '-Ddart.vm.product=false', - ])); + expect((await output).outputFilename, ''); }); testWithoutContext('passes correct AOT config to kernel compiler in aot/release mode', () async { - when(mockFrontendServer.stdout) - .thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value(utf8.encode( - 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0' - )) - )); + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + final KernelCompiler kernelCompiler = KernelCompiler( artifacts: Artifacts.test(), fileSystem: MemoryFileSystem.test(), fileSystemRoots: [], fileSystemScheme: '', - logger: BufferLogger.test(), - processManager: mockProcessManager + logger: logger, + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=true', + '--no-link-platform', + '--aot', + '--tfa', + '--packages', + '.packages', + 'file:///path/to/main.dart' + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, ); - await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', buildMode: BuildMode.release, trackWidgetCreation: false, @@ -138,93 +264,47 @@ void main() { packageConfig: PackageConfig.empty, packagesPath: '.packages', ); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); - expect(mockFrontendServerStdIn.getAndClear(), isEmpty); - final VerificationResult argVerification = verify(mockProcessManager.start(captureAny)); - expect(argVerification.captured.single, containsAll([ - '--aot', - '--tfa', - '-Ddart.vm.profile=false', - '-Ddart.vm.product=true', - ])); + expect((await output).outputFilename, ''); }); - testWithoutContext('batch compile single dart failed compilation', () async { - when(mockFrontendServer.stdout) - .thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value(utf8.encode( - 'result abc\nline1\nline2\nabc\nabc' - )) - )); + testWithoutContext('KernelCompiler passes dartDefines to the frontend_server', () async { final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + final KernelCompiler kernelCompiler = KernelCompiler( artifacts: Artifacts.test(), fileSystem: MemoryFileSystem.test(), fileSystemRoots: [], fileSystemScheme: '', logger: logger, - processManager: mockProcessManager - ); - final CompilerOutput output = await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', - mainPath: '/path/to/main.dart', - buildMode: BuildMode.debug, - trackWidgetCreation: false, - dartDefines: const [], - packageConfig: PackageConfig.empty, - packagesPath: '.packages', + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-DFOO=bar', + '-DBAZ=qux', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + 'file:///path/to/main.dart', + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, ); - expect(mockFrontendServerStdIn.getAndClear(), isEmpty); - expect(logger.errorText, equals('line1\nline2\n')); - expect(output, equals(null)); - }); - - testWithoutContext('batch compile single dart abnormal compiler termination', () async { - when(mockFrontendServer.exitCode).thenAnswer((_) async => 255); - when(mockFrontendServer.stdout) - .thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value(utf8.encode( - 'result abc\nline1\nline2\nabc\nabc' - )) - )); - final BufferLogger logger = BufferLogger.test(); - final KernelCompiler kernelCompiler = KernelCompiler( - artifacts: Artifacts.test(), - fileSystem: MemoryFileSystem.test(), - fileSystemRoots: [], - fileSystemScheme: '', - logger: logger, - processManager: mockProcessManager - ); - final CompilerOutput output = await kernelCompiler.compile( - sdkRoot: '/path/to/sdkroot', - mainPath: '/path/to/main.dart', - buildMode: BuildMode.debug, - trackWidgetCreation: false, - dartDefines: const [], - packageConfig: PackageConfig.empty, - packagesPath: '.packages', - ); - expect(mockFrontendServerStdIn.getAndClear(), isEmpty); - expect(logger.errorText, equals('line1\nline2\n')); - expect(output, equals(null)); - }); - - testWithoutContext('passes dartDefines to the kernel compiler', () async { - // Use unsuccessful result because it's easier to setup in test. We only care about arguments passed to the compiler. - when(mockFrontendServer.exitCode).thenAnswer((_) async => 255); - when(mockFrontendServer.stdout).thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value([]) - )); - final KernelCompiler kernelCompiler = KernelCompiler( - artifacts: Artifacts.test(), - fileSystem: MemoryFileSystem.test(), - fileSystemRoots: [], - fileSystemScheme: '', - logger: BufferLogger.test(), - processManager: mockProcessManager - ); - await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/path/to/main.dart', buildMode: BuildMode.debug, trackWidgetCreation: false, @@ -233,15 +313,17 @@ void main() { packagesPath: '.packages', ); - expect(latestCommand, containsAllInOrder(['-DFOO=bar', '-DBAZ=qux'])); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); + + expect((await output).outputFilename, ''); }); - testWithoutContext('maps a file to a multiroot scheme if provided', () async { - // Use unsuccessful result because it's easier to setup in test. We only care about arguments passed to the compiler. - when(mockFrontendServer.exitCode).thenAnswer((_) async => 255); - when(mockFrontendServer.stdout).thenAnswer((Invocation invocation) => Stream>.fromFuture( - Future>.value([]) - )); + testWithoutContext('KernelCompiler maps a file to a multi-root scheme if provided', () async { + final BufferLogger logger = BufferLogger.test(); + final StdoutHandler stdoutHandler = StdoutHandler(logger: logger); + final Completer completer = Completer(); + final KernelCompiler kernelCompiler = KernelCompiler( artifacts: Artifacts.test(), fileSystem: MemoryFileSystem.test(), @@ -249,10 +331,29 @@ void main() { '/foo/bar/fizz', ], fileSystemScheme: 'scheme', - logger: BufferLogger.test(), - processManager: mockProcessManager + logger: logger, + processManager: FakeProcessManager.list([ + FakeCommand(command: const [ + 'Artifact.engineDartBinary', + '--disable-dart-dev', + 'Artifact.frontendServerSnapshotForEngineDartSdk', + '--sdk-root', + '/path/to/sdkroot/', + '--target=flutter', + '--no-print-incremental-dependencies', + '-Ddart.vm.profile=false', + '-Ddart.vm.product=false', + '--enable-asserts', + '--no-link-platform', + '--packages', + '.packages', + 'scheme:///main.dart', + ], completer: completer), + ]), + stdoutHandler: stdoutHandler, ); - await kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', + + final Future output = kernelCompiler.compile(sdkRoot: '/path/to/sdkroot', mainPath: '/foo/bar/fizz/main.dart', buildMode: BuildMode.debug, trackWidgetCreation: false, @@ -261,9 +362,9 @@ void main() { packagesPath: '.packages', ); - expect(latestCommand, containsAll(['scheme:///main.dart'])); + stdoutHandler.compilerOutput.complete(const CompilerOutput('', 0, [])); + completer.complete(); + + expect((await output).outputFilename, ''); }); } - -class MockProcess extends Mock implements Process {} -class MockProcessManager extends Mock implements ProcessManager {}