| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| // ignore_for_file: empty_catches | |
| import 'dart:async'; | |
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'dart:isolate'; | |
| import 'package:args/args.dart'; | |
| import 'package:front_end/src/api_unstable/vm.dart'; | |
| import 'package:frontend_server/compute_kernel.dart'; | |
| import 'package:frontend_server/frontend_server.dart'; | |
| import 'package:frontend_server/starter.dart'; | |
| import 'package:kernel/ast.dart' show Component, Library; | |
| import 'package:kernel/binary/ast_to_binary.dart'; | |
| import 'package:kernel/class_hierarchy.dart'; | |
| import 'package:kernel/core_types.dart'; | |
| import 'package:kernel/kernel.dart' show loadComponentFromBinary; | |
| import 'package:kernel/target/targets.dart'; | |
| import 'package:kernel/verifier.dart' show VerificationStage, verifyComponent; | |
| import 'package:path/path.dart' as path; | |
| import 'package:test/test.dart'; | |
| import 'package:vm/incremental_compiler.dart'; | |
| import 'package:vm/kernel_front_end.dart'; | |
| bool useJsonForCommunication = false; | |
| bool useJsonLineBreaks = false; | |
| class _MockedBinaryPrinter implements BinaryPrinter { | |
| @override | |
| dynamic noSuchMethod(Invocation invocation) {} | |
| } | |
| class _MockedBinaryPrinterFactory implements BinaryPrinterFactory { | |
| @override | |
| BinaryPrinter newBinaryPrinter(Sink<List<int>> targetSink) { | |
| return new _MockedBinaryPrinter(); | |
| } | |
| @override | |
| dynamic noSuchMethod(Invocation invocation) {} | |
| } | |
| typedef VerifyCompile = void Function(String entryPoint, ArgResults opts); | |
| typedef VerifyInvalidate = void Function(Uri uri); | |
| typedef VerifyRecompileDelta = void Function(String? entryPoint); | |
| typedef Verify = void Function(); | |
| void nopVerifyCompile(String entryPoint, ArgResults opts) {} | |
| void nopVerifyInvalidate(Uri uri) {} | |
| void nopVerifyRecompileDelta(String? entryPoint) {} | |
| void nopVerify() {} | |
| class _MockedCompiler implements CompilerInterface { | |
| new({ | |
| this.verifyCompile = nopVerifyCompile, | |
| this.verifyRecompileDelta = nopVerifyRecompileDelta, | |
| this.verifyInvalidate = nopVerifyInvalidate, | |
| this.verifyAcceptLastDelta = nopVerify, | |
| this.verifyResetIncrementalCompiler = nopVerify, | |
| }); | |
| @override | |
| void acceptLastDelta() { | |
| verifyAcceptLastDelta(); | |
| } | |
| @override | |
| Future<void> recompileDelta({ | |
| String? entryPoint, | |
| bool recompileRestart = false, | |
| }) async { | |
| verifyRecompileDelta(entryPoint); | |
| } | |
| @override | |
| void resetIncrementalCompiler() { | |
| verifyResetIncrementalCompiler(); | |
| } | |
| @override | |
| void invalidate(Uri uri) { | |
| verifyInvalidate(uri); | |
| } | |
| @override | |
| Future<bool> compile( | |
| String entryPoint, | |
| ArgResults opts, { | |
| IncrementalCompiler? generator, | |
| }) async { | |
| verifyCompile(entryPoint, opts); | |
| return true; | |
| } | |
| @override | |
| dynamic noSuchMethod(Invocation invocation) {} | |
| final VerifyCompile verifyCompile; | |
| final VerifyRecompileDelta verifyRecompileDelta; | |
| final VerifyInvalidate verifyInvalidate; | |
| final Verify verifyAcceptLastDelta; | |
| final Verify verifyResetIncrementalCompiler; | |
| } | |
| class _MockedIncrementalCompiler implements IncrementalCompiler { | |
| @override | |
| void accept() {} | |
| @override | |
| bool get initialized => false; | |
| @override | |
| Future<IncrementalCompilerResult> compile({List<Uri>? entryPoints}) async { | |
| Component component = new Component(); | |
| CoreTypes coreTypes = new CoreTypes(component); | |
| ClassHierarchy classHierarchy = new ClassHierarchy(component, coreTypes); | |
| return new Future<IncrementalCompilerResult>.value( | |
| new IncrementalCompilerResult( | |
| component, | |
| coreTypes: coreTypes, | |
| classHierarchy: classHierarchy, | |
| ), | |
| ); | |
| } | |
| @override | |
| dynamic noSuchMethod(Invocation invocation) {} | |
| } | |
| Future<void> main() async { | |
| group('basic', () { | |
| final _MockedCompiler compiler = new _MockedCompiler(); | |
| test('train with mocked compiler completes', () async { | |
| await starter(<String>['--train', 'foo.dart'], compiler: compiler); | |
| }); | |
| }); | |
| group('batch compile with mocked compiler', () { | |
| test('compile from command line', () async { | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final List<String> args = <String>[ | |
| 'server.dart', | |
| '--sdk-root', | |
| 'sdkroot', | |
| ]; | |
| await starter(args, compiler: compiler); | |
| }); | |
| test('compile from command line with link platform', () async { | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| expect(opts['link-platform'], equals(true)); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final List<String> args = <String>[ | |
| 'server.dart', | |
| '--sdk-root', | |
| 'sdkroot', | |
| '--link-platform', | |
| ]; | |
| await starter(args, compiler: compiler); | |
| }); | |
| }); | |
| group('interactive compile with mocked compiler', () { | |
| final List<String> args = <String>['--sdk-root', 'sdkroot']; | |
| test('compile one file', () async { | |
| final ReceivePort compileCalled = new ReceivePort(); | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| compileCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('compile server.dart\n'.codeUnits); | |
| await compileCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| group('compile one file to JavaScript', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| final ReceivePort compileCalled = new ReceivePort(); | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| compileCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| [ | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| ...args, | |
| ], | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('compile server.dart\n'.codeUnits); | |
| await compileCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| }); | |
| group('interactive compile with mocked compiler', () { | |
| final List<String> args = <String>['--sdk-root', 'sdkroot']; | |
| test('compile one file', () async { | |
| final ReceivePort compileCalled = new ReceivePort(); | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| compileCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('compile server.dart\n'.codeUnits); | |
| await compileCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| test('compile few files', () async { | |
| final ReceivePort compileCalled = new ReceivePort(); | |
| int counter = 1; | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(entryPoint, equals('server${counter++}.dart')); | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| compileCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('compile server1.dart\n'.codeUnits); | |
| inputStreamController.add('compile server2.dart\n'.codeUnits); | |
| await compileCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| }); | |
| group('interactive incremental compile with mocked compiler', () { | |
| final List<String> args = <String>[ | |
| '--sdk-root', | |
| 'sdkroot', | |
| '--incremental', | |
| ]; | |
| test('recompile few files', () async { | |
| final ReceivePort recompileDeltaCalled = new ReceivePort(); | |
| int invalidated = 0; | |
| int counter = 1; | |
| void verifyI(Uri uri) { | |
| expect(uri.path, contains('file${counter++}.dart')); | |
| invalidated += 1; | |
| } | |
| void verifyR(String? entryPoint) { | |
| expect(invalidated, equals(2)); | |
| expect(entryPoint, equals(null)); | |
| recompileDeltaCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyInvalidate: verifyI, | |
| verifyRecompileDelta: verifyR, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add( | |
| 'recompile abc\nfile1.dart\nfile2.dart\nabc\n'.codeUnits, | |
| ); | |
| await recompileDeltaCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| test('recompile few files with new entrypoint', () async { | |
| int invalidated = 0; | |
| final ReceivePort recompileDeltaCalled = new ReceivePort(); | |
| int counter = 1; | |
| void verifyI(Uri uri) { | |
| expect(uri.path, contains('file${counter++}.dart')); | |
| invalidated += 1; | |
| } | |
| void verifyR(String? entryPoint) { | |
| expect(invalidated, equals(2)); | |
| expect(entryPoint, equals('file2.dart')); | |
| recompileDeltaCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyRecompileDelta: verifyR, | |
| verifyInvalidate: verifyI, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add( | |
| 'recompile file2.dart abc\nfile1.dart\nfile2.dart\nabc\n'.codeUnits, | |
| ); | |
| await recompileDeltaCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| test('accept', () async { | |
| final ReceivePort acceptCalled = new ReceivePort(); | |
| void verify() { | |
| acceptCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyAcceptLastDelta: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('accept\n'.codeUnits); | |
| await acceptCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| test('reset', () async { | |
| final ReceivePort resetCalled = new ReceivePort(); | |
| void verify() { | |
| resetCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyResetIncrementalCompiler: verify, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('reset\n'.codeUnits); | |
| await resetCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| test('compile then recompile', () async { | |
| final ReceivePort recompileDeltaCalled = new ReceivePort(); | |
| bool compile = false; | |
| int invalidate = 0; | |
| bool acceptDelta = false; | |
| void verifyC(String entryPoint, ArgResults opts) { | |
| compile = true; | |
| expect(entryPoint, equals('file1.dart')); | |
| } | |
| void verifyA() { | |
| expect(compile, equals(true)); | |
| acceptDelta = true; | |
| } | |
| int counter = 2; | |
| void verifyI(Uri uri) { | |
| expect(compile, equals(true)); | |
| expect(acceptDelta, equals(true)); | |
| expect(uri.path, contains('file${counter++}.dart')); | |
| invalidate += 1; | |
| } | |
| void verifyR(String? entryPoint) { | |
| expect(compile, equals(true)); | |
| expect(invalidate, equals(2)); | |
| expect(acceptDelta, equals(true)); | |
| expect(entryPoint, equals(null)); | |
| recompileDeltaCalled.sendPort.send(true); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verifyC, | |
| verifyRecompileDelta: verifyR, | |
| verifyInvalidate: verifyI, | |
| verifyAcceptLastDelta: verifyA, | |
| ); | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: compiler, | |
| input: inputStreamController.stream, | |
| ); | |
| inputStreamController.add('compile file1.dart\n'.codeUnits); | |
| inputStreamController.add('accept\n'.codeUnits); | |
| inputStreamController.add( | |
| 'recompile def\nfile2.dart\nfile3.dart\ndef\n'.codeUnits, | |
| ); | |
| await recompileDeltaCalled.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| }); | |
| group('interactive incremental compile with mocked IKG', () { | |
| final List<String> args = <String>[ | |
| '--sdk-root', | |
| 'sdkroot', | |
| '--incremental', | |
| ]; | |
| late Directory tempDir; | |
| setUp(() { | |
| tempDir = Directory.systemTemp.createTempSync(); | |
| }); | |
| tearDown(() { | |
| tempDir.deleteSync(recursive: true); | |
| }); | |
| test('compile then accept', () async { | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| final StreamController<List<int>> stdoutStreamController = | |
| new StreamController<List<int>>(); | |
| final IOSink ioSink = new IOSink(stdoutStreamController.sink); | |
| ReceivePort receivedResult = new ReceivePort(); | |
| String? boundaryKey; | |
| stdoutStreamController.stream | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .listen((String s) { | |
| const String resultOutputSpace = 'result '; | |
| if (boundaryKey == null) { | |
| if (s.startsWith(resultOutputSpace)) { | |
| boundaryKey = s.substring(resultOutputSpace.length); | |
| } | |
| } else { | |
| if (s.startsWith(boundaryKey!)) { | |
| boundaryKey = null; | |
| receivedResult.sendPort.send(true); | |
| } | |
| } | |
| }); | |
| final _MockedIncrementalCompiler generator = | |
| new _MockedIncrementalCompiler(); | |
| final _MockedBinaryPrinterFactory printerFactory = | |
| new _MockedBinaryPrinterFactory(); | |
| Future<int> result = starter( | |
| args, | |
| compiler: null, | |
| input: inputStreamController.stream, | |
| output: ioSink, | |
| generator: generator, | |
| binaryPrinterFactory: printerFactory, | |
| ); | |
| final File source = new File('${tempDir.path}/file1.dart'); | |
| inputStreamController.add('compile ${source.path}\n'.codeUnits); | |
| await receivedResult.first; | |
| inputStreamController.add('accept\n'.codeUnits); | |
| receivedResult = new ReceivePort(); | |
| inputStreamController.add( | |
| 'recompile def\n${source.path}\ndef\n'.codeUnits, | |
| ); | |
| await receivedResult.first; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| expect(await result, 0); | |
| await inputStreamController.close(); | |
| }); | |
| group('compile with output path', () { | |
| void verify(String entryPoint, ArgResults opts) { | |
| expect(opts['sdk-root'], equals('sdkroot')); | |
| } | |
| final _MockedCompiler compiler = new _MockedCompiler( | |
| verifyCompile: verify, | |
| ); | |
| test('compile from command line', () async { | |
| final List<String> args = <String>[ | |
| 'server.dart', | |
| '--sdk-root', | |
| 'sdkroot', | |
| '--output-dill', | |
| '/foo/bar/server.dart.dill', | |
| '--output-incremental-dill', | |
| '/foo/bar/server.incremental.dart.dill', | |
| ]; | |
| expect(await starter(args, compiler: compiler), 0); | |
| }); | |
| }); | |
| }); | |
| group('full compiler tests', () { | |
| final Uri platformKernel = computePlatformBinariesLocation().resolve( | |
| 'vm_platform.dill', | |
| ); | |
| final Uri ddcPlatformKernel = computePlatformBinariesLocation().resolve( | |
| 'ddc_outline.dill', | |
| ); | |
| final Uri sdkRoot = computePlatformBinariesLocation(); | |
| late Directory tempDir; | |
| setUp(() { | |
| Directory systemTempDir = Directory.systemTemp; | |
| tempDir = systemTempDir.createTempSync('frontendServerTest'); | |
| new Directory('${tempDir.path}/.dart_tool').createSync(); | |
| }); | |
| tearDown(() { | |
| tempDir.deleteSync(recursive: true); | |
| }); | |
| test('compile expression', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n}\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full kernel file. | |
| expect(result.errorsCount, equals(0)); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpression('2+2', file.uri, isStatic: null); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(result.errorsCount, equals(0)); | |
| // Previous request should have failed because isStatic was blank | |
| expect(compiledResult.status, isNull); | |
| frontendServer.compileExpression('2+2', file.uri, isStatic: false); | |
| count += 1; | |
| } else if (count == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| // Second request is to 'compile-expression', which results in | |
| // kernel file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compile('foo.bar'); | |
| count += 1; | |
| } else { | |
| expect(count, 3); | |
| // Third request is to 'compile' nonexistent file, that should fail. | |
| expect(result.errorsCount, greaterThan(0)); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 3); | |
| frontendServer.close(); | |
| }); | |
| test( | |
| 'compile expression extension types', | |
| skip: !useJsonForCommunication, | |
| () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| String data = r""" | |
| //@dart=3.3 | |
| void main() { | |
| Foo f = new Foo(42); | |
| print(f); | |
| print(f.value); | |
| f.printValue(); | |
| f.printThis(); | |
| } | |
| extension type Foo(int value) { | |
| void printValue() { | |
| print("This foos value is '$value'"); | |
| } | |
| void printThis() { | |
| print("This foos this value is '$this'"); | |
| } | |
| }"""; | |
| file.writeAsStringSync(data); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| Uri fileImportUri = Uri.parse("package:hello/foo.dart"); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| final FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full kernel file. | |
| expect(result.errorsCount, equals(0)); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpression( | |
| 'f.value', | |
| fileImportUri, | |
| // actually file.uri but check we accept this too. | |
| scriptUri: fileImportUri, | |
| definitions: ["f"], | |
| // int. | |
| definitionTypes: ["dart:core", "int", "1", "0"], | |
| methodName: "main", | |
| offset: 63, | |
| isStatic: true, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(result.errorsCount, equals(0)); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compileExpression( | |
| 'this.value', | |
| fileImportUri, | |
| scriptUri: file.uri, | |
| definitions: ["#this"], | |
| // int. | |
| definitionTypes: ["dart:core", "int", "1", "0"], | |
| methodName: "Foo.printValue", | |
| offset: 174, | |
| isStatic: true, | |
| ); | |
| count += 1; | |
| } else if (count == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 2); | |
| frontendServer.close(); | |
| }, | |
| ); | |
| group('compile expression extension types to JavaScript', () { | |
| // TODO(jensj): This is the javascript version of the above. | |
| // It should share code. | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| String data = r""" | |
| //@dart=3.3 | |
| void main() { | |
| Foo f = new Foo(42); | |
| print(f); | |
| print(f.value); | |
| f.printValue(); | |
| f.printThis(); | |
| } | |
| extension type Foo(int value) { | |
| void printValue() { | |
| print("This foos value is '$value'"); | |
| } | |
| void printThis() { | |
| print("This foos this value is '$this'"); | |
| } | |
| }"""; | |
| file.writeAsStringSync(data); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/foo.dart.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| final FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full JavaScript | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'f.value', | |
| libraryUri: library, | |
| line: 5, | |
| column: 3, | |
| scriptUri: file.uri, | |
| jsFrameValues: {"f": "42"}, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(result.errorsCount, equals(0)); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'this.value', | |
| libraryUri: library, | |
| line: 11, | |
| column: 5, | |
| scriptUri: file.uri, | |
| jsFrameValues: {r"$this": "42"}, | |
| ); | |
| count += 1; | |
| } else if (count == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 2); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }, skip: !useJsonForCommunication); | |
| test('mixed compile expression commands with non-web target', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {}\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| String library = 'package:hello/foo.dart'; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full kernel file. | |
| expect(result.errorsCount, equals(0)); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpression('2+2', file.uri, isStatic: false); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(result.errorsCount, equals(0)); | |
| // Second request is to 'compile-expression', which results in | |
| // kernel file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compileExpressionToJs( | |
| expression: '', | |
| libraryUri: library, | |
| line: 1, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 2) { | |
| // Third request is to 'compile-expression-to-js' that fails | |
| // due to non-web target | |
| expect(result.errorsCount, equals(0)); | |
| expect(compiledResult.status, isNull); | |
| frontendServer.compileExpression('2+2', file.uri, isStatic: false); | |
| count += 1; | |
| } else if (count == 3) { | |
| expect(result.errorsCount, equals(0)); | |
| // Fourth request is to 'compile-expression', which results in | |
| // kernel file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 3); | |
| frontendServer.close(); | |
| }); | |
| test('compiler reports correct sources added', () async { | |
| File libFile = new File('${tempDir.path}/lib.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("var foo = 42;"); | |
| File mainFile = new File('${tempDir.path}/main.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("main() => print('foo');\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(mainFile.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| compiledResult.expectNoErrors(); | |
| if (count == 0) { | |
| expect(compiledResult.sources.length, equals(1)); | |
| expect(compiledResult.sources, contains('+${mainFile.uri}')); | |
| frontendServer.accept(); | |
| mainFile.writeAsStringSync( | |
| "import 'lib.dart'; main() => print(foo);\n", | |
| ); | |
| frontendServer.recompile(mainFile.uri, entryPoint: mainFile.path); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(compiledResult.sources.length, equals(1)); | |
| expect(compiledResult.sources, contains('+${libFile.uri}')); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('compiler reports correct sources removed', () async { | |
| File libFile = new File('${tempDir.path}/lib.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("var foo = 42;"); | |
| File mainFile = new File('${tempDir.path}/main.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("import 'lib.dart'; main() => print(foo);\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(mainFile.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| compiledResult.expectNoErrors(); | |
| if (count == 0) { | |
| expect(compiledResult.sources.length, equals(2)); | |
| expect( | |
| compiledResult.sources, | |
| allOf(contains('+${mainFile.uri}'), contains('+${libFile.uri}')), | |
| ); | |
| frontendServer.accept(); | |
| mainFile.writeAsStringSync("main() => print('foo');\n"); | |
| frontendServer.recompile(mainFile.uri, entryPoint: mainFile.path); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(compiledResult.sources.length, equals(1)); | |
| expect(compiledResult.sources, contains('-${libFile.uri}')); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('compile expression when delta is rejected', () async { | |
| File fileLib = new File('${tempDir.path}/lib.dart')..createSync(); | |
| fileLib.writeAsStringSync("foo() => 42;\n"); | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("import 'lib.dart'; main1() => print(foo);\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request was to 'compile', which resulted in full kernel | |
| // file. | |
| expect(result.errorsCount, 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpression('main1', file.uri, isStatic: true); | |
| count += 1; | |
| } else if (count == 1) { | |
| // Second request was to 'compile-expression', which resulted in | |
| // kernel file with a function that wraps compiled expression. | |
| expect(result.errorsCount, 0); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| file.writeAsStringSync("import 'lib.dart'; main() => foo();\n"); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| count += 1; | |
| } else if (count == 2) { | |
| // Third request was to recompile the script after renaming a | |
| // function. | |
| expect(result.errorsCount, 0); | |
| frontendServer.reject(); | |
| count += 1; | |
| } else if (count == 3) { | |
| // Fourth request was to reject the compilation results. | |
| frontendServer.compileExpression('main1', file.uri, isStatic: true); | |
| count += 1; | |
| } else { | |
| expect(count, 4); | |
| // Fifth request was to 'compile-expression' that references | |
| // original function, which should still be successful. | |
| expect(result.errorsCount, 0); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('compile expression to JavaScript when delta is rejected', () async { | |
| File fileLib = new File('${tempDir.path}/lib.dart')..createSync(); | |
| fileLib.writeAsStringSync("foo() => 42;\n"); | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| // Initial compile contains a generic class so an edit can be made later | |
| // that is known to be rejected. | |
| file.writeAsStringSync( | |
| "import 'lib.dart'; class A<T, S> {} main1() => print(foo);\n", | |
| ); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| // TODO(nshahan): Remove these two flags when library bundle format is | |
| // the default without passing --dartdevc-canary. | |
| '--dartdevc-module-format=ddc', | |
| '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request was to 'compile', which resulted in full kernel | |
| // file. | |
| expect(result.errorsCount, 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'main1', | |
| libraryUri: 'package:hello/foo.dart', | |
| line: 1, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| // Second request was to 'compile-expression', which resulted in | |
| // kernel file with a function that wraps compiled expression. | |
| expect(result.errorsCount, 0); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| // Removing a generic class type argument is known to cause a reload | |
| // rejection. | |
| file.writeAsStringSync( | |
| "import 'lib.dart'; class A<T> {} main() => foo();\n", | |
| ); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| count += 1; | |
| } else if (count == 2) { | |
| // Third request was to recompile the script after renaming a | |
| // function. | |
| expect(result.errorsCount, 1); | |
| frontendServer.reject(); | |
| count += 1; | |
| } else if (count == 3) { | |
| // Fourth request was to reject the compilation results. | |
| expect(result.errorsCount, 0); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'main1', | |
| libraryUri: 'package:hello/foo.dart', | |
| line: 1, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else { | |
| expect(count, 4); | |
| // Fifth request was to 'compile-expression' that references | |
| // original function, which should still be successful. | |
| expect(result.errorsCount, 0); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('compile expression to JavaScript when delta is accepted', () async { | |
| File fileLib = new File('${tempDir.path}/lib.dart')..createSync(); | |
| fileLib.writeAsStringSync("foo() => 42;\n"); | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("import 'lib.dart'; main1() => print(foo);\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| // TODO(nshahan): Remove these two flags when library bundle format is | |
| // the default without passing --dartdevc-canary. | |
| '--dartdevc-module-format=ddc', | |
| '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request was to 'compile', which resulted in full kernel | |
| // file. | |
| expect(result.errorsCount, 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'main1', | |
| libraryUri: 'package:hello/foo.dart', | |
| line: 1, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| // Second request was to 'compile-expression', which resulted in | |
| // kernel file with a function that wraps compiled expression. | |
| expect(result.errorsCount, 0); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| file.writeAsStringSync("import 'lib.dart'; main() => foo();\n"); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| count += 1; | |
| } else if (count == 2) { | |
| // Third request was to recompile the script after renaming a | |
| // function. | |
| expect(result.errorsCount, 0); | |
| File dillIncFile = new File('${dillFile.path}.incremental.dill'); | |
| expect(dillIncFile.existsSync(), equals(true)); | |
| expect(result.filename, dillIncFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'main', | |
| libraryUri: 'package:hello/foo.dart', | |
| line: 1, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else { | |
| expect(count, 3); | |
| // Fourth request was to 'compile-expression' that references the | |
| // new function, which should still be successful. | |
| expect(result.errorsCount, 0); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('reject - recreate issue 55357', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync( | |
| "extension on String { int get fooValue => 42; }\n", | |
| ); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request was to 'compile', which resulted in full kernel file. | |
| expect(result.errorsCount, 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| count += 1; | |
| } else if (count == 1) { | |
| // Second request was to recompile after an accept. | |
| expect(result.errorsCount, 0); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.reject(); | |
| count += 1; | |
| } else if (count == 2) { | |
| // Third request was to reject. Now ask to compile again. | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| count += 1; | |
| } else if (count == 3) { | |
| // Fourth request was to recompile the script after a reject. | |
| expect(result.errorsCount, 0); | |
| expect(result.errorsCount, 0); | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }, timeout: new Timeout.factor(100)); | |
| test('recompile request keeps incremental output dill filename', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {}\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full kernel file. | |
| expect(dillFile.existsSync(), equals(true)); | |
| compiledResult.expectNoErrors(filename: dillFile.path); | |
| count += 1; | |
| frontendServer.accept(); | |
| File file2 = new File('${tempDir.path}/bar.dart')..createSync(); | |
| file2.writeAsStringSync("main() {}\n"); | |
| frontendServer.recompile(file2.uri, entryPoint: file2.path); | |
| } else { | |
| expect(count, 1); | |
| // Second request is to 'recompile', which results in incremental | |
| // kernel file. | |
| File dillIncFile = new File('${dillFile.path}.incremental.dill'); | |
| compiledResult.expectNoErrors(filename: dillIncFile.path); | |
| expect(dillIncFile.existsSync(), equals(true)); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| test('unsafe-package-serialization', () async { | |
| // Package A. | |
| File file = new File('${tempDir.path}/pkgA/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync("pkgA() {}"); | |
| // Package B. | |
| file = new File('${tempDir.path}/pkgB/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync("pkgB_a() {}"); | |
| file = new File('${tempDir.path}/pkgB/b.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| "import 'package:pkgA/a.dart';" | |
| "pkgB_b() { pkgA(); }", | |
| ); | |
| // Application. | |
| new File('${tempDir.path}/app/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "pkgA", | |
| "rootUri": "${tempDir.uri.resolve('pkgA')}", | |
| "packageUri": "./" | |
| }, | |
| { | |
| "name": "pkgB", | |
| "rootUri": "${tempDir.uri.resolve('pkgB')}", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| // Entry point A uses both package A and B. | |
| file = new File('${tempDir.path}/app/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| "import 'package:pkgB/b.dart';" | |
| "import 'package:pkgB/a.dart';" | |
| "appA() { pkgB_a(); pkgB_b(); }", | |
| ); | |
| // Entry point B uses only package B. | |
| File fileB = new File('${tempDir.path}/app/B.dart') | |
| ..createSync(recursive: true); | |
| fileB.writeAsStringSync( | |
| "import 'package:pkgB/a.dart';" | |
| "appB() { pkgB_a(); }", | |
| ); | |
| // Other setup. | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| // First compile app entry point A. | |
| final String targetName = 'vm'; | |
| final Target target = createFrontEndTarget(targetName)!; | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--unsafe-package-serialization', | |
| '--no-incremental-serialization', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| switch (count) { | |
| case 0: | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| count += 1; | |
| frontendServer.accept(); | |
| frontendServer.reset(); | |
| frontendServer.recompile(fileB.uri, entryPoint: fileB.path); | |
| break; | |
| case 1: | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| // Loadable. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| // Contains (at least) the 2 files we want. | |
| expect( | |
| component.libraries | |
| .where( | |
| (l) => | |
| l.importUri.toString() == "package:pkgB/a.dart" || | |
| l.fileUri == fileB.uri, | |
| ) | |
| .length, | |
| 2, | |
| ); | |
| // Verifiable (together with the platform file). | |
| component = loadComponentFromBinary( | |
| platformKernel.toFilePath(), | |
| component, | |
| ); | |
| verifyComponent( | |
| target, | |
| VerificationStage.afterModularTransformations, | |
| component, | |
| ); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| test('incremental-serialization', () async { | |
| // Package A. | |
| File file = new File('${tempDir.path}/pkgA/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync("pkgA() {}"); | |
| // Package B. | |
| file = new File('${tempDir.path}/pkgB/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync("pkgB_a() {}"); | |
| file = new File('${tempDir.path}/pkgB/b.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| "import 'package:pkgA/a.dart';" | |
| "pkgB_b() { pkgA(); }", | |
| ); | |
| // Application. | |
| new File('${tempDir.path}/app/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "pkgA", | |
| "rootUri": "${tempDir.uri.resolve('pkgA')}", | |
| "packageUri": "./" | |
| }, | |
| { | |
| "name": "pkgB", | |
| "rootUri": "${tempDir.uri.resolve('pkgB')}", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| file = new File('${tempDir.path}/app/.dart_tool/package_config.json') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| jsonEncode({ | |
| "configVersion": 2, | |
| "packages": [ | |
| {"name": "pkgA", "rootUri": "../../pkgA"}, | |
| {"name": "pkgB", "rootUri": "../../pkgB"}, | |
| ], | |
| }), | |
| ); | |
| // Entry point A uses both package A and B. | |
| file = new File('${tempDir.path}/app/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| "import 'package:pkgB/b.dart';" | |
| "import 'package:pkgB/a.dart';" | |
| "appA() { pkgB_a(); pkgB_b(); }", | |
| ); | |
| // Entry point B uses only package B. | |
| File fileB = new File('${tempDir.path}/app/B.dart') | |
| ..createSync(recursive: true); | |
| fileB.writeAsStringSync( | |
| "import 'package:pkgB/a.dart';" | |
| "appB() { pkgB_a(); }", | |
| ); | |
| // Other setup. | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| // First compile app entry point A. | |
| final String targetName = 'vm'; | |
| final Target target = createFrontEndTarget(targetName)!; | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--incremental-serialization', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| switch (count) { | |
| case 0: | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| count += 1; | |
| frontendServer.accept(); | |
| frontendServer.reset(); | |
| frontendServer.recompile(fileB.uri, entryPoint: fileB.path); | |
| break; | |
| case 1: | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| // Loadable. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| // Contains (at least) the 2 files we want. | |
| expect( | |
| component.libraries | |
| .where( | |
| (l) => | |
| l.importUri.toString() == "package:pkgB/a.dart" || | |
| l.fileUri == fileB.uri, | |
| ) | |
| .length, | |
| 2, | |
| ); | |
| // Verifiable (together with the platform file). | |
| component = loadComponentFromBinary( | |
| platformKernel.toFilePath(), | |
| component, | |
| ); | |
| verifyComponent( | |
| target, | |
| VerificationStage.afterModularTransformations, | |
| component, | |
| ); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| test('incremental-serialization with reject', () async { | |
| // Basically a reproduction of | |
| // https://github.com/flutter/flutter/issues/44384. | |
| File file = new File( | |
| '${tempDir.path}/pkgA/.dart_tool/package_config.json', | |
| )..createSync(recursive: true); | |
| file.writeAsStringSync( | |
| jsonEncode({ | |
| "configVersion": 2, | |
| "packages": [ | |
| {"name": "pkgA", "rootUri": ".."}, | |
| ], | |
| }), | |
| ); | |
| file = new File('${tempDir.path}/pkgA/a.dart') | |
| ..createSync(recursive: true); | |
| file.writeAsStringSync("pkgA() {}"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--incremental-serialization', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| switch (count) { | |
| case 0: | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| // Loadable. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| // Contain the file we want. | |
| Iterable<Library> libs = component.libraries.where( | |
| (l) => l.importUri.toString() == "package:pkgA/a.dart", | |
| ); | |
| expect(libs.length, 1); | |
| // Has 1 procedure. | |
| expect(libs.first.procedures.length, 1); | |
| file.writeAsStringSync("pkgA() {} pkgA_2() {}"); | |
| count += 1; | |
| frontendServer.reject(); | |
| break; | |
| case 1: | |
| count += 1; | |
| frontendServer.reset(); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| break; | |
| case 2: | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| // Loadable. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| // Contain the file we want. | |
| Iterable<Library> libs = component.libraries.where( | |
| (l) => l.importUri.toString() == "package:pkgA/a.dart", | |
| ); | |
| expect(libs.length, 1); | |
| // Has 2 procedure. | |
| expect(libs.first.procedures.length, 2); | |
| file.writeAsStringSync("pkgA() {} pkgA_2() {} pkgA_3() {}"); | |
| count += 1; | |
| frontendServer.accept(); | |
| frontendServer.reset(); | |
| frontendServer.recompile(file.uri, entryPoint: file.path); | |
| break; | |
| case 3: | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| // Loadable. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| // Contain the file we want. | |
| Iterable<Library> libs = component.libraries.where( | |
| (l) => l.importUri.toString() == "package:pkgA/a.dart", | |
| ); | |
| expect(libs.length, 1); | |
| // Has 3 procedures. | |
| expect(libs.first.procedures.length, 3); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| test('compile and recompile report non-zero error count', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() { foo(); bar(); }\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(file.uri.toString()); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| switch (count) { | |
| case 0: | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 2); | |
| count += 1; | |
| frontendServer.accept(); | |
| File file2 = new File('${tempDir.path}/bar.dart')..createSync(); | |
| file2.writeAsStringSync("main() { baz(); }\n"); | |
| frontendServer.recompile( | |
| file2.uri, | |
| entryPoint: file2.uri.toString(), | |
| ); | |
| break; | |
| case 1: | |
| File dillIncFile = new File('${dillFile.path}.incremental.dill'); | |
| expect(result.filename, dillIncFile.path); | |
| expect(result.errorsCount, 1); | |
| count += 1; | |
| frontendServer.accept(); | |
| File file2 = new File('${tempDir.path}/bar.dart')..createSync(); | |
| file2.writeAsStringSync("main() { }\n"); | |
| frontendServer.recompile( | |
| file2.uri, | |
| entryPoint: file2.uri.toString(), | |
| ); | |
| break; | |
| case 2: | |
| File dillIncFile = new File('${dillFile.path}.incremental.dill'); | |
| expect(result.filename, dillIncFile.path); | |
| expect(result.errorsCount, 0); | |
| expect(dillIncFile.existsSync(), equals(true)); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| test('compile and recompile with MultiRootFileSystem', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {}\n"); | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync() | |
| ..writeAsStringSync('{"configVersion": 2, "packages": []}'); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=test-scheme:///.dart_tool/package_config.json', | |
| '--filesystem-root=${tempDir.path}', | |
| '--filesystem-scheme=test-scheme', | |
| 'test-scheme:///foo.dart', | |
| ]; | |
| expect(await starter(args), 0); | |
| }); | |
| test('compile multiple sources', () async { | |
| final File src1 = new File('${tempDir.path}/src1.dart') | |
| ..createSync() | |
| ..writeAsStringSync("main() {}\n"); | |
| final File src2 = new File('${tempDir.path}/src2.dart') | |
| ..createSync() | |
| ..writeAsStringSync("entryPoint2() {}\n"); | |
| final File src3 = new File('${tempDir.path}/src3.dart') | |
| ..createSync() | |
| ..writeAsStringSync("entryPoint3() {}\n"); | |
| final File packagesFile = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync() | |
| ..writeAsStringSync('{"configVersion": 2, "packages": []}'); | |
| final File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--packages=${packagesFile.path}', | |
| '--source=${src2.path}', | |
| '--source=${src3.path}', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(src1.uri.toString()); | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| expect(result.errorsCount, 0); | |
| final Component component = loadComponentFromBinary(dillFile.path); | |
| // Contains (at least) the 3 files we want. | |
| final Set<Uri> srcUris = {src1.uri, src2.uri, src3.uri}; | |
| expect( | |
| component.libraries | |
| .where((lib) => srcUris.contains(lib.fileUri)) | |
| .length, | |
| srcUris.length, | |
| ); | |
| frontendServer.quit(); | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| }); | |
| group('http uris', () { | |
| String host = 'localhost'; | |
| late File dillFile; | |
| late int port; | |
| late HttpServer server; | |
| setUp(() async { | |
| dillFile = new File('${tempDir.path}/app.dill'); | |
| server = await HttpServer.bind(host, 0); | |
| port = server.port; | |
| server.listen((request) { | |
| String path = request.uri.path; | |
| File file = new File('${tempDir.path}$path'); | |
| HttpResponse response = request.response; | |
| if (!file.existsSync()) { | |
| response.statusCode = 404; | |
| } else { | |
| response.statusCode = 200; | |
| response.add(file.readAsBytesSync()); | |
| } | |
| response.close(); | |
| }); | |
| File main = new File('${tempDir.path}/foo.dart')..createSync(); | |
| main.writeAsStringSync( | |
| "import 'package:foo/foo.dart'; main() {print(foo);}\n", | |
| ); | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "foo", | |
| "rootUri": "http://$host:$port/packages/foo", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| new File('${tempDir.path}/packages/foo/foo.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("var foo = 'hello';"); | |
| }); | |
| tearDown(() async { | |
| await server.close(); | |
| print('closed'); | |
| }); | |
| test('compile with http uris', () async { | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--enable-http-uris', | |
| '--packages=http://$host:$port/.dart_tool/package_config.json', | |
| 'http://$host:$port/foo.dart', | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| }); | |
| test('compile with an http file system root', () async { | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--enable-http-uris', | |
| '--packages=test-app:///.dart_tool/package_config.json', | |
| '--filesystem-root=http://$host:$port/', | |
| '--filesystem-scheme=test-app', | |
| 'test-app:///foo.dart', | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(dillFile.existsSync(), equals(true)); | |
| }); | |
| }); | |
| group('compile to JavaScript', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| file.path, | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(dillFile.existsSync(), true); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('--import-dill', () { | |
| /// Compiles `package:app` against `package:dep1` and `package:dep2`, | |
| /// which are available only as outline dills because their sources are | |
| /// deleted before the frontend server starts. Compiling at all | |
| /// therefore proves that the dills were used in their place. | |
| /// | |
| /// The first compile is followed by [recompiles] deltas, and is | |
| /// accepted first if [accept] is set. | |
| Future<void> runTests({ | |
| bool dep1DillHoldsPlatform = false, | |
| List<String> extraArgs = const <String>[], | |
| bool accept = false, | |
| int recompiles = 0, | |
| }) async { | |
| String mainWith(String revision) => | |
| ''' | |
| import 'package:app/greet.dart'; | |
| import 'package:dep1/dep1.dart'; | |
| import 'package:dep2/dep2.dart'; | |
| void main() { | |
| print(greet(dep1Greet('$revision'))); | |
| print(dep2Answer()); | |
| } | |
| '''; | |
| new File('${tempDir.path}/dep1/lib/dep1.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| "String dep1Greet(String who) => 'hello ' + who;", | |
| ); | |
| new File('${tempDir.path}/dep2/lib/dep2.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync('int dep2Answer() => 42;'); | |
| new File('${tempDir.path}/app/lib/greet.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync("String greet(String s) => '<' + s + '>';"); | |
| File mainFile = new File('${tempDir.path}/app/lib/main.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(mainWith('revision-0')); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| {'name': 'app', 'rootUri': '../app', 'packageUri': 'lib/'}, | |
| { | |
| 'name': 'dep1', | |
| 'rootUri': '../dep1', | |
| 'packageUri': 'lib/', | |
| }, | |
| { | |
| 'name': 'dep2', | |
| 'rootUri': '../dep2', | |
| 'packageUri': 'lib/', | |
| }, | |
| ], | |
| }), | |
| ); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| Uri dep1Dill = tempDir.uri.resolve('dep1.dill'); | |
| Uri dep2Dill = tempDir.uri.resolve('dep2.dill'); | |
| /// Compiles a DDC outline of [libraryUri] into [output], the way the | |
| /// bazel kernel worker does. | |
| /// | |
| /// With [excludeNonSources], which is how build systems invoke it, | |
| /// the outline holds [libraryUri] and nothing else. Without it the | |
| /// outline also holds the platform. | |
| Future<void> buildOutline( | |
| Uri output, | |
| String libraryUri, { | |
| bool excludeNonSources = true, | |
| }) async { | |
| StringBuffer messages = new StringBuffer(); | |
| ComputeKernelResult result = await computeKernel( | |
| <String>[ | |
| '--output=${output.toFilePath()}', | |
| '--packages-file=${packageConfig.path}', | |
| '--dart-sdk-summary=${ddcPlatformKernel.toFilePath()}', | |
| if (excludeNonSources) '--exclude-non-sources', | |
| '--summary-only', | |
| '--source=$libraryUri', | |
| ], | |
| isWorker: true, | |
| outputBuffer: messages, | |
| ); | |
| expect(result.succeeded, isTrue, reason: '$messages'); | |
| } | |
| await buildOutline( | |
| dep1Dill, | |
| 'package:dep1/dep1.dart', | |
| excludeNonSources: !dep1DillHoldsPlatform, | |
| ); | |
| await buildOutline(dep2Dill, 'package:dep2/dep2.dart'); | |
| expect( | |
| loadComponentFromBinary(dep1Dill.toFilePath()).libraries | |
| .map((Library library) => '${library.importUri}'), | |
| dep1DillHoldsPlatform | |
| ? contains('dart:core') | |
| : equals(<String>['package:dep1/dep1.dart']), | |
| ); | |
| new Directory('${tempDir.path}/dep1').deleteSync(recursive: true); | |
| new Directory('${tempDir.path}/dep2').deleteSync(recursive: true); | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> exitCode = frontendServer.open(<String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.toFilePath()}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=ddc', | |
| '--dartdevc-canary', | |
| '--import-dill=${dep1Dill.toFilePath()}', | |
| '--import-dill=${dep2Dill.toFilePath()}', | |
| ...extraArgs, | |
| ]); | |
| frontendServer.compile(mainFile.path); | |
| int compiles = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, 0, reason: 'compile $compiles'); | |
| Map<String, dynamic> manifest = jsonDecode( | |
| new File('${result.filename}.json').readAsStringSync(), | |
| ) as Map<String, dynamic>; | |
| String sources = new File('${result.filename}.sources') | |
| .readAsStringSync(); | |
| expect(sources, contains('revision-$compiles')); | |
| expect(manifest.keys, contains('/packages/app/main.dart.lib.js')); | |
| if (accept && compiles > 0) { | |
| // Only the changed library is in a delta that follows an | |
| // `accept`: not the prebuilt modules, and not the unchanged | |
| // `package:app/greet.dart` either. | |
| expect( | |
| manifest.keys, | |
| unorderedEquals(<String>['/packages/app/main.dart.lib.js']), | |
| ); | |
| } | |
| // Neither prebuilt package is emitted; the app imports both from | |
| // the JavaScript modules they are already compiled into. | |
| expect( | |
| manifest.keys.where( | |
| (String key) => key.startsWith('/packages/dep'), | |
| ), | |
| isEmpty, | |
| reason: 'compile $compiles', | |
| ); | |
| for (String library in const <String>[ | |
| 'package:dep1/dep1.dart', | |
| 'package:dep2/dep2.dart', | |
| ]) { | |
| expect( | |
| sources, | |
| contains('dartDevEmbedder.importLibrary("$library"'), | |
| reason: 'compile $compiles', | |
| ); | |
| expect( | |
| sources, | |
| isNot(contains('dartDevEmbedder.defineLibrary("$library"')), | |
| reason: 'compile $compiles', | |
| ); | |
| } | |
| if (compiles == recompiles) { | |
| frontendServer.quit(); | |
| return; | |
| } | |
| if (accept && compiles == 0) { | |
| frontendServer.accept(); | |
| } | |
| compiles += 1; | |
| mainFile.writeAsStringSync(mainWith('revision-$compiles')); | |
| frontendServer.recompile(mainFile.uri); | |
| }); | |
| expect(await exitCode, 0); | |
| frontendServer.close(); | |
| expect(compiles, recompiles); | |
| } | |
| test('compile to JavaScript against prebuilt modules', () async { | |
| await runTests(); | |
| }, timeout: new Timeout.factor(8)); | |
| test('an accepted compile is followed by a delta', () async { | |
| await runTests(accept: true, recompiles: 1); | |
| }, timeout: new Timeout.factor(8)); | |
| test('several deltas without accept', () async { | |
| await runTests(recompiles: 2); | |
| }, timeout: new Timeout.factor(8)); | |
| test('a dill that also holds the platform is accepted', () async { | |
| await runTests(dep1DillHoldsPlatform: true); | |
| }, timeout: new Timeout.factor(8)); | |
| test( | |
| 'imported dills work without strongly connected components', | |
| () async { | |
| await runTests( | |
| extraArgs: <String>['--no-js-strongly-connected-components'], | |
| ); | |
| }, | |
| timeout: new Timeout.factor(8), | |
| ); | |
| /// Compiles `package:app` for the VM against `package:dep1`, which is | |
| /// available only as a dill because its sources are deleted before the | |
| /// frontend server runs. | |
| test('compile for the VM against a prebuilt dill', () async { | |
| new File('${tempDir.path}/dep1/lib/dep1.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| "String dep1Greet(String who) => 'hello ' + who;", | |
| ); | |
| File mainFile = new File('${tempDir.path}/app/lib/main.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| import 'package:dep1/dep1.dart'; | |
| void main() { | |
| print(dep1Greet('world')); | |
| } | |
| '''); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| jsonEncode({ | |
| 'configVersion': 2, | |
| 'packages': [ | |
| {'name': 'app', 'rootUri': '../app', 'packageUri': 'lib/'}, | |
| { | |
| 'name': 'dep1', | |
| 'rootUri': '../dep1', | |
| 'packageUri': 'lib/', | |
| }, | |
| ], | |
| }), | |
| ); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| Uri dep1Dill = tempDir.uri.resolve('dep1.dill'); | |
| StringBuffer messages = new StringBuffer(); | |
| ComputeKernelResult result = await computeKernel( | |
| <String>[ | |
| '--output=${dep1Dill.toFilePath()}', | |
| '--packages-file=${packageConfig.path}', | |
| '--dart-sdk-summary=${platformKernel.toFilePath()}', | |
| '--target=vm', | |
| '--no-summary-only', | |
| '--exclude-non-sources', | |
| '--source=package:dep1/dep1.dart', | |
| ], | |
| isWorker: true, | |
| outputBuffer: messages, | |
| ); | |
| expect(result.succeeded, isTrue, reason: '$messages'); | |
| expect( | |
| loadComponentFromBinary(dep1Dill.toFilePath()).libraries | |
| .map((Library library) => '${library.importUri}'), | |
| equals(<String>['package:dep1/dep1.dart']), | |
| ); | |
| new Directory('${tempDir.path}/dep1').deleteSync(recursive: true); | |
| expect( | |
| await starter(<String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| // Otherwise the serializer copies `package:dep1` into the output. | |
| '--no-incremental-serialization', | |
| '--platform=${platformKernel.toFilePath()}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--target=vm', | |
| '--import-dill=${dep1Dill.toFilePath()}', | |
| mainFile.path, | |
| ]), | |
| 0, | |
| ); | |
| // `package:dep1` was resolved from the dill, so it is not compiled | |
| // from source, and it is not in the output either. | |
| expect( | |
| loadComponentFromBinary(dillFile.path).libraries | |
| .map((Library library) => '${library.importUri}'), | |
| equals(<String>['package:app/main.dart']), | |
| ); | |
| }, timeout: new Timeout.factor(8)); | |
| }); | |
| group('compile to JavaScript with canary features enabled', () { | |
| Future<void> runTests({required String moduleFormat}) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourcesFile = new File('${tempDir.path}/app.dill.sources'); | |
| expect(dillFile.existsSync(), false); | |
| expect(sourcesFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| '--dartdevc-canary', | |
| file.path, | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(dillFile.existsSync(), true); | |
| expect(sourcesFile.existsSync(), true); | |
| String ddcFlags = utf8 | |
| .decode(sourcesFile.readAsBytesSync()) | |
| .split('\n') | |
| .singleWhere((l) => l.startsWith('// Flags: ')); | |
| expect(ddcFlags, contains('canary')); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format', () async { | |
| await runTests(moduleFormat: 'ddc'); | |
| }); | |
| }); | |
| group('compile to JavaScript with package scheme', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n}\n"); | |
| File packages = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| jsonEncode({ | |
| "configVersion": 2, | |
| "packages": [ | |
| {"name": "hello", "rootUri": "${tempDir.uri}"}, | |
| ], | |
| }), | |
| ); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packages.path}', | |
| 'package:hello/foo.dart', | |
| ]; | |
| expect(await starter(args), 0); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }, skip: 'https://github.com/dart-lang/sdk/issues/43959'); | |
| group('compile to JavaScript with no metadata', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| File metadataFile = new File('${dillFile.path}.metadata'); | |
| expect(dillFile.existsSync(), false); | |
| expect(sourceFile.existsSync(), false); | |
| expect(manifestFile.existsSync(), false); | |
| expect(sourceMapsFile.existsSync(), false); | |
| expect(metadataFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| count++; | |
| // Request to 'compile', which results in full JavaScript and no | |
| // metadata. | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(metadataFile.existsSync(), equals(false)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| }); | |
| expect(await result, 0); | |
| expect(count, 1); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('recompile to JavaScript without accepting the last delta', () { | |
| // Regression test for a crash that occurred when a 'recompile' request | |
| // arrived before the client had ever sent an 'accept' request: there is | |
| // no last known good component to compute the JavaScript delta against | |
| // in that case. | |
| // See https://github.com/flutter/flutter/issues/192227. | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync('main() {\n "<<v1>>";\n}\n'); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File incrementalSourceFile = new File( | |
| '${dillFile.path}.incremental.dill.sources', | |
| ); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| switch (count) { | |
| case 0: | |
| expect(result.errorsCount, equals(0)); | |
| expect(result.filename, dillFile.path); | |
| expect(sourceFile.readAsStringSync(), contains('<<v1>>')); | |
| file.writeAsStringSync('main() {\n "<<v2>>";\n}\n'); | |
| // Deliberately recompile *without* accepting the compilation | |
| // above first. | |
| frontendServer.recompile(file.uri, entryPoint: library); | |
| break; | |
| case 1: | |
| expect(result.errorsCount, equals(0)); | |
| expect(result.filename, '${dillFile.path}.incremental.dill'); | |
| String source = incrementalSourceFile.readAsStringSync(); | |
| expect(source, contains('<<v2>>')); | |
| expect(source, not(contains('<<v1>>'))); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| break; | |
| default: | |
| break; | |
| } | |
| count++; | |
| }); | |
| expect(await result, 0); | |
| expect(count, 2); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('compile to JavaScript with metadata', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| File metadataFile = new File('${dillFile.path}.metadata'); | |
| File symbolsFile = new File('${dillFile.path}.symbols'); | |
| expect(dillFile.existsSync(), false); | |
| expect(sourceFile.existsSync(), false); | |
| expect(manifestFile.existsSync(), false); | |
| expect(sourceMapsFile.existsSync(), false); | |
| expect(metadataFile.existsSync(), false); | |
| expect(symbolsFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| '--experimental-emit-debug-metadata', | |
| '--emit-debug-symbols', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| count++; | |
| // Request to 'compile', which results in full JavaScript and | |
| // metadata. | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(metadataFile.existsSync(), equals(true)); | |
| expect(symbolsFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| }); | |
| expect(await result, 0); | |
| expect(count, 1); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| // This test exercises what happens when a change occurs with a single | |
| // library bundle of a multi-bundle compilation. | |
| group('recompile to JavaScript with in-body change', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| bool recompileRestart = false, | |
| }) async { | |
| // Five libraries, a to e, in two library bundles, {a, b} and {c, d, e}: | |
| // (a <-> b) -> (c <-> d <-> e) | |
| // In body changes are performed on d and e. With advanced invalidation, | |
| // not currently enabled, only the library bundle {c, d, e} will be | |
| // recompiled. | |
| new File('${tempDir.path}/a.dart') | |
| ..createSync() | |
| ..writeAsStringSync(""" | |
| import 'b.dart'; | |
| main() { | |
| b(); | |
| } | |
| a() => "<<a>>"; | |
| """); | |
| new File('${tempDir.path}/b.dart') | |
| ..createSync() | |
| ..writeAsStringSync(""" | |
| import 'a.dart'; | |
| import 'c.dart'; | |
| b() { | |
| a(); | |
| "<<b>>"; | |
| c(); | |
| } | |
| """); | |
| new File('${tempDir.path}/c.dart') | |
| ..createSync() | |
| ..writeAsStringSync(""" | |
| import 'd.dart'; | |
| c() { | |
| "<<c>>"; | |
| d(); | |
| } | |
| """); | |
| File fileD = new File('${tempDir.path}/d.dart') | |
| ..createSync() | |
| ..writeAsStringSync(""" | |
| import 'e.dart'; | |
| d() { | |
| "<<d>>"; | |
| e(); | |
| } | |
| """); | |
| File fileE = new File('${tempDir.path}/e.dart') | |
| ..createSync() | |
| ..writeAsStringSync(""" | |
| import 'c.dart'; | |
| e() { | |
| c(); | |
| "<<e>>"; | |
| } | |
| """); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "a", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String entryPoint = 'package:a/a.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| File metadataFile = new File('${dillFile.path}.metadata'); | |
| File symbolsFile = new File('${dillFile.path}.symbols'); | |
| expect(dillFile.existsSync(), false); | |
| expect(sourceFile.existsSync(), false); | |
| expect(manifestFile.existsSync(), false); | |
| expect(sourceMapsFile.existsSync(), false); | |
| expect(metadataFile.existsSync(), false); | |
| expect(symbolsFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| '--emit-debug-symbols', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(entryPoint); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| switch (count) { | |
| case 0: | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, equals(0)); | |
| expect(result.filename, dillFile.path); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| String source = sourceFile.readAsStringSync(); | |
| // Split on the comment at the end of each library bundle. | |
| List<String> jsLibraryBundles = source.split( | |
| new RegExp("//# sourceMappingURL=.*.map"), | |
| ); | |
| expect(jsLibraryBundles[0], contains('<<a>>')); | |
| expect(jsLibraryBundles[0], contains('<<b>>')); | |
| expect(jsLibraryBundles[0], not(contains('<<c>>'))); | |
| expect(jsLibraryBundles[0], not(contains('<<d>>'))); | |
| expect(jsLibraryBundles[0], not(contains('<<e>>'))); | |
| expect(jsLibraryBundles[1], not(contains('<<a>>'))); | |
| expect(jsLibraryBundles[1], not(contains('<<b>>'))); | |
| expect(jsLibraryBundles[1], contains('<<c>>')); | |
| expect(jsLibraryBundles[1], contains('<<d>>')); | |
| expect(jsLibraryBundles[1], contains('<<e>>')); | |
| frontendServer.accept(); | |
| fileD.writeAsStringSync(""" | |
| import 'e.dart'; | |
| d() { | |
| "<<d1>>"; | |
| "<<d2>>"; | |
| e(); | |
| } | |
| """); | |
| // Trigger a recompile that invalidates 'd.dart'. The entry point | |
| // uri (a.dart) is passed explicitly. | |
| frontendServer.recompile( | |
| fileD.uri, | |
| entryPoint: entryPoint, | |
| recompileRestart: recompileRestart, | |
| ); | |
| break; | |
| case 1: | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, equals(0)); | |
| expect(result.filename, '${dillFile.path}.incremental.dill'); | |
| File incrementalSourceFile = new File( | |
| '${dillFile.path}.incremental.dill.sources', | |
| ); | |
| expect(incrementalSourceFile.existsSync(), equals(true)); | |
| String source = incrementalSourceFile.readAsStringSync(); | |
| // Split on the comment at the end of each library bundle. | |
| List<String> jsLibraryBundles = source.split( | |
| new RegExp("//# sourceMappingURL=.*.map"), | |
| ); | |
| expect(jsLibraryBundles[0], not(contains('<<a>>'))); | |
| expect(jsLibraryBundles[0], not(contains('<<b>>'))); | |
| expect(jsLibraryBundles[0], contains('<<c>>')); | |
| expect(jsLibraryBundles[0], not(contains('<<d>>'))); | |
| expect(jsLibraryBundles[0], contains('<<d1>>')); | |
| expect(jsLibraryBundles[0], contains('<<d2>>')); | |
| expect(jsLibraryBundles[0], contains('<<e>>')); | |
| frontendServer.accept(); | |
| fileE.writeAsStringSync(""" | |
| import 'c.dart'; | |
| e() { | |
| c(); | |
| "<<e1>>"; | |
| "<<e2>>"; | |
| } | |
| """); | |
| // Trigger a recompile that invalidates 'd.dart'. The entry point | |
| // uri (a.dart) is omitted. | |
| frontendServer.recompile( | |
| fileE.uri, | |
| recompileRestart: recompileRestart, | |
| ); | |
| break; | |
| case 2: | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, equals(0)); | |
| expect(result.filename, '${dillFile.path}.incremental.dill'); | |
| File incrementalSourceFile = new File( | |
| '${dillFile.path}.incremental.dill.sources', | |
| ); | |
| expect(incrementalSourceFile.existsSync(), equals(true)); | |
| String source = incrementalSourceFile.readAsStringSync(); | |
| // Split on the comment at the end of each library bundle. | |
| List<String> jsLibraryBundles = source.split( | |
| new RegExp("//# sourceMappingURL=.*.map"), | |
| ); | |
| expect(jsLibraryBundles[0], not(contains('<<a>>'))); | |
| expect(jsLibraryBundles[0], not(contains('<<b>>'))); | |
| expect(jsLibraryBundles[0], contains('<<c>>')); | |
| expect(jsLibraryBundles[0], not(contains('<<d>>'))); | |
| expect(jsLibraryBundles[0], contains('<<d1>>')); | |
| expect(jsLibraryBundles[0], contains('<<d2>>')); | |
| expect(jsLibraryBundles[0], not(contains('<<e>>'))); | |
| expect(jsLibraryBundles[0], contains('<<e1>>')); | |
| expect(jsLibraryBundles[0], contains('<<e2>>')); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| break; | |
| default: | |
| break; | |
| } | |
| count++; | |
| }); | |
| expect(await result, 0); | |
| expect(count, 3); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| test('DDC module format and canary and recompile-restart', () async { | |
| // Test that `recompile-restart` makes no difference for the given | |
| // inputs. | |
| await runTests( | |
| moduleFormat: 'ddc', | |
| canary: true, | |
| recompileRestart: true, | |
| ); | |
| }); | |
| group('valid change that is invalid for hot reload fails with recompile, ' | |
| 'but not recompile-restart', () { | |
| Future<void> runTests({bool recompileRestart = false}) async { | |
| // Making a const class non-const is not valid for a hot reload. | |
| File fileA = new File('${tempDir.path}/a.dart') | |
| ..createSync() | |
| ..writeAsStringSync(''' | |
| class A { | |
| const A(); | |
| } | |
| main() {} | |
| '''); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "a", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String entryPoint = 'package:a/a.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| // Errors are only emitted with the DDC library bundle format. | |
| // TODO(nshahan): Remove these two flags when library bundle format | |
| // is the default without passing --dartdevc-canary. | |
| '--dartdevc-module-format=ddc', | |
| '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| '--emit-debug-symbols', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(entryPoint); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| switch (count) { | |
| case 0: | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, equals(0)); | |
| frontendServer.accept(); | |
| fileA.writeAsStringSync(''' | |
| class A { | |
| A(); | |
| } | |
| main() {} | |
| '''); | |
| frontendServer.recompile( | |
| fileA.uri, | |
| entryPoint: entryPoint, | |
| recompileRestart: recompileRestart, | |
| ); | |
| break; | |
| case 1: | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, equals(recompileRestart ? 0 : 1)); | |
| if (!recompileRestart) { | |
| expect( | |
| compiledResult.output.join('\n'), | |
| allOf( | |
| contains('Const class cannot become non-const'), | |
| contains('IncrementalJavaScriptBundler.invalidate'), | |
| contains('FrontendCompiler.writeJavaScriptBundle'), | |
| ), | |
| ); | |
| } | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| break; | |
| default: | |
| break; | |
| } | |
| count++; | |
| }); | |
| expect(await result, 0); | |
| expect(count, 2); | |
| frontendServer.close(); | |
| } | |
| test('recompile', () async { | |
| await runTests(recompileRestart: false); | |
| }); | |
| test('recompile-restart', () async { | |
| await runTests(recompileRestart: true); | |
| }); | |
| }); | |
| }); | |
| group( | |
| 'compile to JavaScript, all library bundles with sound null safety', | |
| () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync( | |
| "import 'bar.dart'; typedef myType = void Function(int); " | |
| "main() { fn is myType; }\n", | |
| ); | |
| file = new File('${tempDir.path}/bar.dart')..createSync(); | |
| file.writeAsStringSync("void Function(int) fn = (int i) => null;\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| Completer<bool> expectationCompleter = new Completer<bool>(); | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| count++; | |
| // Request to 'compile', which results in full JavaScript and no | |
| // metadata. | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| String source = sourceFile.readAsStringSync().trim(); | |
| // Split on the comment at the end of each library bundle. | |
| List<String> jsLibraryBundles = source.split( | |
| new RegExp("//# sourceMappingURL=.*.map"), | |
| ); | |
| // There should be two library bundles present. | |
| expect(jsLibraryBundles.length, equals(3)); | |
| expect(jsLibraryBundles.last, isEmpty); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| expectationCompleter.complete(true); | |
| }); | |
| await expectationCompleter.future; | |
| expect(await result, 0); | |
| expect(count, 1); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }, | |
| ); | |
| group('sourcemaps with cross package sources do not reference `lib/` ' | |
| 'directories', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| // Create two packages in a dependency cycle. | |
| final String packagesDir = '${tempDir.path}/packages'; | |
| final File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "a", | |
| "rootUri": "$packagesDir/a", | |
| "packageUri": "lib/" | |
| }, | |
| { | |
| "name": "b", | |
| "rootUri": "$packagesDir/b", | |
| "packageUri": "lib/" | |
| } | |
| ] | |
| } | |
| '''); | |
| new File('$packagesDir/a/lib/a.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| "import 'package:b/b.dart';\n" | |
| "String a1() => 'a1';\n" | |
| "String a2() => 'a2' + b2();\n", | |
| ); | |
| new File('$packagesDir/b/lib/b.dart') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| "import 'package:a/a.dart';\n" | |
| "String b1() => 'b1' + a1();\n" | |
| "String b2() => 'b2';\n", | |
| ); | |
| // Compile the cycle as a single output. | |
| final String library = 'package:a/a.dart'; | |
| final File dillFile = new File('${tempDir.path}/a.dill'); | |
| final File manifestFile = new File('${dillFile.path}.json'); | |
| final File sourceMapsFile = new File('${dillFile.path}.map'); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| final FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| final Completer<bool> expectationCompleter = new Completer<bool>(); | |
| frontendServer.listen((Result compiledResult) { | |
| final CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| expect(result.errorsCount, 0); | |
| expect(result.filename, dillFile.path); | |
| expect(manifestFile.existsSync(), true); | |
| // Extract the sourcemap. | |
| final Map<String, dynamic> manifest = json.decode( | |
| utf8.decode(manifestFile.readAsBytesSync()), | |
| ); | |
| final List<dynamic> offsets = | |
| manifest['/packages/a/a.dart.lib.js']['sourcemap']; | |
| final Map<String, dynamic> sourcemap = json.decode( | |
| utf8.decode( | |
| sourceMapsFile.readAsBytesSync().sublist( | |
| offsets.first, | |
| offsets.last, | |
| ), | |
| ), | |
| ); | |
| // Verify source maps are pointing at correct source files. | |
| // | |
| // There is a built in assumption that the original sources for all | |
| // packages are served in a structure similar to the definition from | |
| // the package_config.json file but without the `lib/` directories. | |
| final List<dynamic> sources = sourcemap['sources']; | |
| expect(sources.length, 2); | |
| expect(sources, containsAll(['a.dart', '../b/b.dart'])); | |
| frontendServer.accept(); | |
| frontendServer.quit(); | |
| expectationCompleter.complete(true); | |
| }); | |
| await expectationCompleter.future; | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('changes to mixin body invalidate libraries that apply the ' | |
| 'mixin', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| new File('${tempDir.path}/main.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "import 'class2.dart';" | |
| "import 'helper.dart';" | |
| "final h = new Helper();" | |
| "final c = new C2();" | |
| "main() {" | |
| " print(c.fn1());" | |
| " print(c.gn1());" | |
| " print(c.fn2());" | |
| " print(c.gn2());" | |
| " print(h.x());" | |
| "}", | |
| ); | |
| new File('${tempDir.path}/helper.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "class Helper { String x() { return 'Helper'; } }", | |
| ); | |
| new File('${tempDir.path}/class2.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "import 'mixin2.dart';\n" | |
| "import 'class1.dart';\n" | |
| "class C2 extends C1 with M2 {}\n", | |
| ); | |
| new File('${tempDir.path}/class1.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "import 'mixin1.dart';\n" | |
| "class C1 with M1 {}\n", | |
| ); | |
| new File('${tempDir.path}/mixin1.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "mixin M1 {\n" | |
| " String fn1() { return 'hello'; }\n" | |
| " String gn1() => 'hello';\n" | |
| "}\n", | |
| ); | |
| new File('${tempDir.path}/mixin2.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "mixin M2 {\n" | |
| " String fn2() { return 'hello'; }\n" | |
| " String gn2() => 'hello';\n" | |
| "}\n", | |
| ); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync( | |
| '{\n' | |
| ' "configVersion": 2,\n' | |
| ' "packages": [\n' | |
| ' {\n' | |
| ' "name": "hello",\n' | |
| ' "rootUri": "../",\n' | |
| ' "packageUri": "./"\n' | |
| ' }\n' | |
| ' ]\n' | |
| '}\n', | |
| ); | |
| final String entrypoint = 'package:hello/main.dart'; | |
| final File dillFile = new File('${tempDir.path}/out.dill'); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| final FrontendServer frontendServer = new FrontendServer(); | |
| final Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(entrypoint); | |
| int count = 0; | |
| final Completer<bool> expectationCompleter = new Completer<bool>(); | |
| frontendServer.listen((Result compiledResult) { | |
| if (count == 0) { | |
| compiledResult.expectNoErrors(filename: dillFile.path); | |
| // Should find all files in the output for the initial compile. | |
| final File manifestFile = new File('${dillFile.path}.json'); | |
| expect(manifestFile.existsSync(), true); | |
| frontendServer.accept(); | |
| count++; | |
| final Map<String, dynamic> manifest = json.decode( | |
| utf8.decode(manifestFile.readAsBytesSync()), | |
| ); | |
| expect( | |
| manifest.keys, | |
| unorderedEquals([ | |
| '/packages/hello/main.dart.lib.js', | |
| '/packages/hello/helper.dart.lib.js', | |
| '/packages/hello/class1.dart.lib.js', | |
| '/packages/hello/mixin1.dart.lib.js', | |
| '/packages/hello/class2.dart.lib.js', | |
| '/packages/hello/mixin2.dart.lib.js', | |
| ]), | |
| ); | |
| // Modify the body expression of an arrow function in the M1 mixin. | |
| final File mixinFile = new File('${tempDir.path}/mixin1.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "mixin M1 {\n" | |
| " String fn1() { return 'hello'; }\n" | |
| " String gn1() => 'goodbye';\n" | |
| "}\n", | |
| ); | |
| frontendServer.recompile(mixinFile.uri, entryPoint: entrypoint); | |
| } else if (count == 1) { | |
| final File dillIncFile = new File( | |
| '${dillFile.path}.incremental.dill', | |
| ); | |
| compiledResult.expectNoErrors(filename: dillIncFile.path); | |
| frontendServer.accept(); | |
| count++; | |
| // Find four output files have been invalidated corresponding to the | |
| // library that contains the modified mixin and the reverse imports | |
| // back to the main entrypoint. | |
| // | |
| // This is a side effect of the arrow function body expressions | |
| // being present in the incremental compilers "textual outline" so | |
| // changes to the body will invalidate the "public" API. This is | |
| // more than DDC needs for correctness. | |
| final File manifestFile = new File('${dillIncFile.path}.json'); | |
| final Map<String, dynamic> manifest = json.decode( | |
| utf8.decode(manifestFile.readAsBytesSync()), | |
| ); | |
| expect( | |
| manifest.keys, | |
| unorderedEquals([ | |
| '/packages/hello/main.dart.lib.js', | |
| '/packages/hello/class2.dart.lib.js', | |
| '/packages/hello/class1.dart.lib.js', | |
| '/packages/hello/mixin1.dart.lib.js', | |
| ]), | |
| ); | |
| // Modify an expression in the block body of a method in the mixin | |
| // M1. | |
| final File mixinFile = new File('${tempDir.path}/mixin1.dart') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| "mixin M1 {\n" | |
| " String fn1() { return 'goodbye'; }\n" | |
| " String gn1() => 'goodbye';\n" | |
| "}\n", | |
| ); | |
| frontendServer.recompile(mixinFile.uri, entryPoint: entrypoint); | |
| } else if (count == 2) { | |
| final File dillIncFile = new File( | |
| '${dillFile.path}.incremental.dill', | |
| ); | |
| compiledResult.expectNoErrors(filename: dillIncFile.path); | |
| frontendServer.accept(); | |
| count++; | |
| // Find two output files have been invalidated corresponding to the | |
| // library that contains the modified mixin and the library contains | |
| // an application of the modified mixin. | |
| // | |
| // DDC needs to reevaluate the library with the mixin application to | |
| // ensure the mixin method body is applied. | |
| final File manifestFile = new File('${dillIncFile.path}.json'); | |
| final Map<String, dynamic> manifest = json.decode( | |
| utf8.decode(manifestFile.readAsBytesSync()), | |
| ); | |
| expect( | |
| manifest.keys, | |
| unorderedEquals([ | |
| '/packages/hello/class1.dart.lib.js', | |
| '/packages/hello/mixin1.dart.lib.js', | |
| ]), | |
| ); | |
| frontendServer.quit(); | |
| expectationCompleter.complete(true); | |
| } | |
| }); | |
| expect(await result, 0); | |
| await expectationCompleter.future; | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('compile expression to JavaScript', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/foo.dart.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full JavaScript | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: '', | |
| libraryUri: library, | |
| line: 2, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| // Second request is to 'compile-expression-to-js' that fails | |
| // due to incorrect input - empty expression | |
| expect(result.errorsCount, 1); | |
| expect(compiledResult.status, (String status) { | |
| return status.endsWith(' 1'); | |
| }); | |
| frontendServer.compileExpressionToJs( | |
| expression: '2+2', | |
| libraryUri: library, | |
| line: 2, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| // Third request is to 'compile-expression-to-js', which results in | |
| // js file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compile('foo.bar'); | |
| count += 1; | |
| } else { | |
| expect(count, 3); | |
| // Fourth request is to 'compile' nonexistent file, that should | |
| // fail. | |
| expect(result.errorsCount, greaterThan(0)); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 3); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('compiled JavaScript includes web library environment defines', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync(''' | |
| main() { | |
| print(const bool.fromEnvironment('dart.library.html')); | |
| }\n'''); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/foo.dart.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // Request to 'compile', which results in full JavaScript. | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| String compiledOutput = sourceFile.readAsStringSync(); | |
| // The constant environment variable should be inlined as a boolean | |
| // literal. | |
| expect(compiledOutput, contains('print(true);')); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: 'const bool.fromEnvironment("dart.library.html")', | |
| libraryUri: library, | |
| line: 2, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else { | |
| expect(count, 1); | |
| // Second request is to 'compile-expression-to-js' that should | |
| // result in a literal `true` . | |
| expect(result.errorsCount, 0); | |
| File resultFile = new File(result.filename); | |
| // The constant environment variable should be inlined as a boolean | |
| // literal. | |
| expect(resultFile.readAsStringSync(), contains('return true;')); | |
| count += 1; | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 2); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| group('mixed compile expression commands with web target', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {\n\n}\n"); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| String library = 'package:hello/foo.dart'; | |
| File dillFile = new File('${tempDir.path}/foo.dart.dill'); | |
| File sourceFile = new File('${dillFile.path}.sources'); | |
| File manifestFile = new File('${dillFile.path}.json'); | |
| File sourceMapsFile = new File('${dillFile.path}.map'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| '--packages=${packageConfig.path}', | |
| ]; | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(library); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full JavaScript | |
| expect(result.errorsCount, equals(0)); | |
| expect(sourceFile.existsSync(), equals(true)); | |
| expect(manifestFile.existsSync(), equals(true)); | |
| expect(sourceMapsFile.existsSync(), equals(true)); | |
| expect(result.filename, dillFile.path); | |
| frontendServer.accept(); | |
| frontendServer.compileExpressionToJs( | |
| expression: '2+2', | |
| libraryUri: library, | |
| line: 2, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 1) { | |
| expect(result.errorsCount, equals(0)); | |
| // Second request is to 'compile-expression-to-js', which results in | |
| // js file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compileExpression('2+2', file.uri, isStatic: false); | |
| count += 1; | |
| } else if (count == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| // Third request is to 'compile-expression', which results in | |
| // kernel file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.compileExpressionToJs( | |
| expression: '2+2', | |
| libraryUri: library, | |
| line: 2, | |
| column: 1, | |
| ); | |
| count += 1; | |
| } else if (count == 3) { | |
| expect(result.errorsCount, equals(0)); | |
| // Fourth request is to 'compile-expression-to-js', which results in | |
| // js file with a function that wraps compiled expression. | |
| File outputFile = new File(result.filename); | |
| expect(outputFile.existsSync(), equals(true)); | |
| expect(outputFile.lengthSync(), isPositive); | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| expect(count, 3); | |
| frontendServer.close(); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| test('compile "package:"-file', () async { | |
| Directory lib = new Directory('${tempDir.path}/lib')..createSync(); | |
| new File('${lib.path}/foo.dart') | |
| ..createSync() | |
| ..writeAsStringSync("main() {}\n"); | |
| File packages = new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync() | |
| ..writeAsStringSync( | |
| jsonEncode({ | |
| "configVersion": 2, | |
| "packages": [ | |
| {"name": "test", "rootUri": "../lib"}, | |
| ], | |
| }), | |
| ); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| File depFile = new File('${tempDir.path}/the depfile'); | |
| expect(depFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--depfile=${depFile.path}', | |
| '--packages=${packages.path}', | |
| 'package:test/foo.dart', | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(depFile.existsSync(), true); | |
| String depContents = depFile.readAsStringSync(); | |
| List<String> depContentsParsed = depContents.split(': '); | |
| expect(path.basename(depContentsParsed[0]), path.basename(dillFile.path)); | |
| expect(depContentsParsed[1], isNotEmpty); | |
| }); | |
| test('compile and produce deps file', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("main() {}\n"); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| File depFile = new File('${tempDir.path}/the depfile'); | |
| expect(depFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--depfile=${depFile.path}', | |
| file.path, | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(depFile.existsSync(), true); | |
| String depContents = depFile.readAsStringSync(); | |
| List<String> depContentsParsed = depContents.split(': '); | |
| expect(path.basename(depContentsParsed[0]), path.basename(dillFile.path)); | |
| expect(depContentsParsed[1], isNotEmpty); | |
| }); | |
| void checkIsEqual(List<int> a, List<int> b) { | |
| int length = a.length; | |
| if (b.length < length) { | |
| length = b.length; | |
| } | |
| for (int i = 0; i < length; ++i) { | |
| if (a[i] != b[i]) { | |
| fail("Data differs at byte ${i + 1}."); | |
| } | |
| } | |
| expect(a.length, equals(b.length)); | |
| } | |
| test('mimic flutter benchmark', () async { | |
| // This is based on what flutters "hot_mode_dev_cycle__benchmark" does. | |
| File dillFile = new File('${tempDir.path}/full.dill'); | |
| File incrementalDillFile = new File('${tempDir.path}/incremental.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final String targetName = 'vm'; | |
| final Target target = createFrontEndTarget(targetName)!; | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--output-incremental-dill=${incrementalDillFile.path}', | |
| ]; | |
| File dart2js = new File.fromUri( | |
| Platform.script.resolve("../../../pkg/compiler/lib/src/dart2js.dart"), | |
| ); | |
| expect(dart2js.existsSync(), equals(true)); | |
| File dart2jsOtherFile = new File.fromUri( | |
| Platform.script.resolve("../../../pkg/compiler/lib/src/compiler.dart"), | |
| ); | |
| expect(dart2jsOtherFile.existsSync(), equals(true)); | |
| int libraryCount = -1; | |
| int sourceCount = -1; | |
| List<List<int>> compiledKernels = <List<int>>[]; | |
| for (int serverCloses = 0; serverCloses < 2; ++serverCloses) { | |
| print("Restart #$serverCloses"); | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(dart2js.path); | |
| int count = 0; | |
| frontendServer.listen((Result compiledResult) { | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| String outputFilename = result.filename; | |
| print("$outputFilename -- count $count"); | |
| // Ensure that kernel file produced when compiler was initialized | |
| // from compiled kernel files matches kernel file produced when | |
| // compiler was initialized from sources on the first run. | |
| if (serverCloses == 0) { | |
| compiledKernels.add(new File(dillFile.path).readAsBytesSync()); | |
| } else { | |
| checkIsEqual( | |
| compiledKernels[count], | |
| new File(dillFile.path).readAsBytesSync(), | |
| ); | |
| } | |
| if (count == 0) { | |
| // First request is to 'compile', which results in full kernel file. | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(outputFilename, dillFile.path); | |
| // Dill file can be loaded and includes data. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| if (serverCloses == 0) { | |
| libraryCount = component.libraries.length; | |
| sourceCount = component.uriToSource.length; | |
| expect(libraryCount > 100, equals(true)); | |
| expect( | |
| sourceCount >= libraryCount, | |
| equals(true), | |
| reason: "Expects >= source entries than libraries.", | |
| ); | |
| } else { | |
| expect( | |
| component.libraries.length, | |
| equals(libraryCount), | |
| reason: | |
| "Expects the same number of libraries " | |
| "when compiling after a restart", | |
| ); | |
| expect( | |
| component.uriToSource.length, | |
| equals(sourceCount), | |
| reason: | |
| "Expects the same number of sources " | |
| "when compiling after a restart", | |
| ); | |
| } | |
| // Include platform and verify. | |
| component = loadComponentFromBinary( | |
| platformKernel.toFilePath(), | |
| component, | |
| ); | |
| expect(component.mainMethod, isNotNull); | |
| verifyComponent( | |
| target, | |
| VerificationStage.afterModularTransformations, | |
| component, | |
| ); | |
| count += 1; | |
| // Restart with no changes | |
| frontendServer.accept(); | |
| frontendServer.reset(); | |
| frontendServer.recompile( | |
| null, | |
| entryPoint: dart2js.path, | |
| boundaryKey: 'x$count', | |
| ); | |
| } else if (count == 1) { | |
| // Restart. Expect full kernel file. | |
| expect(dillFile.existsSync(), equals(true)); | |
| expect(outputFilename, dillFile.path); | |
| // Dill file can be loaded and includes data. | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| expect( | |
| component.libraries.length, | |
| equals(libraryCount), | |
| reason: "Expect the same number of libraries after a reset.", | |
| ); | |
| expect( | |
| component.uriToSource.length, | |
| equals(sourceCount), | |
| reason: "Expect the same number of sources after a reset.", | |
| ); | |
| // Include platform and verify. | |
| component = loadComponentFromBinary( | |
| platformKernel.toFilePath(), | |
| component, | |
| ); | |
| expect(component.mainMethod, isNotNull); | |
| verifyComponent( | |
| target, | |
| VerificationStage.afterModularTransformations, | |
| component, | |
| ); | |
| count += 1; | |
| // Reload with no changes | |
| frontendServer.accept(); | |
| frontendServer.recompile( | |
| null, | |
| entryPoint: dart2js.path, | |
| boundaryKey: 'x$count', | |
| ); | |
| } else if (count == 2) { | |
| // Partial file. Expect to be empty. | |
| expect(incrementalDillFile.existsSync(), equals(true)); | |
| expect(outputFilename, incrementalDillFile.path); | |
| // Dill file can be loaded and includes no data. | |
| Component component = loadComponentFromBinary( | |
| incrementalDillFile.path, | |
| ); | |
| expect(component.libraries.length, equals(0)); | |
| count += 1; | |
| // Reload with 1 change | |
| frontendServer.accept(); | |
| frontendServer.recompile( | |
| dart2jsOtherFile.uri, | |
| entryPoint: dart2js.path, | |
| boundaryKey: 'x$count', | |
| ); | |
| } else if (count == 3) { | |
| // Partial file. Expect to not be empty. | |
| expect(incrementalDillFile.existsSync(), equals(true)); | |
| expect(outputFilename, incrementalDillFile.path); | |
| // Dill file can be loaded and includes some data. | |
| Component component = loadComponentFromBinary( | |
| incrementalDillFile.path, | |
| ); | |
| expect(component.libraries, isNotEmpty); | |
| expect( | |
| component.uriToSource.length >= component.libraries.length, | |
| equals(true), | |
| reason: "Expects >= source entries than libraries.", | |
| ); | |
| count += 1; | |
| frontendServer.quit(); | |
| } | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| } | |
| }, timeout: new Timeout.factor(8)); | |
| test( | |
| 'invalidating part that is not included in the output library', | |
| () async { | |
| // https://github.com/dart-lang/test/issues/2731 | |
| // Previously, when initializing from a dill where a library claims a | |
| // part which doesn't point correctly back, the frontend_server would | |
| // not invalidate the part file if it had changed because the part was | |
| // not included in the uri-to-source table in kernel. | |
| // This verifies that we can now fix the compile error by making the | |
| // part file point correctly when initializing from a dill with the | |
| // error. | |
| File dillFile = new File('${tempDir.path}/full.dill'); | |
| File incrementalDillFile = new File('${tempDir.path}/incremental.dill'); | |
| expect(dillFile.existsSync(), equals(false)); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--output-incremental-dill=${incrementalDillFile.path}', | |
| ]; | |
| File main = new File('${tempDir.path}/main.dart'); | |
| main.writeAsStringSync(""" | |
| part "part.dart"; | |
| main() { | |
| fromPart(); | |
| } | |
| """); | |
| File part = new File('${tempDir.path}/part.dart'); | |
| part.writeAsStringSync(""" | |
| part of "main.dart"; | |
| fromPart() { | |
| print("hello from part"); | |
| } | |
| """); | |
| for (int serverCloses = 0; serverCloses <= 2; serverCloses++) { | |
| print("Restart #$serverCloses"); | |
| FrontendServer frontendServer = new FrontendServer(); | |
| Future<int> result = frontendServer.open(args); | |
| frontendServer.compile(main.path); | |
| frontendServer.listen((Result compiledResult) { | |
| expect(dillFile.existsSync(), equals(true)); | |
| CompilationResult result = new CompilationResult.parse( | |
| compiledResult.status, | |
| ); | |
| if (serverCloses == 0) { | |
| expect(result.errorsCount, equals(0)); | |
| part.writeAsStringSync(""" | |
| part of "nonexisting.dart"; | |
| fromPart() { | |
| print("hello from part"); | |
| } | |
| """); | |
| } else if (serverCloses == 1) { | |
| expect(result.errorsCount, isNot(0)); | |
| part.writeAsStringSync(""" | |
| part of "main.dart"; | |
| fromPart() { | |
| print("hello from part"); | |
| } | |
| """); | |
| } else if (serverCloses == 2) { | |
| expect(result.errorsCount, equals(0)); | |
| } | |
| frontendServer.quit(); | |
| }); | |
| expect(await result, 0); | |
| frontendServer.close(); | |
| } | |
| }, | |
| ); | |
| group('compile with(out) warning', () { | |
| Future<void> runTests({ | |
| required String moduleFormat, | |
| bool canary = false, | |
| }) async { | |
| Future runTest({bool hideWarnings = true}) async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync(""" | |
| import 'dart:js_interop'; | |
| main() {} | |
| @JSExport('Foo') | |
| class Class { | |
| void method() {} | |
| } | |
| """); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync(''' | |
| { | |
| "configVersion": 2, | |
| "packages": [ | |
| { | |
| "name": "hello", | |
| "rootUri": "../", | |
| "packageUri": "./" | |
| } | |
| ] | |
| } | |
| '''); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| expect(dillFile.existsSync(), false); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${ddcPlatformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--target=dartdevc', | |
| '--dartdevc-module-format=$moduleFormat', | |
| if (canary) '--dartdevc-canary', | |
| if (hideWarnings) '--verbosity=error', | |
| file.path, | |
| ]; | |
| StringBuffer output = new StringBuffer(); | |
| int exitCode = await starter(args, output: output); | |
| String result = output.toString(); | |
| expect(exitCode, 0); | |
| Matcher matcher = contains( | |
| "Warning: The value in the `@JSExport` annotation on the " | |
| "class or mixin 'Class' will be ignored.", | |
| ); | |
| if (hideWarnings) { | |
| matcher = isNot(matcher); | |
| } | |
| expect(result, matcher); | |
| file.deleteSync(); | |
| dillFile.deleteSync(); | |
| } | |
| await runTest(hideWarnings: false); | |
| await runTest(hideWarnings: true); | |
| } | |
| test('AMD module format', () async { | |
| await runTests(moduleFormat: 'amd'); | |
| }); | |
| test('DDC module format and canary', () async { | |
| await runTests(moduleFormat: 'ddc', canary: true); | |
| }); | |
| }); | |
| test('compile with dynamic-interface and incremental', () async { | |
| File file = new File('${tempDir.path}/foo.dart')..createSync(); | |
| file.writeAsStringSync("void main() {}\n"); | |
| File extraFile = new File('${tempDir.path}/bar.dart')..createSync(); | |
| extraFile.writeAsStringSync("void exposedHelper() {}\n"); | |
| File dynamicInterface = new File('${tempDir.path}/dynamic_interface.yaml') | |
| ..createSync(); | |
| dynamicInterface.writeAsStringSync(''' | |
| callable: | |
| - library: 'bar.dart' | |
| '''); | |
| File packageConfig = | |
| new File('${tempDir.path}/.dart_tool/package_config.json') | |
| ..createSync(recursive: true) | |
| ..writeAsStringSync('{"configVersion": 2, "packages": []}'); | |
| File dillFile = new File('${tempDir.path}/app.dill'); | |
| final List<String> args = <String>[ | |
| '--sdk-root=${sdkRoot.toFilePath()}', | |
| '--incremental', | |
| '--platform=${platformKernel.path}', | |
| '--output-dill=${dillFile.path}', | |
| '--packages=${packageConfig.path}', | |
| '--dynamic-interface=${dynamicInterface.path}', | |
| file.path, | |
| ]; | |
| expect(await starter(args), 0); | |
| expect(dillFile.existsSync(), true); | |
| Component component = loadComponentFromBinary(dillFile.path); | |
| expect( | |
| component.libraries.any((Library lib) => lib.fileUri == extraFile.uri), | |
| true, | |
| ); | |
| }); | |
| }); | |
| } | |
| /// Computes the location of platform binaries, that is, compiled `.dill` files | |
| /// of the platform libraries that are used to avoid recompiling those | |
| /// libraries. | |
| Uri computePlatformBinariesLocation() { | |
| // The directory of the Dart VM executable. | |
| Uri vmDirectory = Uri.base | |
| .resolveUri(new Uri.file(Platform.resolvedExecutable)) | |
| .resolve("."); | |
| if (vmDirectory.path.endsWith("/bin/")) { | |
| // Looks like the VM is in a `/bin/` directory, so this is running from a | |
| // built SDK. | |
| return vmDirectory.resolve("../lib/_internal/"); | |
| } else { | |
| // We assume this is running from a build directory (for example, | |
| // `out/ReleaseX64` or `xcodebuild/ReleaseX64`). | |
| return vmDirectory; | |
| } | |
| } | |
| class CompilationResult { | |
| late String filename; | |
| int errorsCount = 0; | |
| new parse(String? filenameAndErrorCount) { | |
| if (filenameAndErrorCount == null) { | |
| return; | |
| } | |
| int delim = filenameAndErrorCount.lastIndexOf(' '); | |
| expect(delim > 0, equals(true)); | |
| filename = filenameAndErrorCount.substring(0, delim); | |
| errorsCount = int.parse(filenameAndErrorCount.substring(delim + 1).trim()); | |
| } | |
| } | |
| class OutputParser { | |
| bool expectSources = true; | |
| final StreamController<Result> _receivedResults; | |
| List<String>? _receivedSources; | |
| List<String> _receivedOutput = <String>[]; | |
| String? _boundaryKey; | |
| bool _readingSources = false; | |
| new(this._receivedResults); | |
| void listener(String s) { | |
| if (_boundaryKey == null) { | |
| const String resultOutputSpace = 'result '; | |
| if (s.startsWith(resultOutputSpace)) { | |
| _boundaryKey = s.substring(resultOutputSpace.length); | |
| } | |
| _readingSources = false; | |
| _receivedSources?.clear(); | |
| _receivedOutput = <String>[]; | |
| return; | |
| } | |
| String bKey = _boundaryKey!; | |
| if (s.startsWith(bKey)) { | |
| // First boundaryKey separates compiler output from list of sources | |
| // (if we expect list of sources, which is indicated by receivedSources | |
| // being not null) | |
| if (expectSources && !_readingSources) { | |
| _readingSources = true; | |
| return; | |
| } | |
| // Second boundaryKey indicates end of frontend server response | |
| expectSources = true; | |
| _receivedResults.add( | |
| new Result( | |
| s.length > bKey.length ? s.substring(bKey.length + 1) : null, | |
| _receivedSources!, | |
| _receivedOutput, | |
| ), | |
| ); | |
| _boundaryKey = null; | |
| } else { | |
| if (_readingSources) { | |
| _receivedSources ??= <String>[]; | |
| _receivedSources!.add(s); | |
| } else { | |
| _receivedOutput.add(s); | |
| print("> $s"); | |
| } | |
| } | |
| } | |
| } | |
| class Result { | |
| String? status; | |
| List<String> sources; | |
| List<String> output; | |
| new(this.status, this.sources, [this.output = const <String>[]]); | |
| void expectNoErrors({String? filename}) { | |
| CompilationResult result = new CompilationResult.parse(status); | |
| expect(result.errorsCount, equals(0)); | |
| if (filename != null) { | |
| expect(result.filename, equals(filename)); | |
| } | |
| } | |
| } | |
| /// Creates a matcher for the negation of [matcher]. | |
| Matcher not(Matcher matcher) => new NotMatcher(matcher); | |
| class NotMatcher extends Matcher { | |
| final Matcher matcher; | |
| const new(this.matcher); | |
| @override | |
| Description describe(Description description) => | |
| matcher.describe(description.add('not ')); | |
| @override | |
| bool matches(item, Map matchState) { | |
| return !matcher.matches(item, matchState); | |
| } | |
| } | |
| /// Wrapper for the frontend server communication. | |
| class FrontendServer { | |
| final StreamController<List<int>> inputStreamController; | |
| final StreamController<List<int>> stdoutStreamController; | |
| final IOSink ioSink; | |
| final StreamController<Result> receivedResults; | |
| final OutputParser outputParser; | |
| factory() { | |
| final StreamController<List<int>> inputStreamController = | |
| new StreamController<List<int>>(); | |
| final StreamController<List<int>> stdoutStreamController = | |
| new StreamController<List<int>>(); | |
| final IOSink ioSink = new IOSink(stdoutStreamController.sink); | |
| StreamController<Result> receivedResults = new StreamController<Result>(); | |
| final OutputParser outputParser = new OutputParser(receivedResults); | |
| stdoutStreamController.stream | |
| .transform(utf8.decoder) | |
| .transform(const LineSplitter()) | |
| .listen(outputParser.listener); | |
| return new FrontendServer._internal( | |
| inputStreamController, | |
| stdoutStreamController, | |
| ioSink, | |
| receivedResults, | |
| outputParser, | |
| ); | |
| } | |
| new _internal( | |
| this.inputStreamController, | |
| this.stdoutStreamController, | |
| this.ioSink, | |
| this.receivedResults, | |
| this.outputParser, | |
| ); | |
| /// Sets up the front end server using the provided commandline arguments | |
| /// [args]. | |
| Future<int> open(List<String> args) => | |
| starter(args, input: inputStreamController.stream, output: ioSink); | |
| /// Closes the listener stream. | |
| void close() => inputStreamController.close(); | |
| /// Sets up [f] to be called for each [Result] received from the frontend | |
| /// server. | |
| void listen(void Function(Result) f) => receivedResults.stream.listen(f); | |
| /// Accepts the last compilation delta. | |
| void accept() { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('accept\n'.codeUnits); | |
| } | |
| /// Rejects the last compilation delta. | |
| void reject() { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('reject\n'.codeUnits); | |
| } | |
| /// Resets the incremental compiler. | |
| void reset() { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('reset\n'.codeUnits); | |
| } | |
| /// Terminates the frontend server. | |
| void quit() { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('quit\n'.codeUnits); | |
| } | |
| /// Compiles the program from entry point [path]. | |
| // TODO(johnniwinther): Use (required) named arguments. | |
| void compile(String path) { | |
| outputParser.expectSources = true; | |
| inputStreamController.add('compile $path\n'.codeUnits); | |
| } | |
| /// Recompiles the program. | |
| /// | |
| /// [invalidatedUri] and [invalidatedUris] define which libraries that | |
| /// need recompilation. | |
| /// | |
| /// [entryPoint] defines the program entry-point. If not provided, the | |
| /// original entry point is used. | |
| /// | |
| /// [boundaryKey] is used as the boundary-key in the communication with the | |
| /// frontend server. | |
| // TODO(johnniwinther): Use (required) named arguments. | |
| void recompile( | |
| Uri? invalidatedUri, { | |
| String boundaryKey = 'abc', | |
| List<Uri>? invalidatedUris, | |
| String? entryPoint, | |
| bool recompileRestart = false, | |
| }) { | |
| invalidatedUris ??= [if (invalidatedUri != null) invalidatedUri]; | |
| outputParser.expectSources = true; | |
| inputStreamController.add( | |
| '${recompileRestart ? 'recompile-restart ' : 'recompile '}' | |
| '${entryPoint != null ? '$entryPoint ' : ''}' | |
| '$boundaryKey\n' | |
| '${invalidatedUris.map((uri) => '$uri\n').join()}' | |
| '$boundaryKey\n' | |
| .codeUnits, | |
| ); | |
| } | |
| /// Compiles the native assets in isolation. | |
| void compileNativeAssetsOnly() { | |
| outputParser.expectSources = true; | |
| inputStreamController.add('native-assets-only\n'.codeUnits); | |
| } | |
| /// Sets the native assets yaml [uri]. | |
| void setNativeAssets({required Uri uri}) { | |
| outputParser.expectSources = true; | |
| inputStreamController.add('native-assets $uri\n'.codeUnits); | |
| } | |
| /// Compiles the [expression] as if it occurs in [library] in "script" | |
| /// [scriptUri] at offset [offset]. | |
| /// If no [scriptUri] is provided it defaults to the same as [library]. | |
| /// If no [offset] is provided it defaults to -1 (i.e. "no offset"). | |
| /// [scriptUri] (if different, e.g. if in a part) and [offset] is needed for | |
| /// finding static types which is needed for expression evaluation on | |
| /// extension types. | |
| /// | |
| /// If [className] is provided, [expression] is compiled as if it occurs in | |
| /// the class of that name. | |
| /// | |
| /// If [isStatic] is `true`, the expression is compiled in the static scope | |
| /// of [className]. If [className] is `null`, this must be `false`. | |
| /// | |
| /// [boundaryKey] is used as the boundary-key in the communication with the | |
| /// frontend server. | |
| // TODO(johnniwinther): Use (required) named arguments. | |
| void compileExpression( | |
| String expression, | |
| Uri library, { | |
| String boundaryKey = 'abc', | |
| List<String> definitions = const [], | |
| List<String> definitionTypes = const [], | |
| String? className, | |
| String? methodName, | |
| bool? isStatic, | |
| Uri? scriptUri, | |
| int? offset, | |
| }) { | |
| if (useJsonForCommunication) { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('JSON_INPUT\n'.codeUnits); | |
| String jsonData = json.encode({ | |
| "type": "COMPILE_EXPRESSION", | |
| "data": { | |
| "expression": expression, | |
| "libraryUri": library.toString(), | |
| "definitions": definitions, | |
| "definitionTypes": definitionTypes, | |
| if (className != null) "class": className, | |
| if (methodName != null) "method": methodName, | |
| if (scriptUri != null) "scriptUri": scriptUri.toString(), | |
| if (offset != null) "offset": offset, | |
| if (isStatic != null) "static": isStatic, | |
| }, | |
| }); | |
| if (useJsonLineBreaks) { | |
| jsonData = jsonData.replaceAll(",", ",\n"); | |
| } | |
| inputStreamController.add(jsonData.codeUnits); | |
| inputStreamController.add('\n'.codeUnits); | |
| } else { | |
| // 'compile-expression <boundarykey> | |
| // expression | |
| // definitions (one per line) | |
| // ... | |
| // <boundarykey> | |
| // definitionTypes (one per line) | |
| // ... | |
| // <boundarykey> | |
| // type-definitions (one per line) | |
| // ... | |
| // <boundarykey> | |
| // type-bounds (one per line) | |
| // ... | |
| // <boundarykey> | |
| // type-defaults (one per line) | |
| // ... | |
| // <boundarykey> | |
| // <libraryUri: String> | |
| // <klass: String> | |
| // <method: String> | |
| // <isStatic: true|false> | |
| outputParser.expectSources = false; | |
| inputStreamController.add( | |
| 'compile-expression $boundaryKey\n' | |
| '$expression\n' | |
| '$boundaryKey\n' | |
| '$boundaryKey\n' | |
| '$boundaryKey\n' | |
| '$boundaryKey\n' | |
| '$boundaryKey\n' | |
| '$library\n' | |
| '${className ?? ''}\n' | |
| '\n' | |
| '${isStatic != null ? '$isStatic' : ''}\n' | |
| .codeUnits, | |
| ); | |
| } | |
| } | |
| /// Compiles the [expression] to JavaScript as if it occurs in [line] and | |
| /// [column] of [libraryUri]. | |
| /// | |
| /// [boundaryKey] is used as the boundary-key in the communication with the | |
| /// frontend server. | |
| void compileExpressionToJs({ | |
| required String expression, | |
| required String libraryUri, | |
| required int line, | |
| required int column, | |
| Uri? scriptUri, | |
| Map<String, String>? jsFrameValues, | |
| String boundaryKey = 'abc', | |
| }) { | |
| // TODO(https://github.com/dart-lang/sdk/issues/58265): `moduleName` is | |
| // soft-deprecated, so even though it's unused, the request should still | |
| // contain it so it can be parsed correctly. | |
| final String moduleName = 'unused'; | |
| if (useJsonForCommunication) { | |
| outputParser.expectSources = false; | |
| inputStreamController.add('JSON_INPUT\n'.codeUnits); | |
| String jsonData = json.encode({ | |
| "type": "COMPILE_EXPRESSION_JS", | |
| "data": { | |
| "expression": expression, | |
| "libraryUri": libraryUri, | |
| if (scriptUri != null) "scriptUri": scriptUri.toString(), | |
| if (jsFrameValues != null) "jsFrameValues": jsFrameValues, | |
| "line": line, | |
| "column": column, | |
| "moduleName": moduleName, | |
| }, | |
| }); | |
| if (useJsonLineBreaks) { | |
| jsonData = jsonData.replaceAll(",", ",\n"); | |
| } | |
| inputStreamController.add(jsonData.codeUnits); | |
| inputStreamController.add('\n'.codeUnits); | |
| } else { | |
| // 'compile-expression-to-js <boundarykey> | |
| // libraryUri | |
| // line | |
| // column | |
| // jsModules (one k-v pair per line) | |
| // ... | |
| // <boundarykey> | |
| // jsFrameValues (one k-v pair per line) | |
| // ... | |
| // <boundarykey> | |
| // moduleName | |
| // expression | |
| outputParser.expectSources = false; | |
| inputStreamController.add( | |
| 'compile-expression-to-js $boundaryKey\n' | |
| '$libraryUri\n' | |
| '$line\n' | |
| '$column\n' | |
| '$boundaryKey\n' | |
| '$boundaryKey\n' | |
| '$moduleName\n' | |
| '$expression\n' | |
| .codeUnits, | |
| ); | |
| } | |
| } | |
| } |