// 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 '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/base/platform.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:package_config/package_config.dart'; import 'package:process/process.dart'; import 'package:test/fake.dart'; import '../src/common.dart'; import '../src/fake_process_manager.dart'; import '../src/fakes.dart'; void main() { late FakeProcessManager processManager; late ResidentCompiler generator; late MemoryIOSink frontendServerStdIn; late StreamController<String> stdErrStreamController; late BufferLogger testLogger; late MemoryFileSystem fileSystem; setUp(() { testLogger = BufferLogger.test(); processManager = FakeProcessManager(); frontendServerStdIn = MemoryIOSink(); fileSystem = MemoryFileSystem.test(); generator = ResidentCompiler( 'sdkroot', buildMode: BuildMode.debug, artifacts: Artifacts.test(), processManager: processManager, logger: testLogger, platform: FakePlatform(), fileSystem: fileSystem, ); stdErrStreamController = StreamController<String>(); processManager.process.stdin = frontendServerStdIn; processManager.process.stderr = stdErrStreamController.stream.transform(utf8.encoder); }); testWithoutContext('compile expression fails if not previously compiled', () async { final CompilerOutput? result = await generator.compileExpression( '2+2', null, null, null, null, false); expect(result, isNull); }); testWithoutContext('compile expression can compile single expression', () async { final Completer<List<int>> compileResponseCompleter = Completer<List<int>>(); final Completer<List<int>> compileExpressionResponseCompleter = Completer<List<int>>(); fileSystem.file('/path/to/main.dart.dill') ..createSync(recursive: true) ..writeAsBytesSync(<int>[1, 2, 3, 4]); processManager.process.stdout = Stream<List<int>>.fromFutures( <Future<List<int>>>[ compileResponseCompleter.future, compileExpressionResponseCompleter.future, ], ); compileResponseCompleter.complete(Future<List<int>>.value(utf8.encode( 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n' ))); await generator.recompile( Uri.file('/path/to/main.dart'), null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, projectRootPath: '', fs: fileSystem, ).then((CompilerOutput? output) { expect(frontendServerStdIn.getAndClear(), 'compile file:///path/to/main.dart\n'); expect(testLogger.errorText, equals('line1\nline2\n')); expect(output!.outputFilename, equals('/path/to/main.dart.dill')); compileExpressionResponseCompleter.complete( Future<List<int>>.value(utf8.encode( 'result def\nline1\nline2\ndef\ndef /path/to/main.dart.dill.incremental 0\n' ))); generator.compileExpression( '2+2', null, null, null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[1, 2, 3, 4]); } ); }); }); testWithoutContext('compile expressions without awaiting', () async { final Completer<List<int>> compileResponseCompleter = Completer<List<int>>(); final Completer<List<int>> compileExpressionResponseCompleter1 = Completer<List<int>>(); final Completer<List<int>> compileExpressionResponseCompleter2 = Completer<List<int>>(); processManager.process.stdout = Stream<List<int>>.fromFutures( <Future<List<int>>>[ compileResponseCompleter.future, compileExpressionResponseCompleter1.future, compileExpressionResponseCompleter2.future, ]); // The test manages timing via completers. unawaited( generator.recompile( Uri.parse('/path/to/main.dart'), null, /* invalidatedFiles */ outputPath: '/build/', packageConfig: PackageConfig.empty, projectRootPath: '', fs: MemoryFileSystem(), ).then((CompilerOutput? outputCompile) { expect(testLogger.errorText, equals('line1\nline2\n')); expect(outputCompile!.outputFilename, equals('/path/to/main.dart.dill')); fileSystem.file('/path/to/main.dart.dill.incremental') ..createSync(recursive: true) ..writeAsBytesSync(<int>[0, 1, 2, 3]); compileExpressionResponseCompleter1.complete(Future<List<int>>.value(utf8.encode( 'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n' ))); }), ); // The test manages timing via completers. final Completer<bool> lastExpressionCompleted = Completer<bool>(); unawaited( generator.compileExpression('0+1', null, null, null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[0, 1, 2, 3]); fileSystem.file('/path/to/main.dart.dill.incremental') ..createSync(recursive: true) ..writeAsBytesSync(<int>[4, 5, 6, 7]); compileExpressionResponseCompleter2.complete(Future<List<int>>.value(utf8.encode( 'result def\nline1\nline2\ndef /path/to/main.dart.dill.incremental 0\n' ))); }, ), ); // The test manages timing via completers. unawaited( generator.compileExpression('1+1', null, null, null, null, false).then( (CompilerOutput? outputExpression) { expect(outputExpression, isNotNull); expect(outputExpression!.expressionData, <int>[4, 5, 6, 7]); lastExpressionCompleted.complete(true); }, ), ); compileResponseCompleter.complete(Future<List<int>>.value(utf8.encode( 'result abc\nline1\nline2\nabc\nabc /path/to/main.dart.dill 0\n' ))); expect(await lastExpressionCompleted.future, isTrue); }); } class FakeProcess extends Fake implements Process { @override Stream<List<int>> stdout = const Stream<List<int>>.empty(); @override Stream<List<int>> stderr = const Stream<List<int>>.empty(); @override IOSink stdin = IOSink(StreamController<List<int>>().sink); @override Future<int> get exitCode => Completer<int>().future; } class FakeProcessManager extends Fake implements ProcessManager { final FakeProcess process = FakeProcess(); @override bool canRun(dynamic executable, {String? workingDirectory}) { return true; } @override Future<Process> start(List<Object> command, {String? workingDirectory, Map<String, String>? environment, bool includeParentEnvironment = true, bool runInShell = false, ProcessStartMode mode = ProcessStartMode.normal}) async { return process; } }