| // 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/base/processed_options.dart' |
| show ProcessedOptions; |
| |
| import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; |
| |
| import 'package:front_end/src/api_prototype/compiler_options.dart' |
| show CompilerOptions; |
| |
| import 'package:front_end/src/api_prototype/diagnostic_message.dart' |
| show DiagnosticMessage, getMessageCodeObject; |
| |
| import "package:front_end/src/api_prototype/memory_file_system.dart" |
| show MemoryFileSystem; |
| |
| 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/severity.dart' show Severity; |
| |
| import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder; |
| |
| import 'package:kernel/kernel.dart' |
| show |
| Class, |
| Component, |
| EmptyStatement, |
| Field, |
| Library, |
| LibraryDependency, |
| Name, |
| Procedure; |
| |
| import 'package:kernel/target/targets.dart' show TargetFlags; |
| |
| import 'package:kernel/text/ast_to_text.dart' show componentToString; |
| |
| import "package:testing/testing.dart" |
| show Chain, ChainContext, Result, Step, TestDescription, runMe; |
| |
| import "package:vm/target/vm.dart" show VmTarget; |
| |
| import "package:yaml/yaml.dart" show YamlList, YamlMap, loadYamlNode; |
| |
| import "incremental_utils.dart" as util; |
| |
| import 'package:front_end/src/fasta/fasta_codes.dart' |
| show DiagnosticMessageFromJson, FormattedMessage; |
| |
| main([List<String> arguments = const []]) => |
| runMe(arguments, createContext, "../testing.json"); |
| |
| Future<Context> createContext( |
| Chain suite, Map<String, String> environment) async { |
| return new Context(); |
| } |
| |
| class Context extends ChainContext { |
| final List<Step> steps = const <Step>[ |
| const ReadTest(), |
| const RunCompilations(), |
| ]; |
| |
| @override |
| Future<void> cleanUp(TestDescription description, Result result) async { |
| await cleanupHelper?.outDir?.delete(recursive: true); |
| } |
| |
| TestData cleanupHelper; |
| } |
| |
| class TestData { |
| YamlMap map; |
| Directory outDir; |
| } |
| |
| class ReadTest extends Step<TestDescription, TestData, Context> { |
| const ReadTest(); |
| |
| String get name => "read test"; |
| |
| Future<Result<TestData>> run( |
| TestDescription description, Context context) async { |
| Uri uri = description.uri; |
| String contents = await new File.fromUri(uri).readAsString(); |
| TestData data = new TestData(); |
| data.map = loadYamlNode(contents, sourceUrl: uri); |
| data.outDir = |
| Directory.systemTemp.createTempSync("incremental_load_from_dill_test"); |
| context.cleanupHelper = data; |
| return pass(data); |
| } |
| } |
| |
| class RunCompilations extends Step<TestData, TestData, Context> { |
| const RunCompilations(); |
| |
| String get name => "run compilations"; |
| |
| Future<Result<TestData>> run(TestData data, Context context) async { |
| YamlMap map = data.map; |
| switch (map["type"]) { |
| case "basic": |
| await basicTest( |
| map["sources"], |
| map["entry"], |
| map["invalidate"], |
| data.outDir, |
| ); |
| break; |
| case "newworld": |
| await newWorldTest( |
| map["worlds"], |
| map["modules"], |
| map["omitPlatform"], |
| ); |
| break; |
| default: |
| throw "Unexpected type: ${map['type']}"; |
| } |
| return pass(data); |
| } |
| } |
| |
| Future<Null> basicTest(YamlMap sourceFiles, String entryPoint, |
| YamlList invalidate, Directory outDir) async { |
| Uri entryPointUri = outDir.uri.resolve(entryPoint); |
| Set<String> invalidateFilenames = |
| invalidate == null ? new Set<String>() : new Set<String>.from(invalidate); |
| List<Uri> invalidateUris = <Uri>[]; |
| Uri packagesUri; |
| for (String filename in sourceFiles.keys) { |
| Uri uri = outDir.uri.resolve(filename); |
| if (invalidateFilenames.contains(filename)) { |
| invalidateUris.add(uri); |
| invalidateFilenames.remove(filename); |
| } |
| String source = sourceFiles[filename]; |
| if (filename == ".packages") { |
| packagesUri = uri; |
| } |
| File file = new File.fromUri(uri); |
| await file.parent.create(recursive: true); |
| await file.writeAsString(source); |
| } |
| for (String invalidateFilename in invalidateFilenames) { |
| if (invalidateFilename.startsWith('package:')) { |
| invalidateUris.add(Uri.parse(invalidateFilename)); |
| } else { |
| throw "Error in test yaml: $invalidateFilename was not recognized."; |
| } |
| } |
| |
| Uri output = outDir.uri.resolve("full.dill"); |
| Uri initializedOutput = outDir.uri.resolve("full_from_initialized.dill"); |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| CompilerOptions options = getOptions(); |
| if (packagesUri != null) { |
| options.packagesFileUri = packagesUri; |
| } |
| await normalCompile(entryPointUri, output, options: options); |
| print("Normal compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| stopwatch.reset(); |
| options = getOptions(); |
| if (packagesUri != null) { |
| options.packagesFileUri = packagesUri; |
| } |
| bool initializedResult = await initializedCompile( |
| entryPointUri, initializedOutput, output, invalidateUris, |
| options: options); |
| print("Initialized compile(s) from ${output.pathSegments.last} " |
| "took ${stopwatch.elapsedMilliseconds} ms"); |
| Expect.isTrue(initializedResult); |
| |
| // Compare the two files. |
| List<int> normalDillData = new File.fromUri(output).readAsBytesSync(); |
| List<int> initializedDillData = |
| new File.fromUri(initializedOutput).readAsBytesSync(); |
| checkIsEqual(normalDillData, initializedDillData); |
| } |
| |
| Future<Map<String, List<int>>> createModules( |
| Map module, final List<int> sdkSummaryData) async { |
| final Uri base = Uri.parse("org-dartlang-test:///"); |
| final Uri sdkSummary = base.resolve("vm_platform.dill"); |
| |
| MemoryFileSystem fs = new MemoryFileSystem(base); |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); |
| |
| // Setup all sources |
| for (Map moduleSources in module.values) { |
| for (String filename in moduleSources.keys) { |
| String data = moduleSources[filename]; |
| Uri uri = base.resolve(filename); |
| if (await fs.entityForUri(uri).exists()) { |
| throw "More than one entry for $filename"; |
| } |
| fs.entityForUri(uri).writeAsStringSync(data); |
| } |
| } |
| |
| Map<String, List<int>> moduleResult = new Map<String, List<int>>(); |
| |
| for (String moduleName in module.keys) { |
| List<Uri> moduleSources = new List<Uri>(); |
| Uri packagesUri; |
| for (String filename in module[moduleName].keys) { |
| Uri uri = base.resolve(filename); |
| if (uri.pathSegments.last == ".packages") { |
| packagesUri = uri; |
| } else { |
| moduleSources.add(uri); |
| } |
| } |
| CompilerOptions options = getOptions(); |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummary; |
| options.omitPlatform = true; |
| options.onDiagnostic = (DiagnosticMessage message) { |
| if (getMessageCodeObject(message)?.name == "InferredPackageUri") return; |
| throw message.ansiFormatted; |
| }; |
| if (packagesUri != null) { |
| options.packagesFileUri = packagesUri; |
| } |
| TestIncrementalCompiler compiler = |
| new TestIncrementalCompiler(options, moduleSources.first, null); |
| Component c = await compiler.computeDelta(entryPoints: moduleSources); |
| c.computeCanonicalNames(); |
| List<Library> wantedLibs = new List<Library>(); |
| for (Library lib in c.libraries) { |
| if (moduleSources.contains(lib.importUri) || |
| moduleSources.contains(lib.fileUri)) { |
| wantedLibs.add(lib); |
| } |
| } |
| if (wantedLibs.length != moduleSources.length) { |
| throw "Module probably not setup right."; |
| } |
| Component result = new Component(libraries: wantedLibs); |
| List<int> resultBytes = util.postProcess(result); |
| moduleResult[moduleName] = resultBytes; |
| } |
| |
| return moduleResult; |
| } |
| |
| Future<Null> newWorldTest(List worlds, Map modules, bool omitPlatform) async { |
| final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); |
| final Uri base = Uri.parse("org-dartlang-test:///"); |
| final Uri sdkSummary = base.resolve("vm_platform.dill"); |
| final Uri initializeFrom = base.resolve("initializeFrom.dill"); |
| Uri platformUri = sdkRoot.resolve("vm_platform_strong.dill"); |
| final List<int> sdkSummaryData = |
| await new File.fromUri(platformUri).readAsBytes(); |
| |
| List<int> newestWholeComponentData; |
| Component newestWholeComponent; |
| MemoryFileSystem fs; |
| Map<String, String> sourceFiles; |
| CompilerOptions options; |
| TestIncrementalCompiler compiler; |
| |
| Map<String, List<int>> moduleData; |
| Map<String, Component> moduleComponents; |
| Component sdk; |
| if (modules != null) { |
| moduleData = await createModules(modules, sdkSummaryData); |
| sdk = newestWholeComponent = new Component(); |
| new BinaryBuilder(sdkSummaryData, filename: null, disableLazyReading: false) |
| .readComponent(newestWholeComponent); |
| } |
| |
| for (YamlMap world in worlds) { |
| List<Component> modulesToUse; |
| if (world["modules"] != null) { |
| moduleComponents ??= new Map<String, Component>(); |
| |
| sdk.adoptChildren(); |
| for (Component c in moduleComponents.values) { |
| c.adoptChildren(); |
| } |
| |
| modulesToUse = new List<Component>(); |
| for (String moduleName in world["modules"]) { |
| Component moduleComponent = moduleComponents[moduleName]; |
| if (moduleComponent != null) { |
| modulesToUse.add(moduleComponent); |
| } |
| } |
| for (String moduleName in world["modules"]) { |
| Component moduleComponent = moduleComponents[moduleName]; |
| if (moduleComponent == null) { |
| moduleComponent = new Component(nameRoot: sdk.root); |
| new BinaryBuilder(moduleData[moduleName], |
| filename: null, |
| disableLazyReading: false, |
| alwaysCreateNewNamedNodes: true) |
| .readComponent(moduleComponent); |
| moduleComponents[moduleName] = moduleComponent; |
| modulesToUse.add(moduleComponent); |
| } |
| } |
| } |
| bool brandNewWorld = true; |
| if (world["worldType"] == "updated") { |
| brandNewWorld = false; |
| } |
| bool noFullComponent = false; |
| if (world["noFullComponent"] == true) { |
| noFullComponent = true; |
| } |
| |
| if (brandNewWorld) { |
| fs = new MemoryFileSystem(base); |
| } |
| fs.entityForUri(sdkSummary).writeAsBytesSync(sdkSummaryData); |
| bool expectInitializeFromDill = false; |
| if (newestWholeComponentData != null && |
| newestWholeComponentData.isNotEmpty) { |
| fs |
| .entityForUri(initializeFrom) |
| .writeAsBytesSync(newestWholeComponentData); |
| expectInitializeFromDill = true; |
| } |
| if (world["expectInitializeFromDill"] != null) { |
| expectInitializeFromDill = world["expectInitializeFromDill"]; |
| } |
| if (brandNewWorld) { |
| sourceFiles = new Map<String, String>.from(world["sources"]); |
| } else { |
| sourceFiles.addAll( |
| new Map<String, String>.from(world["sources"] ?? <String, String>{})); |
| } |
| Uri packagesUri; |
| for (String filename in sourceFiles.keys) { |
| String data = sourceFiles[filename] ?? ""; |
| Uri uri = base.resolve(filename); |
| if (filename == ".packages") { |
| packagesUri = uri; |
| } |
| fs.entityForUri(uri).writeAsStringSync(data); |
| } |
| if (world["dotPackagesFile"] != null) { |
| packagesUri = base.resolve(world["dotPackagesFile"]); |
| } |
| |
| if (brandNewWorld) { |
| options = getOptions(); |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummary; |
| options.omitPlatform = omitPlatform != false; |
| } |
| if (packagesUri != null) { |
| options.packagesFileUri = packagesUri; |
| } |
| bool gotError = false; |
| final Set<String> formattedErrors = Set<String>(); |
| bool gotWarning = false; |
| final Set<String> formattedWarnings = Set<String>(); |
| |
| options.onDiagnostic = (DiagnosticMessage message) { |
| String stringId = message.ansiFormatted.join("\n"); |
| if (message is FormattedMessage) { |
| stringId = message.toJsonString(); |
| } else if (message is DiagnosticMessageFromJson) { |
| stringId = message.toJsonString(); |
| } |
| if (message.severity == Severity.error) { |
| gotError = true; |
| if (!formattedErrors.add(stringId)) { |
| Expect.fail("Got the same message twice: ${stringId}"); |
| } |
| } else if (message.severity == Severity.warning) { |
| gotWarning = true; |
| if (!formattedWarnings.add(stringId)) { |
| Expect.fail("Got the same message twice: ${stringId}"); |
| } |
| } |
| }; |
| |
| List<Uri> entries; |
| if (world["entry"] is String) { |
| entries = [base.resolve(world["entry"])]; |
| } else { |
| entries = new List<Uri>(); |
| List<dynamic> entryList = world["entry"]; |
| for (String entry in entryList) { |
| entries.add(base.resolve(entry)); |
| } |
| } |
| bool outlineOnly = world["outlineOnly"] == true; |
| bool skipOutlineBodyCheck = world["skipOutlineBodyCheck"] == true; |
| if (brandNewWorld) { |
| if (world["fromComponent"] == true) { |
| compiler = new TestIncrementalCompiler.fromComponent( |
| options, entries.first, newestWholeComponent, outlineOnly); |
| } else { |
| compiler = new TestIncrementalCompiler( |
| options, entries.first, initializeFrom, outlineOnly); |
| } |
| } |
| |
| List<Uri> invalidated = new List<Uri>(); |
| if (world["invalidate"] != null) { |
| for (String filename in world["invalidate"]) { |
| Uri uri = base.resolve(filename); |
| invalidated.add(uri); |
| compiler.invalidate(uri); |
| } |
| } |
| |
| if (modulesToUse != null) { |
| compiler.setModulesToLoadOnNextComputeDelta(modulesToUse); |
| compiler.invalidateAllSources(); |
| compiler.trackNeededDillLibraries = true; |
| } |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| Component component = await compiler.computeDelta( |
| entryPoints: entries, |
| fullComponent: brandNewWorld ? false : (noFullComponent ? false : true), |
| simulateTransformer: world["simulateTransformer"]); |
| if (outlineOnly && !skipOutlineBodyCheck) { |
| for (Library lib in component.libraries) { |
| for (Class c in lib.classes) { |
| for (Procedure p in c.procedures) { |
| if (p.function.body != null && p.function.body is! EmptyStatement) { |
| throw "Got body (${p.function.body.runtimeType})"; |
| } |
| } |
| } |
| for (Procedure p in lib.procedures) { |
| if (p.function.body != null && p.function.body is! EmptyStatement) { |
| throw "Got body (${p.function.body.runtimeType})"; |
| } |
| } |
| } |
| } |
| performErrorAndWarningCheck( |
| world, gotError, formattedErrors, gotWarning, formattedWarnings); |
| util.throwOnEmptyMixinBodies(component); |
| util.throwOnInsufficientUriToSource(component); |
| print("Compile took ${stopwatch.elapsedMilliseconds} ms"); |
| |
| checkExpectedContent(world, component); |
| checkNeededDillLibraries(world, compiler.neededDillLibraries, base); |
| |
| if (!noFullComponent) { |
| Set<Library> allLibraries = new Set<Library>(); |
| for (Library lib in component.libraries) { |
| computeAllReachableLibrariesFor(lib, allLibraries); |
| } |
| if (allLibraries.length != component.libraries.length) { |
| Expect.fail("Expected for the reachable stuff to be equal to " |
| "${component.libraries} but it was $allLibraries"); |
| } |
| Set<Library> tooMany = allLibraries.toSet() |
| ..removeAll(component.libraries); |
| if (tooMany.isNotEmpty) { |
| Expect.fail("Expected for the reachable stuff to be equal to " |
| "${component.libraries} but these were there too: $tooMany " |
| "(and others were missing)"); |
| } |
| } |
| |
| newestWholeComponentData = util.postProcess(component); |
| newestWholeComponent = component; |
| print("*****\n\ncomponent:\n" |
| "${componentToStringSdkFiltered(component)}\n\n\n"); |
| |
| if (world["uriToSourcesDoesntInclude"] != null) { |
| for (String filename in world["uriToSourcesDoesntInclude"]) { |
| Uri uri = base.resolve(filename); |
| if (component.uriToSource[uri] != null) { |
| throw "Expected no uriToSource for $uri but found " |
| "${component.uriToSource[uri]}"; |
| } |
| } |
| } |
| |
| int nonSyntheticLibraries = countNonSyntheticLibraries(component); |
| int nonSyntheticPlatformLibraries = |
| countNonSyntheticPlatformLibraries(component); |
| int syntheticLibraries = countSyntheticLibraries(component); |
| if (world["expectsPlatform"] == true) { |
| if (nonSyntheticPlatformLibraries < 5) { |
| throw "Expected to have at least 5 platform libraries " |
| "(actually, the entire sdk), " |
| "but got $nonSyntheticPlatformLibraries."; |
| } |
| } else { |
| if (nonSyntheticPlatformLibraries != 0) { |
| throw "Expected to have 0 platform libraries " |
| "but got $nonSyntheticPlatformLibraries."; |
| } |
| } |
| if (world["expectedLibraryCount"] != null) { |
| if (nonSyntheticLibraries - nonSyntheticPlatformLibraries != |
| world["expectedLibraryCount"]) { |
| throw "Expected ${world["expectedLibraryCount"]} non-synthetic " |
| "libraries, got " |
| "${nonSyntheticLibraries - nonSyntheticPlatformLibraries} " |
| "(not counting platform libraries)"; |
| } |
| } |
| if (world["expectedSyntheticLibraryCount"] != null) { |
| if (syntheticLibraries != world["expectedSyntheticLibraryCount"]) { |
| throw "Expected ${world["expectedSyntheticLibraryCount"]} synthetic " |
| "libraries, got ${syntheticLibraries}"; |
| } |
| } |
| if (!noFullComponent) { |
| List<Library> entryLib = component.libraries |
| .where((Library lib) => |
| entries.contains(lib.importUri) || entries.contains(lib.fileUri)) |
| .toList(); |
| if (entryLib.length != entries.length) { |
| throw "Expected the entries to become libraries. " |
| "Got ${entryLib.length} libraries for the expected " |
| "${entries.length} entries."; |
| } |
| } |
| if (compiler.initializedFromDill != expectInitializeFromDill) { |
| throw "Expected that initializedFromDill would be " |
| "$expectInitializeFromDill but was ${compiler.initializedFromDill}"; |
| } |
| if (world["checkInvalidatedFiles"] != false) { |
| Set<Uri> filteredInvalidated = |
| compiler.getFilteredInvalidatedImportUrisForTesting(invalidated); |
| if (world["invalidate"] != null) { |
| Expect.equals( |
| world["invalidate"].length, filteredInvalidated?.length ?? 0); |
| List expectedInvalidatedUri = world["expectedInvalidatedUri"]; |
| if (expectedInvalidatedUri != null) { |
| Expect.setEquals(expectedInvalidatedUri.map((s) => base.resolve(s)), |
| filteredInvalidated); |
| } |
| } else { |
| Expect.isNull(filteredInvalidated); |
| Expect.isNull(world["expectedInvalidatedUri"]); |
| } |
| } |
| |
| if (!noFullComponent) { |
| Set<String> prevFormattedErrors = formattedErrors.toSet(); |
| Set<String> prevFormattedWarnings = formattedWarnings.toSet(); |
| gotError = false; |
| formattedErrors.clear(); |
| gotWarning = false; |
| formattedWarnings.clear(); |
| Component component2 = await compiler.computeDelta( |
| entryPoints: entries, |
| fullComponent: true, |
| simulateTransformer: world["simulateTransformer"]); |
| performErrorAndWarningCheck( |
| world, gotError, formattedErrors, gotWarning, formattedWarnings); |
| List<int> thisWholeComponent = util.postProcess(component2); |
| print("*****\n\ncomponent2:\n" |
| "${componentToStringSdkFiltered(component2)}\n\n\n"); |
| checkIsEqual(newestWholeComponentData, thisWholeComponent); |
| if (prevFormattedErrors.length != formattedErrors.length) { |
| Expect.fail("Previously had ${prevFormattedErrors.length} errors, " |
| "now had ${formattedErrors.length}.\n\n" |
| "Before:\n" |
| "${prevFormattedErrors.join("\n")}" |
| "\n\n" |
| "Now:\n" |
| "${formattedErrors.join("\n")}"); |
| } |
| if ((prevFormattedErrors.toSet()..removeAll(formattedErrors)) |
| .isNotEmpty) { |
| Expect.fail("Previously got error messages $prevFormattedErrors, " |
| "now had ${formattedErrors}."); |
| } |
| if (prevFormattedWarnings.length != formattedWarnings.length) { |
| Expect.fail("Previously had ${prevFormattedWarnings.length} errors, " |
| "now had ${formattedWarnings.length}."); |
| } |
| if ((prevFormattedWarnings.toSet()..removeAll(formattedWarnings)) |
| .isNotEmpty) { |
| Expect.fail("Previously got error messages $prevFormattedWarnings, " |
| "now had ${formattedWarnings}."); |
| } |
| } |
| |
| if (world["expressionCompilation"] != null) { |
| Uri uri = base.resolve(world["expressionCompilation"]["uri"]); |
| String expression = world["expressionCompilation"]["expression"]; |
| await compiler.compileExpression(expression, {}, [], "debugExpr", uri); |
| } |
| } |
| } |
| |
| void computeAllReachableLibrariesFor(Library lib, Set<Library> allLibraries) { |
| Set<Library> libraries = new Set<Library>(); |
| List<Library> workList = <Library>[]; |
| allLibraries.add(lib); |
| libraries.add(lib); |
| workList.add(lib); |
| while (workList.isNotEmpty) { |
| Library library = workList.removeLast(); |
| for (LibraryDependency dependency in library.dependencies) { |
| if (libraries.add(dependency.targetLibrary)) { |
| workList.add(dependency.targetLibrary); |
| allLibraries.add(dependency.targetLibrary); |
| } |
| } |
| } |
| } |
| |
| void checkExpectedContent(YamlMap world, Component component) { |
| if (world["expectedContent"] != null) { |
| Map<String, Set<String>> actualContent = new Map<String, Set<String>>(); |
| for (Library lib in component.libraries) { |
| Set<String> libContent = |
| actualContent[lib.importUri.toString()] = new Set<String>(); |
| for (Class c in lib.classes) { |
| libContent.add("Class ${c.name}"); |
| } |
| for (Procedure p in lib.procedures) { |
| libContent.add("Procedure ${p.name}"); |
| } |
| for (Field f in lib.fields) { |
| libContent.add("Field ${f.name}"); |
| } |
| } |
| |
| Map expectedContent = world["expectedContent"]; |
| |
| doThrow() { |
| throw "Expected and actual content not the same.\n" |
| "Expected $expectedContent.\n" |
| "Got $actualContent"; |
| } |
| |
| if (actualContent.length != expectedContent.length) doThrow(); |
| Set<String> missingKeys = actualContent.keys.toSet() |
| ..removeAll(expectedContent.keys); |
| if (missingKeys.isNotEmpty) doThrow(); |
| for (String key in expectedContent.keys) { |
| Set<String> expected = new Set<String>.from(expectedContent[key]); |
| Set<String> actual = actualContent[key].toSet(); |
| if (expected.length != actual.length) doThrow(); |
| actual.removeAll(expected); |
| if (actual.isNotEmpty) doThrow(); |
| } |
| } |
| } |
| |
| void checkNeededDillLibraries( |
| YamlMap world, Set<Library> neededDillLibraries, Uri base) { |
| if (world["neededDillLibraries"] != null) { |
| List<Uri> actualContent = new List<Uri>(); |
| for (Library lib in neededDillLibraries) { |
| if (lib.importUri.scheme == "dart") continue; |
| actualContent.add(lib.importUri); |
| } |
| |
| List<Uri> expectedContent = new List<Uri>(); |
| for (String entry in world["neededDillLibraries"]) { |
| expectedContent.add(base.resolve(entry)); |
| } |
| |
| doThrow() { |
| throw "Expected and actual content not the same.\n" |
| "Expected $expectedContent.\n" |
| "Got $actualContent"; |
| } |
| |
| if (actualContent.length != expectedContent.length) doThrow(); |
| Set<Uri> notInExpected = |
| actualContent.toSet().difference(expectedContent.toSet()); |
| Set<Uri> notInActual = |
| expectedContent.toSet().difference(actualContent.toSet()); |
| if (notInExpected.isNotEmpty) doThrow(); |
| if (notInActual.isNotEmpty) doThrow(); |
| } |
| } |
| |
| String componentToStringSdkFiltered(Component node) { |
| Component c = new Component(); |
| List<Uri> dartUris = new List<Uri>(); |
| for (Library lib in node.libraries) { |
| if (lib.importUri.scheme == "dart") { |
| dartUris.add(lib.importUri); |
| } else { |
| c.libraries.add(lib); |
| } |
| } |
| |
| StringBuffer s = new StringBuffer(); |
| s.write(componentToString(c)); |
| |
| if (dartUris.isNotEmpty) { |
| s.writeln(""); |
| s.writeln("And ${dartUris.length} platform libraries:"); |
| for (Uri uri in dartUris) { |
| s.writeln(" - $uri"); |
| } |
| } |
| |
| return s.toString(); |
| } |
| |
| int countNonSyntheticLibraries(Component c) { |
| int result = 0; |
| for (Library lib in c.libraries) { |
| if (!lib.isSynthetic) result++; |
| } |
| return result; |
| } |
| |
| int countNonSyntheticPlatformLibraries(Component c) { |
| int result = 0; |
| for (Library lib in c.libraries) { |
| if (!lib.isSynthetic && lib.importUri.scheme == "dart") result++; |
| } |
| return result; |
| } |
| |
| int countSyntheticLibraries(Component c) { |
| int result = 0; |
| for (Library lib in c.libraries) { |
| if (lib.isSynthetic) result++; |
| } |
| return result; |
| } |
| |
| void performErrorAndWarningCheck( |
| YamlMap world, |
| bool gotError, |
| Set<String> formattedErrors, |
| bool gotWarning, |
| Set<String> formattedWarnings) { |
| if (world["errors"] == true && !gotError) { |
| throw "Expected error, but didn't get any."; |
| } else if (world["errors"] != true && gotError) { |
| throw "Got unexpected error(s): $formattedErrors."; |
| } |
| if (world["warnings"] == true && !gotWarning) { |
| throw "Expected warning, but didn't get any."; |
| } else if (world["warnings"] != true && gotWarning) { |
| throw "Got unexpected warnings(s): $formattedWarnings."; |
| } |
| } |
| |
| 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]) { |
| Expect.fail("Data differs at byte ${i + 1}."); |
| } |
| } |
| Expect.equals(a.length, b.length); |
| } |
| |
| CompilerOptions getOptions() { |
| final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); |
| CompilerOptions options = new CompilerOptions() |
| ..sdkRoot = sdkRoot |
| ..target = new VmTarget(new TargetFlags()) |
| ..librariesSpecificationUri = Uri.base.resolve("sdk/lib/libraries.json") |
| ..omitPlatform = true |
| ..onDiagnostic = (DiagnosticMessage message) { |
| if (message.severity == Severity.error || |
| message.severity == Severity.warning) { |
| Expect.fail( |
| "Unexpected error: ${message.plainTextFormatted.join('\n')}"); |
| } |
| } |
| ..sdkSummary = sdkRoot.resolve("vm_platform_strong.dill") |
| ..environmentDefines = const {}; |
| return options; |
| } |
| |
| Future<bool> normalCompile(Uri input, Uri output, |
| {CompilerOptions options}) async { |
| options ??= getOptions(); |
| TestIncrementalCompiler compiler = |
| new TestIncrementalCompiler(options, input); |
| Component component = await compiler.computeDelta(); |
| util.throwOnEmptyMixinBodies(component); |
| util.throwOnInsufficientUriToSource(component); |
| new File.fromUri(output).writeAsBytesSync(util.postProcess(component)); |
| return compiler.initializedFromDill; |
| } |
| |
| Future<bool> initializedCompile( |
| Uri input, Uri output, Uri initializeWith, List<Uri> invalidateUris, |
| {CompilerOptions options}) async { |
| options ??= getOptions(); |
| TestIncrementalCompiler compiler = |
| new TestIncrementalCompiler(options, input, initializeWith); |
| for (Uri invalidateUri in invalidateUris) { |
| compiler.invalidate(invalidateUri); |
| } |
| Component initializedComponent = await compiler.computeDelta(); |
| util.throwOnEmptyMixinBodies(initializedComponent); |
| util.throwOnInsufficientUriToSource(initializedComponent); |
| bool result = compiler.initializedFromDill; |
| new File.fromUri(output) |
| .writeAsBytesSync(util.postProcess(initializedComponent)); |
| int actuallyInvalidatedCount = compiler |
| .getFilteredInvalidatedImportUrisForTesting(invalidateUris) |
| ?.length ?? |
| 0; |
| if (result && actuallyInvalidatedCount < invalidateUris.length) { |
| Expect.fail("Expected at least ${invalidateUris.length} invalidated uris, " |
| "got $actuallyInvalidatedCount"); |
| } |
| |
| Component initializedFullComponent = |
| await compiler.computeDelta(fullComponent: true); |
| util.throwOnEmptyMixinBodies(initializedFullComponent); |
| util.throwOnInsufficientUriToSource(initializedFullComponent); |
| Expect.equals(initializedComponent.libraries.length, |
| initializedFullComponent.libraries.length); |
| Expect.equals(initializedComponent.uriToSource.length, |
| initializedFullComponent.uriToSource.length); |
| |
| for (Uri invalidateUri in invalidateUris) { |
| compiler.invalidate(invalidateUri); |
| } |
| |
| Component partialComponent = await compiler.computeDelta(); |
| util.throwOnEmptyMixinBodies(partialComponent); |
| util.throwOnInsufficientUriToSource(partialComponent); |
| actuallyInvalidatedCount = (compiler |
| .getFilteredInvalidatedImportUrisForTesting(invalidateUris) |
| ?.length ?? |
| 0); |
| if (actuallyInvalidatedCount < invalidateUris.length) { |
| Expect.fail("Expected at least ${invalidateUris.length} invalidated uris, " |
| "got $actuallyInvalidatedCount"); |
| } |
| |
| Component emptyComponent = await compiler.computeDelta(); |
| util.throwOnEmptyMixinBodies(emptyComponent); |
| util.throwOnInsufficientUriToSource(emptyComponent); |
| |
| List<Uri> fullLibUris = |
| initializedComponent.libraries.map((lib) => lib.importUri).toList(); |
| List<Uri> partialLibUris = |
| partialComponent.libraries.map((lib) => lib.importUri).toList(); |
| List<Uri> emptyLibUris = |
| emptyComponent.libraries.map((lib) => lib.importUri).toList(); |
| |
| Expect.isTrue(fullLibUris.length > partialLibUris.length || |
| partialLibUris.length == invalidateUris.length); |
| Expect.isTrue(partialLibUris.isNotEmpty || invalidateUris.isEmpty); |
| |
| Expect.isTrue(emptyLibUris.isEmpty); |
| |
| return result; |
| } |
| |
| class TestIncrementalCompiler extends IncrementalCompiler { |
| Set<Uri> invalidatedImportUrisForTesting; |
| final Uri entryPoint; |
| |
| /// Filter out the automatically added entryPoint, unless it's explicitly |
| /// specified as being invalidated. |
| /// Also filter out uris with "nonexisting.dart" in the name as synthetic |
| /// libraries are invalidated automatically too. |
| /// This is not perfect, but works for what it's currently used for. |
| Set<Uri> getFilteredInvalidatedImportUrisForTesting( |
| List<Uri> invalidatedUris) { |
| if (invalidatedImportUrisForTesting == null) return null; |
| |
| Set<String> invalidatedFilenames = |
| invalidatedUris.map((uri) => uri.pathSegments.last).toSet(); |
| Set<Uri> result = new Set<Uri>(); |
| for (Uri uri in invalidatedImportUrisForTesting) { |
| if (uri.pathSegments.last == "nonexisting.dart") continue; |
| if (invalidatedFilenames.contains(entryPoint.pathSegments.last) || |
| invalidatedFilenames.contains(uri.pathSegments.last)) result.add(uri); |
| } |
| |
| return result.isEmpty ? null : result; |
| } |
| |
| TestIncrementalCompiler(CompilerOptions options, this.entryPoint, |
| [Uri initializeFrom, bool outlineOnly]) |
| : super( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [entryPoint])), |
| initializeFrom, |
| outlineOnly); |
| |
| TestIncrementalCompiler.fromComponent(CompilerOptions options, |
| this.entryPoint, Component componentToInitializeFrom, [bool outlineOnly]) |
| : super.fromComponent( |
| new CompilerContext( |
| new ProcessedOptions(options: options, inputs: [entryPoint])), |
| componentToInitializeFrom, |
| outlineOnly); |
| |
| @override |
| void recordInvalidatedImportUrisForTesting(List<Uri> uris) { |
| invalidatedImportUrisForTesting = uris.isEmpty ? null : uris.toSet(); |
| } |
| |
| @override |
| void recordNonFullComponentForTesting(Component component) { |
| // It should at least contain the sdk. Slight smoke test. |
| if (!component.libraries |
| .map((lib) => lib.importUri.toString()) |
| .contains("dart:core")) { |
| throw "Loaders builder should contain the sdk, " |
| "but didn't even contain dart:core."; |
| } |
| } |
| |
| @override |
| Future<Component> computeDelta( |
| {List<Uri> entryPoints, |
| bool fullComponent = false, |
| bool simulateTransformer}) async { |
| Component result = await super |
| .computeDelta(entryPoints: entryPoints, fullComponent: fullComponent); |
| |
| // We should at least have the SDK builders available. Slight smoke test. |
| if (!dillLoadedData.loader.builders.keys |
| .map((uri) => uri.toString()) |
| .contains("dart:core")) { |
| throw "Loaders builder should contain the sdk, " |
| "but didn't even contain dart:core."; |
| } |
| |
| if (simulateTransformer == true) { |
| doSimulateTransformer(result); |
| } |
| return result; |
| } |
| } |
| |
| void doSimulateTransformer(Component c) { |
| for (Library lib in c.libraries) { |
| if (lib.fields |
| .where((f) => f.name.name == "lalala_SimulateTransformer") |
| .toList() |
| .isNotEmpty) continue; |
| Name fieldName = new Name("lalala_SimulateTransformer"); |
| Field field = new Field(fieldName, |
| isFinal: true, |
| reference: lib.reference.canonicalName |
| ?.getChildFromFieldWithName(fieldName) |
| ?.reference); |
| lib.addMember(field); |
| for (Class c in lib.classes) { |
| if (c.fields |
| .where((f) => f.name.name == "lalala_SimulateTransformer") |
| .toList() |
| .isNotEmpty) continue; |
| fieldName = new Name("lalala_SimulateTransformer"); |
| field = new Field(fieldName, |
| isFinal: true, |
| reference: c.reference.canonicalName |
| ?.getChildFromFieldWithName(fieldName) |
| ?.reference); |
| c.addMember(field); |
| } |
| } |
| } |