| // Copyright (c) 2018, 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. |
| |
| import 'dart:async' show Future; |
| import 'dart:io' show Directory, File; |
| |
| import 'package:expect/expect.dart' show Expect; |
| import 'package:front_end/src/api_prototype/compiler_options.dart' |
| show CompilerOptions; |
| import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart' |
| show IncrementalKernelGenerator; |
| import 'package:front_end/src/compute_platform_binaries_location.dart' |
| show computePlatformBinariesLocation; |
| import 'package:front_end/src/fasta/incremental_compiler.dart' |
| show IncrementalCompiler; |
| import 'package:front_end/src/fasta/kernel/utils.dart' |
| show writeProgramToFile, serializeProgram; |
| import "package:front_end/src/api_prototype/memory_file_system.dart" |
| show MemoryFileSystem; |
| import 'package:kernel/kernel.dart' |
| show Class, EmptyStatement, Library, Procedure, Program; |
| |
| import 'package:front_end/src/fasta/fasta_codes.dart' show LocatedMessage; |
| |
| import 'package:front_end/src/fasta/severity.dart' show Severity; |
| |
| Directory outDir; |
| |
| main() async { |
| await runPassingTest(testDart2jsCompile); |
| await runPassingTest(testDisappearingLibrary); |
| await runPassingTest(testDeferredLibrary); |
| await runPassingTest(testStrongModeMixins); |
| await runFailingTest( |
| testStrongModeMixins2, |
| "testStrongModeMixins2_a.dart: Error: " |
| "The parameter 'value' of the method 'A::child' has type"); |
| await runPassingTest(testInvalidateExportOfMain); |
| await runPassingTest(testInvalidatePart); |
| } |
| |
| void runFailingTest(dynamic test, String expectContains) async { |
| try { |
| await runPassingTest(test); |
| throw "Expected this to fail."; |
| } catch (e) { |
| if (e.toString().contains(expectContains)) { |
| print("got expected error as this test is currently failing"); |
| } else { |
| rethrow; |
| } |
| } |
| } |
| |
| void runPassingTest(dynamic test) async { |
| outDir = |
| Directory.systemTemp.createTempSync("incremental_load_from_dill_test"); |
| try { |
| await test(); |
| print("----"); |
| } finally { |
| outDir.deleteSync(recursive: true); |
| } |
| } |
| |
| /// Invalidate a part file. |
| void testInvalidatePart() async { |
| final Uri a = outDir.uri.resolve("testInvalidatePart_a.dart"); |
| final Uri b = outDir.uri.resolve("testInvalidatePart_b.dart"); |
| final Uri c = outDir.uri.resolve("testInvalidatePart_c.dart"); |
| final Uri d = outDir.uri.resolve("testInvalidatePart_d.dart"); |
| |
| Uri output = outDir.uri.resolve("testInvalidatePart_full.dill"); |
| Uri bootstrappedOutput = |
| outDir.uri.resolve("testInvalidatePart_full_from_bootstrap.dill"); |
| |
| new File.fromUri(a).writeAsStringSync(""" |
| library a; |
| import 'testInvalidatePart_c.dart'; |
| part 'testInvalidatePart_b.dart'; |
| """); |
| new File.fromUri(b).writeAsStringSync(""" |
| part of a; |
| b() { print("b"); } |
| """); |
| new File.fromUri(c).writeAsStringSync(""" |
| library c; |
| part 'testInvalidatePart_d.dart'; |
| """); |
| new File.fromUri(d).writeAsStringSync(""" |
| part of c; |
| d() { print("d"); } |
| """); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(a, output, options: getOptions(true)); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| a, bootstrappedOutput, output, [b], |
| performSizeTests: true, options: getOptions(true)); |
| print("Bootstrapped compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(bootstrapResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedOutput).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| |
| /// Invalidate the entrypoint which just exports another file (which has main). |
| void testInvalidateExportOfMain() async { |
| final Uri a = outDir.uri.resolve("testInvalidateExportOfMain_a.dart"); |
| final Uri b = outDir.uri.resolve("testInvalidateExportOfMain_b.dart"); |
| |
| Uri output = outDir.uri.resolve("testInvalidateExportOfMain_full.dill"); |
| Uri bootstrappedOutput = |
| outDir.uri.resolve("testInvalidateExportOfMain_full_from_bootstrap.dill"); |
| |
| new File.fromUri(a).writeAsStringSync(""" |
| export 'testInvalidateExportOfMain_b.dart'; |
| """); |
| new File.fromUri(b).writeAsStringSync(""" |
| main() { print("hello"); } |
| """); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(a, output, options: getOptions(true)); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| a, bootstrappedOutput, output, [a], |
| performSizeTests: true, options: getOptions(true)); |
| print("Bootstrapped compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(bootstrapResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedOutput).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| |
| /// Compile in strong mode. Use mixins. |
| void testStrongModeMixins2() async { |
| final Uri a = outDir.uri.resolve("testStrongModeMixins2_a.dart"); |
| final Uri b = outDir.uri.resolve("testStrongModeMixins2_b.dart"); |
| |
| Uri output = outDir.uri.resolve("testStrongModeMixins2_full.dill"); |
| Uri bootstrappedOutput = |
| outDir.uri.resolve("testStrongModeMixins2_full_from_bootstrap.dill"); |
| |
| new File.fromUri(a).writeAsStringSync(""" |
| import 'testStrongModeMixins2_b.dart'; |
| class A extends Object with B<C>, D<Object> {} |
| """); |
| new File.fromUri(b).writeAsStringSync(""" |
| abstract class B<ChildType extends Object> extends Object { |
| ChildType get child => null; |
| set child(ChildType value) {} |
| } |
| |
| class C extends Object {} |
| |
| abstract class D<T extends Object> extends Object with B<T> {} |
| """); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(a, output, options: getOptions(true)); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| a, bootstrappedOutput, output, [a], |
| performSizeTests: true, options: getOptions(true)); |
| print("Bootstrapped compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(bootstrapResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedOutput).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| |
| /// Compile in strong mode. Invalidate a file so type inferrer starts |
| /// on something compiled from source and (potentially) goes into |
| /// something loaded from dill. |
| void testStrongModeMixins() async { |
| final Uri a = outDir.uri.resolve("testStrongModeMixins_a.dart"); |
| final Uri b = outDir.uri.resolve("testStrongModeMixins_b.dart"); |
| |
| Uri output = outDir.uri.resolve("testStrongModeMixins_full.dill"); |
| Uri bootstrappedOutput = |
| outDir.uri.resolve("testStrongModeMixins_full_from_bootstrap.dill"); |
| |
| new File.fromUri(a).writeAsStringSync(""" |
| import 'testStrongModeMixins_b.dart'; |
| class A extends Object with B<Object>, C {} |
| """); |
| new File.fromUri(b).writeAsStringSync(""" |
| abstract class C<T extends Object> extends Object with B<T> {} |
| abstract class B<ChildType extends Object> extends Object {} |
| """); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(a, output, options: getOptions(true)); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| a, bootstrappedOutput, output, [a], |
| performSizeTests: true, options: getOptions(true)); |
| print("Bootstrapped compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(bootstrapResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedOutput).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| |
| /// Test loading from a dill file with a deferred library. |
| /// This is done by bootstrapping with no changes. |
| void testDeferredLibrary() async { |
| final Uri input = outDir.uri.resolve("testDeferredLibrary_main.dart"); |
| final Uri b = outDir.uri.resolve("testDeferredLibrary_b.dart"); |
| Uri output = outDir.uri.resolve("testDeferredLibrary_full.dill"); |
| Uri bootstrappedOutput = |
| outDir.uri.resolve("testDeferredLibrary_full_from_bootstrap.dill"); |
| new File.fromUri(input).writeAsStringSync(""" |
| import 'testDeferredLibrary_b.dart' deferred as b; |
| |
| void main() { |
| print(b.foo()); |
| } |
| """); |
| new File.fromUri(b).writeAsStringSync(""" |
| String foo() => "hello from foo in b"; |
| """); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(input, output); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| input, bootstrappedOutput, output, [], |
| performSizeTests: false); |
| print("Bootstrapped compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(bootstrapResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedOutput).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| |
| void testDart2jsCompile() async { |
| final Uri dart2jsUrl = Uri.base.resolve("pkg/compiler/bin/dart2js.dart"); |
| final Uri invalidateUri = Uri.parse("package:compiler/src/filenames.dart"); |
| Uri normalDill = outDir.uri.resolve("dart2js.full.dill"); |
| Uri bootstrappedDill = outDir.uri.resolve("dart2js.bootstrap.dill"); |
| Uri nonexisting = outDir.uri.resolve("dart2js.nonexisting.dill"); |
| Uri nonLoadable = outDir.uri.resolve("dart2js.nonloadable.dill"); |
| |
| // Compile dart2js without bootstrapping. |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| await normalCompile(dart2jsUrl, normalDill); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| // Create a file that cannot be (fully) loaded as a dill file. |
| List<int> corruptData = new File.fromUri(normalDill).readAsBytesSync(); |
| for (int i = 10 * (corruptData.length ~/ 16); |
| i < 15 * (corruptData.length ~/ 16); |
| ++i) { |
| corruptData[i] = 42; |
| } |
| new File.fromUri(nonLoadable).writeAsBytesSync(corruptData); |
| |
| // Compile dart2js, bootstrapping from the just-compiled dill, |
| // a nonexisting file and a dill file that isn't valid. |
| for (List<Object> bootstrapData in [ |
| [normalDill, true], |
| [nonexisting, false], |
| // [nonLoadable, false] // disabled for now |
| ]) { |
| Uri bootstrapWith = bootstrapData[0]; |
| bool bootstrapExpect = bootstrapData[1]; |
| stopwatch.reset(); |
| bool bootstrapResult = await bootstrapCompile( |
| dart2jsUrl, bootstrappedDill, bootstrapWith, [invalidateUri]); |
| Expect.equals(bootstrapExpect, bootstrapResult); |
| print("Bootstrapped compile(s) from ${bootstrapWith.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(normalDill).readAsBytesSync(); |
| List<int> bootstrappedDillData = |
| new File.fromUri(bootstrappedDill).readAsBytesSync(); |
| checkBootstrappedIsEqual(normalDillData, bootstrappedDillData); |
| } |
| } |
| |
| void checkBootstrappedIsEqual( |
| List<int> normalDillData, List<int> bootstrappedDillData) { |
| Expect.equals(normalDillData.length, bootstrappedDillData.length); |
| for (int i = 0; i < normalDillData.length; ++i) { |
| if (normalDillData[i] != bootstrappedDillData[i]) { |
| Expect.fail("Normally compiled and bootstrapped compile differs."); |
| } |
| } |
| } |
| |
| /// Compile an application with n libraries, then |
| /// compile "the same" application, but with m < n libraries, |
| /// where (at least one) of the missing libraries are "in the middle" |
| /// of the library list --- bootstrapping from the dill with n libarries. |
| void testDisappearingLibrary() async { |
| final Uri base = Uri.parse("org-dartlang-test:///"); |
| final Uri sdkSummary = base.resolve("vm_platform.dill"); |
| final Uri main = base.resolve("main.dart"); |
| final Uri b = base.resolve("b.dart"); |
| final Uri bootstrap = base.resolve("bootstrapFrom.dill"); |
| final List<int> sdkSummaryData = await new File.fromUri( |
| computePlatformBinariesLocation().resolve("vm_platform.dill")) |
| .readAsBytes(); |
| |
| List<int> libCount2; |
| { |
| MemoryFileSystem fs = new MemoryFileSystem(base); |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); |
| |
| fs.entityForUri(main).writeAsStringSync(""" |
| library mainLibrary; |
| import "b.dart" as b; |
| |
| main() { |
| b.foo(); |
| } |
| """); |
| |
| fs.entityForUri(b).writeAsStringSync(""" |
| library bLibrary; |
| |
| foo() { |
| print("hello from b.dart foo!"); |
| } |
| """); |
| |
| CompilerOptions options = getOptions(false); |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummary; |
| IncrementalCompiler compiler = |
| new IncrementalKernelGenerator(options, main); |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| var program = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(program); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| libCount2 = serializeProgram(program); |
| if (program.libraries.length != 2) { |
| throw "Expected 2 libraries, got ${program.libraries.length}"; |
| } |
| if (program.libraries[0].fileUri != main) { |
| throw "Expected the first library to have uri $main but was " |
| "${program.libraries[0].fileUri}"; |
| } |
| } |
| |
| { |
| MemoryFileSystem fs = new MemoryFileSystem(base); |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); |
| fs.entityForUri(bootstrap).writeAsBytesSync(libCount2); |
| fs.entityForUri(b).writeAsStringSync(""" |
| library bLibrary; |
| |
| main() { |
| print("hello from b!"); |
| } |
| """); |
| CompilerOptions options = getOptions(false); |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummary; |
| IncrementalCompiler compiler = |
| new IncrementalKernelGenerator(options, b, bootstrap); |
| compiler.invalidate(main); |
| compiler.invalidate(b); |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| var program = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(program); |
| print("Bootstrapped compile took ${stopwatch.elapsedMilliseconds} ms"); |
| if (program.libraries.length != 1) { |
| throw "Expected 1 library, got ${program.libraries.length}"; |
| } |
| } |
| } |
| |
| CompilerOptions getOptions(bool strong) { |
| final Uri sdkRoot = computePlatformBinariesLocation(); |
| var options = new CompilerOptions() |
| ..sdkRoot = sdkRoot |
| ..librariesSpecificationUri = Uri.base.resolve("sdk/lib/libraries.json") |
| ..onProblem = (LocatedMessage message, Severity severity, String formatted, |
| int line, int column) { |
| if (severity == Severity.error || severity == Severity.warning) { |
| Expect.fail("Unexpected error: $formatted"); |
| } |
| } |
| ..strongMode = strong; |
| if (strong) { |
| options.sdkSummary = |
| computePlatformBinariesLocation().resolve("vm_platform_strong.dill"); |
| } else { |
| options.sdkSummary = |
| computePlatformBinariesLocation().resolve("vm_platform.dill"); |
| } |
| return options; |
| } |
| |
| Future<bool> normalCompile(Uri input, Uri output, |
| {CompilerOptions options}) async { |
| options ??= getOptions(false); |
| IncrementalCompiler compiler = new IncrementalKernelGenerator(options, input); |
| var program = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(program); |
| await writeProgramToFile(program, output); |
| return compiler.initializedFromDill; |
| } |
| |
| void throwOnEmptyMixinBodies(Program program) { |
| int empty = countEmptyMixinBodies(program); |
| if (empty != 0) { |
| throw "Expected 0 empty bodies in mixins, but found $empty"; |
| } |
| } |
| |
| int countEmptyMixinBodies(Program program) { |
| int empty = 0; |
| for (Library lib in program.libraries) { |
| for (Class c in lib.classes) { |
| if (c.isSyntheticMixinImplementation) { |
| // Assume mixin |
| for (Procedure p in c.procedures) { |
| if (p.function.body is EmptyStatement) { |
| empty++; |
| } |
| } |
| } |
| } |
| } |
| return empty; |
| } |
| |
| Future<bool> bootstrapCompile( |
| Uri input, Uri output, Uri bootstrapWith, List<Uri> invalidateUris, |
| {bool performSizeTests: true, CompilerOptions options}) async { |
| options ??= getOptions(false); |
| IncrementalCompiler compiler = |
| new IncrementalKernelGenerator(options, input, bootstrapWith); |
| for (Uri invalidateUri in invalidateUris) { |
| compiler.invalidate(invalidateUri); |
| } |
| var bootstrappedProgram = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(bootstrappedProgram); |
| bool result = compiler.initializedFromDill; |
| await writeProgramToFile(bootstrappedProgram, output); |
| for (Uri invalidateUri in invalidateUris) { |
| compiler.invalidate(invalidateUri); |
| } |
| |
| var partialProgram = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(partialProgram); |
| var emptyProgram = await compiler.computeDelta(); |
| throwOnEmptyMixinBodies(emptyProgram); |
| |
| var fullLibUris = |
| bootstrappedProgram.libraries.map((lib) => lib.importUri).toList(); |
| var partialLibUris = |
| partialProgram.libraries.map((lib) => lib.importUri).toList(); |
| var emptyLibUris = |
| emptyProgram.libraries.map((lib) => lib.importUri).toList(); |
| |
| if (performSizeTests) { |
| Expect.isTrue(fullLibUris.length > partialLibUris.length); |
| Expect.isTrue(partialLibUris.isNotEmpty); |
| Expect.isTrue(emptyLibUris.isEmpty); |
| } |
| |
| return result; |
| } |