// 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. // @dart = 2.9 import 'dart:developer' show debugger; import 'dart:io' show Directory, File; import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart' show DiagnosticMessage, getMessageCodeObject; import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors; import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity; import "package:dev_compiler/src/kernel/target.dart" show DevCompilerTarget; import 'package:expect/expect.dart' show Expect; import 'package:front_end/src/api_prototype/compiler_options.dart' show CompilerOptions, parseExperimentalArguments, parseExperimentalFlags; import 'package:front_end/src/api_prototype/experimental_flags.dart'; import "package:front_end/src/api_prototype/memory_file_system.dart" show MemoryFileSystem; import 'package:front_end/src/base/nnbd_mode.dart' show NnbdMode; import 'package:front_end/src/base/processed_options.dart' show ProcessedOptions; import 'package:front_end/src/compute_platform_binaries_location.dart' show computePlatformBinariesLocation; import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; import 'package:front_end/src/fasta/fasta_codes.dart' show DiagnosticMessageFromJson, FormattedMessage; import 'package:front_end/src/fasta/incremental_compiler.dart' show IncrementalCompiler; import 'package:front_end/src/fasta/incremental_serializer.dart' show IncrementalSerializer; import 'package:front_end/src/fasta/kernel/kernel_api.dart'; import 'package:front_end/src/fasta/kernel/utils.dart' show ByteSink; import 'package:kernel/ast.dart'; import 'package:kernel/binary/ast_from_binary.dart' show BinaryBuilder; import 'package:kernel/binary/ast_to_binary.dart' show BinaryPrinter; import 'package:kernel/class_hierarchy.dart'; import 'package:kernel/kernel.dart' show Class, Component, EmptyStatement, Field, Library, LibraryDependency, Member, Name, Procedure, Supertype, TreeNode, Version; import 'package:kernel/target/targets.dart' show NoneTarget, LateLowering, Target, TargetFlags; import 'package:kernel/text/ast_to_text.dart' show Printer, componentToString; import "package:testing/testing.dart" show Chain, ChainContext, Expectation, Result, Step, TestDescription, runMe; import "package:vm/target/vm.dart" show VmTarget; import "package:yaml/yaml.dart" show YamlList, YamlMap, loadYamlNode; import 'binary_md_dill_reader.dart' show DillComparer; import "incremental_utils.dart" as util; import 'utils/io_utils.dart' show computeRepoDir; main([List arguments = const []]) => runMe(arguments, createContext, configurationPath: "../testing.json"); const Expectation ExpectationFileMismatch = const Expectation.fail("ExpectationFileMismatch"); const Expectation ExpectationFileMissing = const Expectation.fail("ExpectationFileMissing"); const Expectation MissingErrors = const Expectation.fail("MissingErrors"); const Expectation UnexpectedErrors = const Expectation.fail("UnexpectedErrors"); const Expectation MissingWarnings = const Expectation.fail("MissingWarnings"); const Expectation UnexpectedWarnings = const Expectation.fail("UnexpectedWarnings"); const Expectation ClassHierarchyError = const Expectation.fail("ClassHierarchyError"); const Expectation NeededDillMismatch = const Expectation.fail("NeededDillMismatch"); const Expectation IncrementalSerializationError = const Expectation.fail("IncrementalSerializationError"); const Expectation ContentDataMismatch = const Expectation.fail("ContentDataMismatch"); const Expectation MissingInitializationError = const Expectation.fail("MissingInitializationError"); const Expectation UnexpectedInitializationError = const Expectation.fail("UnexpectedInitializationError"); const Expectation ReachableLibrariesError = const Expectation.fail("ReachableLibrariesError"); const Expectation UriToSourceError = const Expectation.fail("UriToSourceError"); const Expectation MissingPlatformLibraries = const Expectation.fail("MissingPlatformLibraries"); const Expectation UnexpectedPlatformLibraries = const Expectation.fail("UnexpectedPlatformLibraries"); const Expectation UnexpectedRebuildBodiesOnly = const Expectation.fail("UnexpectedRebuildBodiesOnly"); const Expectation UnexpectedEntryToLibraryCount = const Expectation.fail("UnexpectedEntryToLibraryCount"); const Expectation LibraryCountMismatch = const Expectation.fail("LibraryCountMismatch"); const Expectation InitializedFromDillMismatch = const Expectation.fail("InitializedFromDillMismatch"); const Expectation NNBDModeMismatch = const Expectation.fail("NNBDModeMismatch"); Future createContext( Chain suite, Map environment) async { // Disable colors to ensure that expectation files are the same across // platforms and independent of stdin/stderr. colors.enableColors = false; return new Context(environment["updateExpectations"] == "true", environment["addDebugBreaks"] == "true"); } class Context extends ChainContext { final List steps = const [ const ReadTest(), const RunCompilations(), ]; final bool updateExpectations; /// Add a debug break (via dart:developers `debugger()` call) after each /// iteration (or 'world run') when doing a "new world test". final bool breakBetween; Context(this.updateExpectations, this.breakBetween); @override Future cleanUp(TestDescription description, Result result) async { await cleanupHelper?.outDir?.delete(recursive: true); cleanupHelper?.outDir = null; } TestData cleanupHelper; } class TestData { YamlMap map; Directory outDir; Uri loadedFrom; } class ReadTest extends Step { const ReadTest(); String get name => "read test"; Future> run( TestDescription description, Context context) async { Uri uri = description.uri; String contents = await new File.fromUri(uri).readAsString(); TestData data = new TestData(); data.loadedFrom = uri; 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 { const RunCompilations(); String get name => "run compilations"; Future> run(TestData data, Context context) async { Result result; YamlMap map = data.map; Set keys = new Set.from(map.keys.cast()); keys.remove("type"); switch (map["type"]) { case "basic": keys.removeAll(["sources", "entry", "invalidate"]); await basicTest( map["sources"], map["entry"], map["invalidate"], data.outDir, ); break; case "newworld": keys.removeAll([ "worlds", "modules", "omitPlatform", "target", "forceLateLoweringForTesting", "trackWidgetCreation", "incrementalSerialization" ]); result = await new NewWorldTest().newWorldTest( data, context, map["worlds"], map["modules"], map["omitPlatform"], map["target"], map["forceLateLoweringForTesting"] ?? false, map["trackWidgetCreation"] ?? false, map["incrementalSerialization"], ); break; default: throw "Unexpected type: ${map['type']}"; } if (keys.isNotEmpty) throw "Unknown toplevel keys: $keys"; return result ?? pass(data); } } Future basicTest(YamlMap sourceFiles, String entryPoint, YamlList invalidate, Directory outDir) async { Uri entryPointUri = outDir.uri.resolve(entryPoint); Set invalidateFilenames = invalidate == null ? new Set() : new Set.from(invalidate); List invalidateUris = []; 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 normalDillData = new File.fromUri(output).readAsBytesSync(); List initializedDillData = new File.fromUri(initializedOutput).readAsBytesSync(); checkIsEqual(normalDillData, initializedDillData); } Future>> createModules(Map module, final List sdkSummaryData, Target target, String sdkSummary) async { final Uri base = Uri.parse("org-dartlang-test:///"); final Uri sdkSummaryUri = base.resolve(sdkSummary); MemoryFileSystem fs = new MemoryFileSystem(base); fs.entityForUri(sdkSummaryUri).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> moduleResult = new Map>(); for (String moduleName in module.keys) { List moduleSources = []; 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(target: target, sdkSummary: sdkSummary); options.fileSystem = fs; options.sdkRoot = null; options.sdkSummary = sdkSummaryUri; 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 wantedLibs = []; 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) ..setMainMethodAndMode(null, false, c.mode); List resultBytes = util.postProcess(result); moduleResult[moduleName] = resultBytes; } return moduleResult; } class NewWorldTest { // These are fields in a class to make it easier to track down memory leaks // via the leak detector test. Component newestWholeComponent; Component sdk; Component component; Component component2; Component component3; String doStringReplacements(String input) { Version enableNonNullableVersion = experimentEnabledVersion[ExperimentalFlag.nonNullable]; String output = input.replaceAll("%NNBD_VERSION_MARKER%", "${enableNonNullableVersion.major}.${enableNonNullableVersion.minor}"); return output; } Future> newWorldTest( TestData data, Context context, List worlds, Map modules, bool omitPlatform, String targetName, bool forceLateLoweringForTesting, bool trackWidgetCreation, bool incrementalSerialization) async { final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); TargetFlags targetFlags = new TargetFlags( forceLateLoweringsForTesting: forceLateLoweringForTesting ? LateLowering.all : LateLowering.none, trackWidgetCreation: trackWidgetCreation); Target target = new VmTarget(targetFlags); String sdkSummary = "vm_platform_strong.dill"; if (targetName != null) { if (targetName == "None") { target = new NoneTarget(targetFlags); } else if (targetName == "DDC") { target = new DevCompilerTarget(targetFlags); sdkSummary = "ddc_platform.dill"; } else if (targetName == "VM") { // default. } else { throw "Unknown target name '$targetName'"; } } final Uri base = Uri.parse("org-dartlang-test:///"); final Uri sdkSummaryUri = base.resolve(sdkSummary); final Uri initializeFrom = base.resolve("initializeFrom.dill"); Uri platformUri = sdkRoot.resolve(sdkSummary); final List sdkSummaryData = await new File.fromUri(platformUri).readAsBytes(); List newestWholeComponentData; MemoryFileSystem fs; Map sourceFiles; CompilerOptions options; TestIncrementalCompiler compiler; IncrementalSerializer incrementalSerializer; Map> moduleData; Map moduleComponents; if (modules != null) { moduleData = await createModules(modules, sdkSummaryData, target, sdkSummary); sdk = newestWholeComponent = new Component(); new BinaryBuilder(sdkSummaryData, filename: null, disableLazyReading: false) .readComponent(newestWholeComponent); } int worldNum = 0; for (YamlMap world in worlds) { worldNum++; print("----------------"); print("World #$worldNum"); print("----------------"); List modulesToUse; if (world["modules"] != null) { moduleComponents ??= new Map(); sdk.adoptChildren(); for (Component c in moduleComponents.values) { c.adoptChildren(); } modulesToUse = []; 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(sdkSummaryUri).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.from(world["sources"]); } else { sourceFiles.addAll(new Map.from( world["sources"] ?? {})); } Uri packagesUri; for (String filename in sourceFiles.keys) { String data = sourceFiles[filename] ?? ""; Uri uri = base.resolve(filename); if (filename == ".packages") { packagesUri = uri; } else if (filename == ".dart_tool/package_config.json") { packagesUri = uri; } if (world["enableStringReplacement"] == true) { data = doStringReplacements(data); } fs.entityForUri(uri).writeAsStringSync(data); } if (world["dotPackagesFile"] != null) { packagesUri = base.resolve(world["dotPackagesFile"]); } if (brandNewWorld) { options = getOptions(target: target, sdkSummary: sdkSummary); options.fileSystem = fs; options.sdkRoot = null; options.sdkSummary = sdkSummaryUri; if (world["badSdk"] == true) { options.sdkSummary = sdkSummaryUri.resolve("nonexisting.dill"); } options.omitPlatform = omitPlatform != false; if (world["experiments"] != null) { Map flagsFromOptions = parseExperimentalArguments([world["experiments"]]); // Ensure that we run with non-nullable turned off even when the // flag is on by default. // TODO(johnniwinther,jensj): Update tests to explicitly opt out. flagsFromOptions['non-nullable'] ??= false; Map explicitExperimentalFlags = parseExperimentalFlags(flagsFromOptions, onError: (e) => throw "Error on parsing experiments flags: $e"); options.explicitExperimentalFlags = explicitExperimentalFlags; } else { options.explicitExperimentalFlags = { ExperimentalFlag.nonNullable: false }; } if (world["nnbdMode"] != null) { String nnbdMode = world["nnbdMode"]; switch (nnbdMode) { case "strong": options.nnbdMode = NnbdMode.Strong; break; default: throw "Not supported nnbd mode: $nnbdMode"; } } } if (packagesUri != null) { options.packagesFileUri = packagesUri; } bool gotError = false; final Set formattedErrors = Set(); bool gotWarning = false; final Set formattedWarnings = Set(); final Set seenDiagnosticCodes = Set(); options.onDiagnostic = (DiagnosticMessage message) { String code = getMessageCodeObject(message)?.name; if (code != null) seenDiagnosticCodes.add(code); 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 entries; if (world["entry"] is String) { entries = [base.resolve(world["entry"])]; } else { entries = []; List 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 (incrementalSerialization == true) { incrementalSerializer = new IncrementalSerializer(); } if (world["fromComponent"] == true) { compiler = new TestIncrementalCompiler.fromComponent( options, entries.first, (modulesToUse != null) ? sdk : newestWholeComponent, outlineOnly, incrementalSerializer); } else { compiler = new TestIncrementalCompiler(options, entries.first, initializeFrom, outlineOnly, incrementalSerializer); if (modulesToUse != null) { throw "You probably shouldn't do this! " "Any modules will have another sdk loaded!"; } } } List invalidated = []; 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 = 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})"; } } } } Result result = performErrorAndWarningCheck(world, data, gotError, formattedErrors, gotWarning, formattedWarnings); if (result != null) return result; if (world["expectInitializationError"] != null) { Set seenInitializationError = seenDiagnosticCodes.intersection({ "InitializeFromDillNotSelfContainedNoDump", "InitializeFromDillNotSelfContained", "InitializeFromDillUnknownProblem", "InitializeFromDillUnknownProblemNoDump", }); if (world["expectInitializationError"] == true) { if (seenInitializationError.isEmpty) { return new Result(data, MissingInitializationError, "Expected to see an initialization error but didn't."); } } else if (world["expectInitializationError"] == false) { if (seenInitializationError.isNotEmpty) { return new Result( data, UnexpectedInitializationError, "Expected not to see an initialization error but did: " "$seenInitializationError."); } } else { throw "Unsupported value for 'expectInitializationError': " "${world["expectInitializationError"]}"; } } util.throwOnEmptyMixinBodies(component); await util.throwOnInsufficientUriToSource(component, fileSystem: gotError ? null : fs); print("Compile took ${stopwatch.elapsedMilliseconds} ms"); Result contentResult = checkExpectedContent(world, component); if (contentResult != null) return contentResult.copyWithOutput(data); result = checkNeededDillLibraries( world, data, compiler.neededDillLibraries, base); if (result != null) return result; Result nnbdCheck = checkNNBDSettings(component); if (nnbdCheck != null) return nnbdCheck.copyWithOutput(data); if (!noFullComponent) { Set allLibraries = new Set(); for (Library lib in component.libraries) { computeAllReachableLibrariesFor(lib, allLibraries); } if (allLibraries.length != component.libraries.length) { return new Result( data, ReachableLibrariesError, "Expected for the reachable stuff to be equal to " "${component.libraries} but it was $allLibraries"); } Set tooMany = allLibraries.toSet() ..removeAll(component.libraries); if (tooMany.isNotEmpty) { return new Result( data, ReachableLibrariesError, "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; String actualSerialized = componentToStringSdkFiltered(component); print("*****\n\ncomponent:\n" "${actualSerialized}\n\n\n"); if (world["uriToSourcesDoesntInclude"] != null) { for (String filename in world["uriToSourcesDoesntInclude"]) { Uri uri = base.resolve(filename); if (component.uriToSource[uri] != null) { return new Result( data, UriToSourceError, "Expected no uriToSource for $uri but found " "${component.uriToSource[uri]}"); } } } if (world["uriToSourcesOnlyIncludes"] != null) { Set allowed = {}; for (String filename in world["uriToSourcesOnlyIncludes"]) { Uri uri = base.resolve(filename); allowed.add(uri); } for (Uri uri in component.uriToSource.keys) { // null is always there, so allow it implicitly. // Dart scheme uris too. if (uri == null || uri.scheme == "org-dartlang-sdk") continue; if (!allowed.contains(uri)) { return new Result( data, UriToSourceError, "Expected no uriToSource for $uri but found " "${component.uriToSource[uri]}"); } } } result = checkExpectFile(data, worldNum, "", context, actualSerialized); if (result != null) return result; if (world["skipClassHierarchyTest"] != true) { result = checkClassHierarchy(compiler, component, data, worldNum, context); if (result != null) return result; } int nonSyntheticLibraries = countNonSyntheticLibraries(component); int nonSyntheticPlatformLibraries = countNonSyntheticPlatformLibraries(component); int syntheticLibraries = countSyntheticLibraries(component); if (world["expectsPlatform"] == true) { if (nonSyntheticPlatformLibraries < 5) { return new Result( data, MissingPlatformLibraries, "Expected to have at least 5 platform libraries " "(actually, the entire sdk), " "but got $nonSyntheticPlatformLibraries."); } } else { if (nonSyntheticPlatformLibraries != 0) { return new Result( data, UnexpectedPlatformLibraries, "Expected to have 0 platform libraries " "but got $nonSyntheticPlatformLibraries."); } } if (world["expectedLibraryCount"] != null) { if (nonSyntheticLibraries - nonSyntheticPlatformLibraries != world["expectedLibraryCount"]) { return new Result( data, LibraryCountMismatch, "Expected ${world["expectedLibraryCount"]} non-synthetic " "libraries, got " "${nonSyntheticLibraries - nonSyntheticPlatformLibraries} " "(not counting platform libraries)"); } } if (world["expectedSyntheticLibraryCount"] != null) { if (syntheticLibraries != world["expectedSyntheticLibraryCount"]) { return new Result( data, LibraryCountMismatch, "Expected ${world["expectedSyntheticLibraryCount"]} synthetic " "libraries, got ${syntheticLibraries}"); } } if (world["expectsRebuildBodiesOnly"] != null) { bool didRebuildBodiesOnly = compiler.rebuildBodiesCount > 0; if (world["expectsRebuildBodiesOnly"] != didRebuildBodiesOnly) { return new Result( data, UnexpectedRebuildBodiesOnly, "Expected didRebuildBodiesOnly=" "${world["expectsRebuildBodiesOnly"]}, " "didRebuildBodiesOnly=${didRebuildBodiesOnly}."); } } if (!noFullComponent) { if (world["checkEntries"] != false) { List entryLib = component.libraries .where((Library lib) => entries.contains(lib.importUri) || entries.contains(lib.fileUri)) .toList(); if (entryLib.length != entries.length) { return new Result( data, UnexpectedEntryToLibraryCount, "Expected the entries to become libraries. " "Got ${entryLib.length} libraries for the expected " "${entries.length} entries."); } } } if (compiler.initializedFromDill != expectInitializeFromDill) { return new Result( data, InitializedFromDillMismatch, "Expected that initializedFromDill would be " "$expectInitializeFromDill but was " "${compiler.initializedFromDill}"); } if (incrementalSerialization == true && compiler.initializedFromDill) { Expect.isTrue(compiler.initializedIncrementalSerializer); } else { Expect.isFalse(compiler.initializedIncrementalSerializer); } if (world["checkInvalidatedFiles"] != false) { Set 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"]); } } Result> serializationResult = checkIncrementalSerialization( incrementalSerialization, component, incrementalSerializer, world); if (!serializationResult.isPass) { return serializationResult.copyWithOutput(data); } List incrementalSerializationBytes = serializationResult.output; Set prevFormattedErrors = formattedErrors.toSet(); Set prevFormattedWarnings = formattedWarnings.toSet(); clearPrevErrorsEtc() { gotError = false; formattedErrors.clear(); gotWarning = false; formattedWarnings.clear(); } if (!noFullComponent) { clearPrevErrorsEtc(); component2 = await compiler.computeDelta( entryPoints: entries, fullComponent: true, simulateTransformer: world["simulateTransformer"]); Result result = performErrorAndWarningCheck(world, data, gotError, formattedErrors, gotWarning, formattedWarnings); if (result != null) return result; List thisWholeComponent = util.postProcess(component2); print("*****\n\ncomponent2:\n" "${componentToStringSdkFiltered(component2)}\n\n\n"); checkIsEqual(newestWholeComponentData, thisWholeComponent); checkErrorsAndWarnings(prevFormattedErrors, formattedErrors, prevFormattedWarnings, formattedWarnings); newestWholeComponent = component2; Result> serializationResult = checkIncrementalSerialization( incrementalSerialization, component2, incrementalSerializer, world); if (!serializationResult.isPass) { return serializationResult.copyWithOutput(data); } List incrementalSerializationBytes2 = serializationResult.output; if ((incrementalSerializationBytes == null && incrementalSerializationBytes2 != null) || (incrementalSerializationBytes != null && incrementalSerializationBytes2 == null)) { return new Result( data, IncrementalSerializationError, "Incremental serialization gave results in one instance, " "but not another."); } if (incrementalSerializationBytes != null) { checkIsEqual( incrementalSerializationBytes, incrementalSerializationBytes2); } } if (world["expressionCompilation"] != null) { List compilations; if (world["expressionCompilation"] is List) { compilations = world["expressionCompilation"]; } else { compilations = [world["expressionCompilation"]]; } int expressionCompilationNum = 0; for (Map compilation in compilations) { expressionCompilationNum++; clearPrevErrorsEtc(); bool expectErrors = compilation["errors"] ?? false; bool expectWarnings = compilation["warnings"] ?? false; Uri uri = base.resolve(compilation["uri"]); String expression = compilation["expression"]; Procedure procedure = await compiler.compileExpression( expression, {}, [], "debugExpr", uri); if (gotError && !expectErrors) { return new Result(data, UnexpectedErrors, "Got error(s) on expression compilation: ${formattedErrors}."); } else if (!gotError && expectErrors) { return new Result( data, MissingErrors, "Didn't get any errors."); } if (gotWarning && !expectWarnings) { return new Result( data, UnexpectedWarnings, "Got warning(s) on expression compilation: " "${formattedWarnings}."); } else if (!gotWarning && expectWarnings) { return new Result( data, MissingWarnings, "Didn't get any warnings."); } Result result = checkExpectFile( data, worldNum, ".expression.$expressionCompilationNum", context, nodeToString(procedure)); if (result != null) return result; } } if (!noFullComponent && incrementalSerialization == true) { // Do compile from scratch and compare. clearPrevErrorsEtc(); TestIncrementalCompiler compilerFromScratch; IncrementalSerializer incrementalSerializer2; if (incrementalSerialization == true) { incrementalSerializer2 = new IncrementalSerializer(); } if (world["fromComponent"] == true || modulesToUse != null) { compilerFromScratch = new TestIncrementalCompiler.fromComponent( options, entries.first, sdk, outlineOnly, incrementalSerializer2); } else { compilerFromScratch = new TestIncrementalCompiler(options, entries.first, null, outlineOnly, incrementalSerializer2); } if (modulesToUse != null) { compilerFromScratch.setModulesToLoadOnNextComputeDelta(modulesToUse); compilerFromScratch.invalidateAllSources(); compilerFromScratch.trackNeededDillLibraries = true; } Stopwatch stopwatch = new Stopwatch()..start(); component3 = await compilerFromScratch.computeDelta( entryPoints: entries, simulateTransformer: world["simulateTransformer"]); compilerFromScratch = null; Result result = performErrorAndWarningCheck(world, data, gotError, formattedErrors, gotWarning, formattedWarnings); if (result != null) return result; util.throwOnEmptyMixinBodies(component3); await util.throwOnInsufficientUriToSource(component3); print("Compile took ${stopwatch.elapsedMilliseconds} ms"); util.postProcess(component3); print("*****\n\ncomponent3:\n" "${componentToStringSdkFiltered(component3)}\n\n\n"); checkErrorsAndWarnings(prevFormattedErrors, formattedErrors, prevFormattedWarnings, formattedWarnings); Result> serializationResult = checkIncrementalSerialization( incrementalSerialization, component3, incrementalSerializer2, world); if (!serializationResult.isPass) { return serializationResult.copyWithOutput(data); } List incrementalSerializationBytes3 = serializationResult.output; if ((incrementalSerializationBytes == null && incrementalSerializationBytes3 != null) || (incrementalSerializationBytes != null && incrementalSerializationBytes3 == null)) { return new Result( data, IncrementalSerializationError, "Incremental serialization gave results in one instance, " "but not another."); } if (incrementalSerializationBytes != null) { if (world["brandNewIncrementalSerializationAllowDifferent"] == true) { // Don't check for equality when we allow it to be different // (e.g. when the old one contains more, and the new one doesn't). } else { checkIsEqual( incrementalSerializationBytes, incrementalSerializationBytes3); } newestWholeComponentData = incrementalSerializationBytes; } } component = null; component2 = null; component3 = null; if (context.breakBetween) { debugger(); print("Continuing after debug break"); } } return new Result.pass(data); } } Result checkNNBDSettings(Component component) { NonNullableByDefaultCompiledMode mode = component.mode; if (mode == NonNullableByDefaultCompiledMode.Invalid) return null; for (Library lib in component.libraries) { if (mode == lib.nonNullableByDefaultCompiledMode) continue; if (mode == NonNullableByDefaultCompiledMode.Agnostic) { // Component says agnostic but the library isn't => Error! return new Result( null, NNBDModeMismatch, "Component mode was agnostic but ${lib.importUri} had mode " "${lib.nonNullableByDefaultCompiledMode}."); } // Agnostic can be mixed with everything. if (lib.nonNullableByDefaultCompiledMode == NonNullableByDefaultCompiledMode.Agnostic) continue; if (mode == NonNullableByDefaultCompiledMode.Strong || lib.nonNullableByDefaultCompiledMode == NonNullableByDefaultCompiledMode.Strong) { // Non agnostic and one (but not both) are strong => error. return new Result( null, NNBDModeMismatch, "Component mode was $mode but ${lib.importUri} had mode " "${lib.nonNullableByDefaultCompiledMode}."); } } return null; } Result checkExpectFile(TestData data, int worldNum, String extraUriString, Context context, String actualSerialized) { Uri uri = data.loadedFrom.resolve(data.loadedFrom.pathSegments.last + ".world.$worldNum${extraUriString}.expect"); String expected; File file = new File.fromUri(uri); if (file.existsSync()) { expected = file.readAsStringSync(); } if (expected != actualSerialized) { if (context.updateExpectations) { file.writeAsStringSync(actualSerialized); } else { String extra = ""; if (expected == null) extra = "Expect file did not exist.\n"; return new Result( data, expected == null ? ExpectationFileMissing : ExpectationFileMismatch, "${extra}Unexpected serialized representation. " "Fix or update $uri to contain the below:\n\n" "$actualSerialized", autoFixCommand: "updateExpectations=true", canBeFixWithUpdateExpectations: true); } } return null; } /// Check that the class hierarchy is up-to-date with reality. /// /// This has the option to do expect files, but it's disabled by default /// while we're trying to figure out if it's useful or not. Result checkClassHierarchy(TestIncrementalCompiler compiler, Component component, TestData data, int worldNum, Context context, {bool checkExpectFile: false}) { ClassHierarchy classHierarchy = compiler.getClassHierarchy(); if (classHierarchy is! ClosedWorldClassHierarchy) { return new Result( data, ClassHierarchyError, "Expected the class hierarchy to be ClosedWorldClassHierarchy " "but it wasn't. It was ${classHierarchy.runtimeType}"); } List classHierarchyData = (classHierarchy as ClosedWorldClassHierarchy).getTestingClassInfo(); Map classHierarchyMap = new Map(); for (ForTestingClassInfo info in classHierarchyData) { if (classHierarchyMap[info.classNode] != null) { return new Result( data, ClassHierarchyError, "Two entries for ${info.classNode}"); } classHierarchyMap[info.classNode] = info; } StringBuffer sb = new StringBuffer(); for (Library library in component.libraries) { if (library.importUri.scheme == "dart") continue; sb.writeln("Library ${library.importUri}"); for (Class c in library.classes) { sb.writeln(" - Class ${c.name}"); Set checkedSupertypes = {}; Result checkSupertype(Supertype supertype) { if (supertype == null) return null; Class superclass = supertype.classNode; if (checkedSupertypes.add(superclass)) { Supertype asSuperClass = classHierarchy.getClassAsInstanceOf(c, superclass); if (asSuperClass == null) { return new Result(data, ClassHierarchyError, "${superclass} not found as a superclass of $c"); } Result result = checkSupertype(superclass.supertype); if (result != null) return result; result = checkSupertype(superclass.mixedInType); if (result != null) return result; for (Supertype interface in superclass.implementedTypes) { result = checkSupertype(interface); if (result != null) return result; } } return null; } Result result = checkSupertype(c.asThisSupertype); if (result != null) return result; ForTestingClassInfo info = classHierarchyMap[c]; if (info == null) { return new Result(data, ClassHierarchyError, "Didn't find any class hierarchy info for $c"); } if (info.lazyDeclaredGettersAndCalls != null) { sb.writeln(" - lazyDeclaredGettersAndCalls:"); for (Member member in info.lazyDeclaredGettersAndCalls) { sb.writeln(" - ${member.name.text}"); } // Expect these to be the same as in the class. Set members = info.lazyDeclaredGettersAndCalls.toSet(); for (Field f in c.fields) { if (f.isStatic) continue; if (!members.remove(f)) { return new Result( data, ClassHierarchyError, "Didn't find ${f.name.text} in lazyDeclaredGettersAndCalls " "for ${c.name} in ${library.importUri}"); } } for (Procedure p in c.procedures) { if (p.isStatic) continue; if (p.isSetter) continue; if (!members.remove(p)) { return new Result( data, ClassHierarchyError, "Didn't find ${p.name.text} in lazyDeclaredGettersAndCalls " "for ${c.name} in ${library.importUri}"); } } if (members.isNotEmpty) { return new Result( data, ClassHierarchyError, "Still have ${members.map((m) => m.name.text)} left " "for ${c.name} in ${library.importUri}"); } } if (info.lazyDeclaredSetters != null) { sb.writeln(" - lazyDeclaredSetters:"); for (Member member in info.lazyDeclaredSetters) { sb.writeln(" - ${member.name.text}"); } // Expect these to be the same as in the class. Set members = info.lazyDeclaredSetters.toSet(); for (Field f in c.fields) { if (f.isStatic) continue; if (!f.hasSetter) continue; if (!members.remove(f)) { return new Result(data, ClassHierarchyError, "Didn't find $f in lazyDeclaredSetters for $c"); } } for (Procedure p in c.procedures) { if (p.isStatic) continue; if (!p.isSetter) continue; if (!members.remove(p)) { return new Result(data, ClassHierarchyError, "Didn't find $p in lazyDeclaredSetters for $c"); } } if (members.isNotEmpty) { return new Result( data, ClassHierarchyError, "Still have ${members.map((m) => m.name.text)} left " "for ${c.name} in ${library.importUri}"); } } if (info.lazyImplementedGettersAndCalls != null) { sb.writeln(" - lazyImplementedGettersAndCalls:"); for (Member member in info.lazyImplementedGettersAndCalls) { sb.writeln(" - ${member.name.text}"); } } if (info.lazyImplementedSetters != null) { sb.writeln(" - lazyImplementedSetters:"); for (Member member in info.lazyImplementedSetters) { sb.writeln(" - ${member.name.text}"); } } if (info.lazyInterfaceGettersAndCalls != null) { sb.writeln(" - lazyInterfaceGettersAndCalls:"); for (Member member in info.lazyInterfaceGettersAndCalls) { sb.writeln(" - ${member.name.text}"); } } if (info.lazyInterfaceSetters != null) { sb.writeln(" - lazyInterfaceSetters:"); for (Member member in info.lazyInterfaceSetters) { sb.writeln(" - ${member.name.text}"); } } } } if (checkExpectFile) { String actualClassHierarchy = sb.toString(); Uri uri = data.loadedFrom.resolve(data.loadedFrom.pathSegments.last + ".world.$worldNum.class_hierarchy.expect"); String expected; File file = new File.fromUri(uri); if (file.existsSync()) { expected = file.readAsStringSync(); } if (expected != actualClassHierarchy) { if (context.updateExpectations) { file.writeAsStringSync(actualClassHierarchy); } else { String extra = ""; if (expected == null) extra = "Expect file did not exist.\n"; return new Result( data, ClassHierarchyError, "${extra}Unexpected serialized representation. " "Fix or update $uri to contain the below:\n\n" "$actualClassHierarchy"); } } } return null; } void checkErrorsAndWarnings( Set prevFormattedErrors, Set formattedErrors, Set prevFormattedWarnings, Set formattedWarnings) { 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}."); } } Result> checkIncrementalSerialization( bool incrementalSerialization, Component component, IncrementalSerializer incrementalSerializer, YamlMap world) { if (incrementalSerialization == true) { Component c = new Component(nameRoot: component.root) ..setMainMethodAndMode(null, false, component.mode); c.libraries.addAll(component.libraries); c.uriToSource.addAll(component.uriToSource); Map> originalContent = buildMapOfContent(c); ByteSink sink = new ByteSink(); int librariesBefore = c.libraries.length; incrementalSerializer.writePackagesToSinkAndTrimComponent(c, sink); int librariesAfter = c.libraries.length; if (librariesAfter > librariesBefore) { return new Result>(null, IncrementalSerializationError, "Incremental serialization added libraries!"); } if (librariesBefore == librariesAfter && world["incrementalSerializationDoesWork"] == true) { return new Result>(null, IncrementalSerializationError, "Incremental serialization didn't remove any libraries!"); } if (librariesAfter < librariesBefore && sink.builder.isEmpty) { return new Result>( null, IncrementalSerializationError, "Incremental serialization didn't output any bytes, " "but did remove libraries"); } else if (librariesAfter == librariesBefore && !sink.builder.isEmpty) { return new Result>( null, IncrementalSerializationError, "Incremental serialization did output bytes, " "but didn't remove libraries"); } if (librariesAfter < librariesBefore) { // If we actually did incrementally serialize anything, check the output! BinaryPrinter printer = new BinaryPrinter(sink); printer.writeComponentFile(c); List bytes = sink.builder.takeBytes(); // Load the bytes back in. Component loadedComponent = new Component(); new BinaryBuilder(bytes, filename: null).readComponent(loadedComponent); // Check that it doesn't contain anything we said it shouldn't. if (world["serializationShouldNotInclude"] is List) { List serializationShouldNotInclude = world["serializationShouldNotInclude"]; Set includedImportUris = loadedComponent.libraries.map((l) => l.importUri).toSet(); for (String uriString in serializationShouldNotInclude) { Uri uri = Uri.parse(uriString); if (includedImportUris.contains(uri)) { return new Result>( null, IncrementalSerializationError, "Incremental serialization shouldn't include " "$uriString but did."); } } } // Check that it contains at least what we want. Map> afterContent = buildMapOfContent(loadedComponent); // Remove any keys in afterContent not in the original as the written // one is allowed to contain *more*. Set newKeys = afterContent.keys.toSet() ..removeAll(originalContent.keys); for (String key in newKeys) { afterContent.remove(key); } Result result = checkExpectedContentData(afterContent, originalContent); if (result != null) return result.copyWithOutput>(null); // Check that the result is self-contained. result = checkSelfContained(loadedComponent); if (result != null) return result.copyWithOutput>(null); return new Result>.pass(bytes); } } return new Result>.pass(null); } Result checkSelfContained(Component component) { Set got = new Set.from(component.libraries); for (Library lib in component.libraries) { for (LibraryDependency dependency in lib.dependencies) { if (dependency.importedLibraryReference.node == null || !got.contains(dependency.targetLibrary)) { if (dependency.importedLibraryReference.canonicalName .toString() .startsWith("root::dart:")) { continue; } return Result( null, IncrementalSerializationError, "Component didn't contain ${dependency.importedLibraryReference} " "and it should have."); } } } return null; } void computeAllReachableLibrariesFor(Library lib, Set allLibraries) { Set libraries = new Set(); List workList = []; allLibraries.add(lib); libraries.add(lib); workList.add(lib); while (workList.isNotEmpty) { Library library = workList.removeLast(); for (LibraryDependency dependency in library.dependencies) { if (dependency.targetLibrary.importUri.scheme == "dart") continue; if (libraries.add(dependency.targetLibrary)) { workList.add(dependency.targetLibrary); allLibraries.add(dependency.targetLibrary); } } } } Result checkExpectedContent(YamlMap world, Component component) { if (world["expectedContent"] != null) { Map> actualContent = buildMapOfContent(component); Map expectedContent = world["expectedContent"]; return checkExpectedContentData(actualContent, expectedContent); } return null; } Result checkExpectedContentData( Map> actualContent, Map expectedContent) { Result createFailureResult() { return new Result( null, ContentDataMismatch, "Expected and actual content not the same.\n" "Expected $expectedContent.\n" "Got $actualContent"); } if (actualContent.length != expectedContent.length) { return createFailureResult(); } Set missingKeys = actualContent.keys.toSet() ..removeAll(expectedContent.keys); if (missingKeys.isNotEmpty) { return createFailureResult(); } for (String key in expectedContent.keys) { Set expected = new Set.from(expectedContent[key]); Set actual = actualContent[key].toSet(); if (expected.length != actual.length) { return createFailureResult(); } actual.removeAll(expected); if (actual.isNotEmpty) { return createFailureResult(); } } return null; } Map> buildMapOfContent(Component component) { Map> actualContent = new Map>(); for (Library lib in component.libraries) { Set libContent = actualContent[lib.importUri.toString()] = new Set(); for (Class c in lib.classes) { libContent.add("Class ${c.name}"); } for (Procedure p in lib.procedures) { libContent.add("Procedure ${p.name.text}"); } for (Field f in lib.fields) { libContent.add("Field ${f.name.text}"); } } return actualContent; } Result checkNeededDillLibraries( YamlMap world, TestData data, Set neededDillLibraries, Uri base) { if (world["neededDillLibraries"] != null) { List actualContent = []; for (Library lib in neededDillLibraries) { if (lib.importUri.scheme == "dart") continue; actualContent.add(lib.importUri); } List expectedContent = []; for (String entry in world["neededDillLibraries"]) { expectedContent.add(base.resolve(entry)); } Result createFailureResult() { return new Result( data, NeededDillMismatch, "Expected and actual content not the same.\n" "Expected $expectedContent.\n" "Got $actualContent"); } if (actualContent.length != expectedContent.length) { return createFailureResult(); } Set notInExpected = actualContent.toSet().difference(expectedContent.toSet()); Set notInActual = expectedContent.toSet().difference(actualContent.toSet()); if (notInExpected.isNotEmpty) { return createFailureResult(); } if (notInActual.isNotEmpty) { return createFailureResult(); } } return null; } String nodeToString(TreeNode node) { StringBuffer buffer = new StringBuffer(); new Printer(buffer, syntheticNames: new NameSystem()).writeNode(node); return '$buffer'; } String componentToStringSdkFiltered(Component component) { Component c = new Component(); List dartUris = []; for (Library lib in component.libraries) { if (lib.importUri.scheme == "dart") { dartUris.add(lib.importUri); } else { c.libraries.add(lib); } } c.setMainMethodAndMode(component.mainMethodName, true, component.mode); c.problemsAsJson = component.problemsAsJson; 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; } Result performErrorAndWarningCheck( YamlMap world, TestData data, bool gotError, Set formattedErrors, bool gotWarning, Set formattedWarnings) { if (world["errors"] == true && !gotError) { return new Result( data, MissingErrors, "Expected error, but didn't get any."); } else if (world["errors"] != true && gotError) { return new Result( data, UnexpectedErrors, "Got unexpected error(s): $formattedErrors."); } if (world["warnings"] == true && !gotWarning) { return new Result( data, MissingWarnings, "Expected warning, but didn't get any."); } else if (world["warnings"] != true && gotWarning) { return new Result(data, UnexpectedWarnings, "Got unexpected warnings(s): $formattedWarnings."); } return null; } void checkIsEqual(List a, List b) { int length = a.length; if (b.length < length) { length = b.length; } for (int i = 0; i < length; ++i) { if (a[i] != b[i]) { print("Data differs at byte ${i + 1}."); StringBuffer message = new StringBuffer(); message.writeln("Data differs at byte ${i + 1}."); message.writeln(""); message.writeln("Will try to find more useful information:"); final String repoDir = computeRepoDir(); File binaryMd = new File("$repoDir/pkg/kernel/binary.md"); String binaryMdContent = binaryMd.readAsStringSync(); DillComparer dillComparer = new DillComparer(); if (dillComparer.compare(a, b, binaryMdContent, message)) { message.writeln( "Somehow the two different byte-lists compared to the same."); } Expect.fail(message.toString()); } } Expect.equals(a.length, b.length); } CompilerOptions getOptions({Target target, String sdkSummary}) { target ??= new VmTarget(new TargetFlags()); sdkSummary ??= 'vm_platform_strong.dill'; final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true); CompilerOptions options = new CompilerOptions() ..sdkRoot = sdkRoot ..target = target ..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(sdkSummary) ..environmentDefines = const {}; return options; } Future normalCompile(Uri input, Uri output, {CompilerOptions options}) async { options ??= getOptions(); TestIncrementalCompiler compiler = new TestIncrementalCompiler(options, input); List bytes = await normalCompileToBytes(input, options: options, compiler: compiler); new File.fromUri(output).writeAsBytesSync(bytes); return compiler.initializedFromDill; } Future> normalCompileToBytes(Uri input, {CompilerOptions options, IncrementalCompiler compiler}) async { Component component = await normalCompileToComponent(input, options: options, compiler: compiler); return util.postProcess(component); } Future normalCompileToComponent(Uri input, {CompilerOptions options, IncrementalCompiler compiler}) async { Component component = await normalCompilePlain(input, options: options, compiler: compiler); util.throwOnEmptyMixinBodies(component); await util.throwOnInsufficientUriToSource(component); return component; } Future normalCompilePlain(Uri input, {CompilerOptions options, IncrementalCompiler compiler}) async { options ??= getOptions(); compiler ??= new TestIncrementalCompiler(options, input); return await compiler.computeDelta(); } Future initializedCompile( Uri input, Uri output, Uri initializeWith, List 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); await 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); await 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); await 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); await util.throwOnInsufficientUriToSource(emptyComponent); List fullLibUris = initializedComponent.libraries.map((lib) => lib.importUri).toList(); List partialLibUris = partialComponent.libraries.map((lib) => lib.importUri).toList(); List 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 invalidatedImportUrisForTesting; int rebuildBodiesCount; 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 getFilteredInvalidatedImportUrisForTesting( List invalidatedUris) { if (invalidatedImportUrisForTesting == null) return null; Set invalidatedFilenames = invalidatedUris.map((uri) => uri.pathSegments.last).toSet(); Set result = new Set(); for (Uri uri in invalidatedImportUrisForTesting) { if (uri.pathSegments.isNotEmpty && 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, IncrementalSerializer incrementalSerializer]) : super( new CompilerContext( new ProcessedOptions(options: options, inputs: [entryPoint])), initializeFrom, outlineOnly, incrementalSerializer); TestIncrementalCompiler.fromComponent(CompilerOptions options, this.entryPoint, Component componentToInitializeFrom, [bool outlineOnly, IncrementalSerializer incrementalSerializer]) : super.fromComponent( new CompilerContext( new ProcessedOptions(options: options, inputs: [entryPoint])), componentToInitializeFrom, outlineOnly, incrementalSerializer); @override void recordInvalidatedImportUrisForTesting(List 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 void recordRebuildBodiesCountForTesting(int count) { rebuildBodiesCount = count; } @override Future computeDelta( {List 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 recordTemporaryFileForTesting(Uri uri) { File f = new File.fromUri(uri); if (f.existsSync()) f.deleteSync(); } } void doSimulateTransformer(Component c) { for (Library lib in c.libraries) { if (lib.fields .where((f) => f.name.text == "unique_SimulateTransformer") .toList() .isNotEmpty) continue; Name fieldName = new Name("unique_SimulateTransformer"); Field field = new Field.immutable(fieldName, isFinal: true, getterReference: lib.reference.canonicalName ?.getChildFromFieldWithName(fieldName) ?.reference, fileUri: lib.fileUri); lib.addField(field); for (Class c in lib.classes) { if (c.fields .where((f) => f.name.text == "unique_SimulateTransformer") .toList() .isNotEmpty) continue; fieldName = new Name("unique_SimulateTransformer"); field = new Field.immutable(fieldName, isFinal: true, getterReference: c.reference.canonicalName ?.getChildFromFieldWithName(fieldName) ?.reference, fileUri: c.fileUri); c.addField(field); } } }