| // 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:convert' show jsonDecode; |
| 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/messages/severity.dart' show Severity; |
| import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors; |
| import 'package:compiler/src/kernel/dart2js_target.dart' show Dart2jsTarget; |
| 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' |
| show ExperimentalFlag; |
| import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart' |
| show IncrementalCompilerResult; |
| import "package:front_end/src/api_prototype/memory_file_system.dart" |
| show MemoryFileSystem, MemoryFileSystemEntity; |
| import 'package:front_end/src/base/compiler_context.dart' show CompilerContext; |
| import 'package:front_end/src/base/incremental_compiler.dart' |
| show AdvancedInvalidationResult, IncrementalCompiler, RecorderForTesting; |
| import 'package:front_end/src/base/incremental_serializer.dart' |
| show IncrementalSerializer; |
| 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/codes/cfe_codes.dart' |
| show DiagnosticMessageFromJson, FormattedMessage; |
| import 'package:front_end/src/compute_platform_binaries_location.dart' |
| show computePlatformBinariesLocation, computePlatformDillName; |
| import 'package:front_end/src/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' |
| show ClassHierarchy, ClosedWorldClassHierarchy, ForTestingClassInfo; |
| import 'package:kernel/src/equivalence.dart' |
| show |
| EquivalenceResult, |
| EquivalenceStrategy, |
| EquivalenceVisitor, |
| checkEquivalence; |
| import 'package:kernel/target/targets.dart' |
| show |
| LateLowering, |
| NoneTarget, |
| Target, |
| TargetFlags, |
| TestTargetFlags, |
| TestTargetWrapper; |
| import 'package:kernel/text/ast_to_text.dart' |
| show NameSystem, Printer, componentToString; |
| import "package:testing/testing.dart" |
| show |
| Chain, |
| ChainContext, |
| Expectation, |
| ExpectationSet, |
| Result, |
| Step, |
| TestDescription; |
| import "package:vm/modular/target/vm.dart" show VmTarget; |
| import "package:yaml/yaml.dart" show YamlMap, loadYamlNode; |
| |
| import 'binary_md_dill_reader.dart' show DillComparer; |
| import 'fasta/suite_utils.dart'; |
| import 'fasta/testing/environment_keys.dart'; |
| import "incremental_utils.dart" as util; |
| import 'test_utils.dart'; |
| import 'testing_utils.dart' show checkEnvironment; |
| import 'utils/io_utils.dart' show computeRepoDir; |
| import 'utils/symbolic_language_versions.dart'; |
| import 'utils/values.dart'; |
| |
| void main([List<String> arguments = const []]) => internalMain(createContext, |
| arguments: arguments, |
| displayName: "incremental suite", |
| configurationPath: "../testing.json"); |
| |
| /// Top level yaml properties for an incremental test. |
| class TestProperties { |
| /// Value of [type] for selecting a [basicTest]. |
| static const String type_basic = 'basic'; |
| |
| /// Value of [type] for selecting a [NewWorldTest]. |
| static const String type_newworld = 'newworld'; |
| |
| /// This selects the type of test. |
| /// |
| /// [type_basic] runs a [basicTest] with additional properties defined in |
| /// [BasicTestProperties]. |
| /// |
| /// [type_newworld] runs a [NewWorldTest] with additional properties defined |
| /// in [NewWorldTestProperties]. |
| static const Property<String> type = Property.required( |
| 'type', StringValue(options: {type_basic, type_newworld})); |
| } |
| |
| /// Additional yaml properties for a [basicTest]. |
| // TODO(johnniwinther,jensj): Document all properties. |
| class BasicTestProperties { |
| static const Property<Map<String, String>> sources = |
| Property.required('sources', MapValue(StringValue())); |
| |
| static const Property<String> entry = |
| Property.required('entry', StringValue()); |
| |
| static const Property<List<String>?> invalidate = |
| Property.optional('invalidate', ListValue(StringValue())); |
| } |
| |
| /// Additional yaml properties for a [NewWorldTest]. |
| // TODO(johnniwinther,jensj): Document all properties. |
| class NewWorldTestProperties { |
| static const Property<List<World>> worlds = |
| Property.required('worlds', ListValue(CustomValue(World.create))); |
| |
| static const Property<Map<String, Map<String, String>>?> modules = |
| Property.optional('modules', MapValue(MapValue(StringValue()))); |
| |
| static const Property<bool> omitPlatform = |
| Property.optional('omitPlatform', BoolValue(), defaultValue: true); |
| |
| static const Property<bool> forceLateLoweringForTesting = Property.optional( |
| 'forceLateLoweringForTesting', BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool> trackWidgetCreation = Property.optional( |
| 'trackWidgetCreation', BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool> incrementalSerialization = Property.optional( |
| 'incrementalSerialization', BoolValue(), |
| defaultValue: false); |
| |
| static const String nnbdMode_weak = 'weak'; |
| |
| static const Property<String?> nnbdMode = |
| Property.optional('nnbdMode', StringValue(options: {nnbdMode_weak})); |
| |
| static const String target_none = 'none'; |
| static const String target_dartdevc = 'dartdevc'; |
| static const String target_dart2js = 'dart2js'; |
| static const String target_vm = 'vm'; |
| |
| static const Property<String?> target = Property.optional( |
| 'target', |
| StringValue( |
| options: {target_none, target_dartdevc, target_dart2js, target_vm})); |
| } |
| |
| /// Yaml properties for a [World] within a [NewWorldTest]. |
| // TODO(johnniwinther,jensj): Document all properties. |
| class WorldProperties { |
| static const Property<List<String>?> modules = |
| Property.optional('modules', ListValue(StringValue())); |
| |
| static const String worldType_updated = 'updated'; |
| |
| static const Property<String?> worldType = |
| Property.optional('worldType', StringValue(options: {worldType_updated})); |
| |
| static const Property<bool> noFullComponent = |
| Property.optional('noFullComponent', BoolValue(), defaultValue: false); |
| |
| static const Property<bool?> expectInitializeFromDill = |
| Property.optional('expectInitializeFromDill', BoolValue()); |
| |
| static const Property<Map<String, String>> sources = Property.optional( |
| 'sources', MapValue(StringValue()), |
| defaultValue: const {}); |
| |
| static const Property<bool> badSdk = |
| Property.optional('badSdk', BoolValue(), defaultValue: false); |
| |
| static const Property<bool> enableStringReplacement = Property.optional( |
| 'enableStringReplacement', BoolValue(), |
| defaultValue: false); |
| |
| static const Property<String?> packageConfigFile = |
| Property.optional('packageConfigFile', StringValue()); |
| |
| static const Property<String?> experiments = |
| Property.optional("experiments", StringValue()); |
| |
| static const String nnbdMode_weak = 'weak'; |
| |
| static const Property<String?> nnbdMode = |
| Property.optional("nnbdMode", StringValue(options: {nnbdMode_weak})); |
| |
| static const Property<List<String>> entry = Property.required( |
| 'entry', ListValue(StringValue(), supportSingleton: true)); |
| |
| static const Property<bool> outlineOnly = |
| Property.optional("outlineOnly", BoolValue(), defaultValue: false); |
| |
| static const Property<bool> skipOutlineBodyCheck = Property.optional( |
| "skipOutlineBodyCheck", BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool> fromComponent = |
| Property.optional("fromComponent", BoolValue(), defaultValue: false); |
| |
| static const Property<List<String>?> invalidate = |
| Property.optional("invalidate", ListValue(StringValue())); |
| |
| static const Property<bool> simulateTransformer = Property.optional( |
| "simulateTransformer", BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool?> expectInitializationError = |
| Property.optional("expectInitializationError", BoolValue()); |
| |
| static const Property<bool> compareToPrevious = |
| Property.optional("compareToPrevious", BoolValue(), defaultValue: false); |
| |
| static const Property<List<String>?> uriToSourcesDoesntInclude = |
| Property.optional("uriToSourcesDoesntInclude", ListValue(StringValue())); |
| |
| static const Property<List<String>?> uriToSourcesOnlyIncludes = |
| Property.optional("uriToSourcesOnlyIncludes", ListValue(StringValue())); |
| |
| static const Property<bool> skipClassHierarchyTest = Property.optional( |
| "skipClassHierarchyTest", BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool> expectsPlatform = |
| Property.optional("expectsPlatform", BoolValue(), defaultValue: false); |
| |
| static const Property<int?> expectedLibraryCount = |
| const Property.optional("expectedLibraryCount", IntValue()); |
| |
| static const Property<int?> expectedSyntheticLibraryCount = |
| Property.optional("expectedSyntheticLibraryCount", IntValue()); |
| |
| /// The expected result of the advanced invalidation. |
| /// |
| /// If omitted, this defaults to `noDirectlyInvalidated` which corresponds |
| /// to the advanced invalidation result of the initial compilation. This means |
| /// that all subsequent world entries must specify the expected advanced |
| /// invalidation result. |
| static const Property<AdvancedInvalidationResult> advancedInvalidation = |
| Property.optional( |
| "advancedInvalidation", EnumValue(AdvancedInvalidationResult.values), |
| defaultValue: AdvancedInvalidationResult.noDirectlyInvalidated); |
| |
| static const Property<bool> checkEntries = |
| Property.optional("checkEntries", BoolValue(), defaultValue: false); |
| |
| static const Property<bool> checkInvalidatedFiles = Property.optional( |
| "checkInvalidatedFiles", BoolValue(), |
| defaultValue: true); |
| |
| static const Property<List<String>?> expectedInvalidatedUri = |
| Property.optional('expectedInvalidatedUri', ListValue(StringValue())); |
| |
| static const Property<int?> expectSameErrorsAsWorld = |
| Property.optional('expectSameErrorsAsWorld', IntValue()); |
| |
| static const Property<List<ExpressionCompilation>?> expressionCompilation = |
| Property.optional( |
| 'expressionCompilation', |
| ListValue(CustomValue(ExpressionCompilation.create), |
| supportSingleton: true)); |
| |
| static const Property<bool> compareWithFromScratch = Property.optional( |
| 'compareWithFromScratch', BoolValue(), |
| defaultValue: false); |
| |
| static const Property<bool> brandNewIncrementalSerializationAllowDifferent = |
| Property.optional( |
| 'brandNewIncrementalSerializationAllowDifferent', BoolValue(), |
| defaultValue: false); |
| |
| /// Whether the compilation of the world is expected to result in warnings. |
| static const Property<bool> warnings = |
| Property.optional('warnings', BoolValue(), defaultValue: false); |
| |
| /// Whether the compilation of the world is expected to result in compile-time |
| /// errors. |
| static const Property<bool> errors = |
| Property.optional('errors', BoolValue(), defaultValue: false); |
| |
| static const Property<List<String>?> neededDillLibraries = |
| Property.optional('neededDillLibraries', ListValue(StringValue())); |
| |
| static const Property<Map<String, List<String>>?> expectedContent = |
| Property.optional('expectedContent', MapValue(ListValue(StringValue()))); |
| |
| static const Property<bool> incrementalSerializationDoesWork = |
| Property.optional('incrementalSerializationDoesWork', BoolValue(), |
| defaultValue: false); |
| |
| static const Property<List<String>?> serializationShouldNotInclude = |
| Property.optional( |
| 'serializationShouldNotInclude', ListValue(StringValue())); |
| |
| static const Property<bool> checkConstantCoverageReferences = |
| Property.optional('checkConstantCoverageReferences', BoolValue(), |
| defaultValue: true); |
| |
| /// If `true`, the compilation is allowed to report an error twice. |
| static const Property<bool> allowDuplicateErrors = Property.optional( |
| 'allowDuplicateErrors', BoolValue(), |
| defaultValue: false); |
| |
| /// If `true`, the compilation is allowed to report a warning twice. |
| static const Property<bool> allowDuplicateWarnings = Property.optional( |
| 'allowDuplicateWarnings', BoolValue(), |
| defaultValue: true); |
| |
| /// If `true`, the expect files will contain a print of all errors. |
| static const Property<bool> printErrorsInExpect = Property.optional( |
| 'printErrorsInExpect', BoolValue(), |
| defaultValue: false); |
| |
| /// If `true`, the just compiled world will be "rejected", i.e. |
| /// not saved as the latest component and the previous non-rejected compile |
| /// (i.e. the one saved in the latest component) will be relinked, mimicking |
| /// a reject via the frontend server. At least for now all checking after this |
| /// point will be skipped. |
| static const Property<bool> reject = |
| Property.optional('reject', BoolValue(), defaultValue: false); |
| } |
| |
| /// Yaml properties for an [ExpressionCompilation] with a [World]. |
| // TODO(johnniwinther,jensj): Document all properties. |
| class ExpressionCompilationProperties { |
| static const Property<bool> errors = |
| Property.optional('errors', BoolValue(), defaultValue: false); |
| |
| static const Property<bool> warnings = |
| Property.optional('warnings', BoolValue(), defaultValue: false); |
| |
| static const Property<String> uri = Property.required('uri', StringValue()); |
| |
| static const Property<String> expression = |
| Property.required('expression', StringValue()); |
| |
| static const Property<String?> className = |
| Property.optional('className', StringValue()); |
| } |
| |
| final ExpectationSet staticExpectationSet = |
| new ExpectationSet.fromJsonList(jsonDecode(EXPECTATIONS)); |
| |
| const String EXPECTATIONS = ''' |
| [ |
| { |
| "name": "ExpectationFileMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "ExpectationFileMissing", |
| "group": "Fail" |
| }, |
| { |
| "name": "MissingErrors", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedErrors", |
| "group": "Fail" |
| }, |
| { |
| "name": "MissingWarnings", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedWarnings", |
| "group": "Fail" |
| }, |
| { |
| "name": "ClassHierarchyError", |
| "group": "Fail" |
| }, |
| { |
| "name": "NeededDillMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "IncrementalSerializationError", |
| "group": "Fail" |
| }, |
| { |
| "name": "ContentDataMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "MissingInitializationError", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedInitializationError", |
| "group": "Fail" |
| }, |
| { |
| "name": "ReachableLibrariesError", |
| "group": "Fail" |
| }, |
| { |
| "name": "EquivalenceError", |
| "group": "Fail" |
| }, |
| { |
| "name": "UriToSourceError", |
| "group": "Fail" |
| }, |
| { |
| "name": "MissingPlatformLibraries", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedPlatformLibraries", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedAdvancedInvalidation", |
| "group": "Fail" |
| }, |
| { |
| "name": "UnexpectedEntryToLibraryCount", |
| "group": "Fail" |
| }, |
| { |
| "name": "LibraryCountMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "InitializedFromDillMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "NNBDModeMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "ConstantCoverageReferenceWithoutNode", |
| "group": "Fail" |
| } |
| ] |
| '''; |
| |
| final Expectation ExpectationFileMismatch = |
| staticExpectationSet["ExpectationFileMismatch"]; |
| final Expectation ExpectationFileMissing = |
| staticExpectationSet["ExpectationFileMissing"]; |
| final Expectation MissingErrors = staticExpectationSet["MissingErrors"]; |
| final Expectation UnexpectedErrors = staticExpectationSet["UnexpectedErrors"]; |
| final Expectation MissingWarnings = staticExpectationSet["MissingWarnings"]; |
| final Expectation UnexpectedWarnings = |
| staticExpectationSet["UnexpectedWarnings"]; |
| final Expectation ClassHierarchyError = |
| staticExpectationSet["ClassHierarchyError"]; |
| final Expectation NeededDillMismatch = |
| staticExpectationSet["NeededDillMismatch"]; |
| final Expectation IncrementalSerializationError = |
| staticExpectationSet["IncrementalSerializationError"]; |
| final Expectation ContentDataMismatch = |
| staticExpectationSet["ContentDataMismatch"]; |
| final Expectation MissingInitializationError = |
| staticExpectationSet["MissingInitializationError"]; |
| final Expectation UnexpectedInitializationError = |
| staticExpectationSet["UnexpectedInitializationError"]; |
| final Expectation ReachableLibrariesError = |
| staticExpectationSet["ReachableLibrariesError"]; |
| final Expectation EquivalenceError = staticExpectationSet["EquivalenceError"]; |
| final Expectation UriToSourceError = staticExpectationSet["UriToSourceError"]; |
| final Expectation MissingPlatformLibraries = |
| staticExpectationSet["MissingPlatformLibraries"]; |
| final Expectation UnexpectedPlatformLibraries = |
| staticExpectationSet["UnexpectedPlatformLibraries"]; |
| final Expectation UnexpectedAdvancedInvalidation = |
| staticExpectationSet["UnexpectedAdvancedInvalidation"]; |
| final Expectation UnexpectedEntryToLibraryCount = |
| staticExpectationSet["UnexpectedEntryToLibraryCount"]; |
| final Expectation LibraryCountMismatch = |
| staticExpectationSet["LibraryCountMismatch"]; |
| final Expectation InitializedFromDillMismatch = |
| staticExpectationSet["InitializedFromDillMismatch"]; |
| final Expectation NNBDModeMismatch = staticExpectationSet["NNBDModeMismatch"]; |
| final Expectation ConstantCoverageReferenceWithoutNode = |
| staticExpectationSet["ConstantCoverageReferenceWithoutNode"]; |
| |
| Future<Context> createContext(Chain suite, Map<String, String> environment) { |
| const Set<String> knownEnvironmentKeys = { |
| EnvironmentKeys.updateExpectations, |
| EnvironmentKeys.addDebugBreaks, |
| EnvironmentKeys.skipTests, |
| }; |
| checkEnvironment(environment, knownEnvironmentKeys); |
| |
| // Disable colors to ensure that expectation files are the same across |
| // platforms and independent of stdin/stderr. |
| colors.enableColors = false; |
| Set<String> skipTests = |
| environment[EnvironmentKeys.skipTests]?.split(",").toSet() ?? {}; |
| return new Future.value(new Context( |
| environment[EnvironmentKeys.updateExpectations] == "true", |
| environment[EnvironmentKeys.addDebugBreaks] == "true", |
| skipTests, |
| )); |
| } |
| |
| class Context extends ChainContext { |
| @override |
| final List<Step> steps = const <Step>[ |
| 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; |
| |
| final Set<String> skipTests; |
| |
| Context(this.updateExpectations, this.breakBetween, this.skipTests); |
| |
| @override |
| Future<List<TestDescription>> list(Chain suite) async { |
| if (skipTests.isEmpty) return await super.list(suite); |
| return filterSkipped(await super.list(suite)); |
| } |
| |
| List<TestDescription> filterSkipped(List<TestDescription> all) { |
| List<TestDescription> result = []; |
| for (TestDescription testDescription in all) { |
| if (!skipTests.contains(testDescription.shortName)) { |
| result.add(testDescription); |
| } |
| } |
| return result; |
| } |
| |
| @override |
| Future<void> cleanUp(TestDescription description, Result result) async { |
| await cleanupHelper?.outDir?.delete(recursive: true); |
| cleanupHelper?.outDir = null; |
| } |
| |
| @override |
| final ExpectationSet expectationSet = staticExpectationSet; |
| |
| TestData? cleanupHelper; |
| } |
| |
| class TestData { |
| YamlMap map; |
| Directory? outDir; |
| Uri loadedFrom; |
| |
| TestData(this.map, this.outDir, this.loadedFrom); |
| } |
| |
| class ReadTest extends Step<TestDescription, TestData, Context> { |
| const ReadTest(); |
| |
| @override |
| String get name => "read test"; |
| |
| @override |
| 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( |
| loadYamlNode(contents, sourceUrl: uri) as YamlMap, |
| Directory.systemTemp.createTempSync("incremental_load_from_dill_test"), |
| uri); |
| context.cleanupHelper = data; |
| return pass(data); |
| } |
| } |
| |
| class RunCompilations extends Step<TestData, TestData, Context> { |
| const RunCompilations(); |
| |
| @override |
| String get name => "run compilations"; |
| |
| @override |
| Future<Result<TestData>> run(TestData data, Context context) async { |
| Result<TestData>? result; |
| YamlMap map = data.map; |
| Set<String> keys = new Set<String>.from(map.keys.cast<String>()); |
| String type = TestProperties.type.read(map, keys); |
| switch (type) { |
| case TestProperties.type_basic: |
| await basicTest( |
| BasicTestProperties.sources.read(map, keys), |
| BasicTestProperties.entry.read(map, keys), |
| BasicTestProperties.invalidate.read(map, keys), |
| data.outDir!, |
| ); |
| break; |
| case TestProperties.type_newworld: |
| result = await new NewWorldTest( |
| data: data, |
| context: context, |
| worlds: NewWorldTestProperties.worlds.read(map, keys), |
| omitPlatform: NewWorldTestProperties.omitPlatform.read(map, keys), |
| forceLateLoweringForTesting: NewWorldTestProperties |
| .forceLateLoweringForTesting |
| .read(map, keys), |
| trackWidgetCreation: |
| NewWorldTestProperties.trackWidgetCreation.read(map, keys), |
| incrementalSerialization: |
| NewWorldTestProperties.incrementalSerialization.read(map, keys), |
| nnbdMode: NewWorldTestProperties.nnbdMode.read(map, keys) == |
| NewWorldTestProperties.nnbdMode_weak |
| ? NnbdMode.Weak |
| : NnbdMode.Strong, |
| modules: NewWorldTestProperties.modules.read(map, keys), |
| targetName: NewWorldTestProperties.target.read(map, keys), |
| ).newWorldTest(); |
| break; |
| default: |
| throw "Unexpected type: ${type}"; |
| } |
| |
| if (keys.isNotEmpty) throw "Unknown toplevel keys: $keys"; |
| return result ?? pass(data); |
| } |
| } |
| |
| Future<Null> basicTest(Map<String, String> sourceFiles, String entryPoint, |
| List<String>? 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 == ".dart_tool/package_config.json") { |
| 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<String, Map<String, String>> module, |
| final List<int> sdkSummaryData, |
| Target target, |
| Target originalTarget, |
| String sdkSummary, |
| {required bool trackNeededDillLibraries, |
| required Uri checkoutRoot}) async { |
| final Uri base = Uri.parse("org-dartlang-test:///"); |
| final Uri sdkSummaryUri = base.resolve(sdkSummary); |
| |
| TestMemoryFileSystem fs = |
| new TestMemoryFileSystem(base, holePunchBase: checkoutRoot); |
| fs.entityForUri(sdkSummaryUri).writeAsBytesSync(sdkSummaryData); |
| |
| // Setup all sources |
| for (Map<String, String> 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 = <Uri>[]; |
| Uri? packagesUri; |
| for (String filename in module[moduleName]!.keys) { |
| Uri uri = base.resolve(filename); |
| if (uri.pathSegments.last == "package_config.json") { |
| packagesUri = uri; |
| } else { |
| moduleSources.add(uri); |
| } |
| } |
| bool outlineOnly = false; |
| if (originalTarget is DevCompilerTarget) { |
| outlineOnly = true; |
| } |
| CompilerOptions options = |
| getOptions(target: target, sdkSummary: sdkSummary); |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummaryUri; |
| options.omitPlatform = true; |
| options.onDiagnostic = (DiagnosticMessage message) { |
| throw message.ansiFormatted; |
| }; |
| if (packagesUri != null) { |
| options.packagesFileUri = packagesUri; |
| } |
| TestIncrementalCompiler compiler = new TestIncrementalCompiler( |
| options, moduleSources.first, /* initializeFrom = */ null, outlineOnly); |
| IncrementalCompilerResult? compilerResult = await compiler.computeDelta( |
| entryPoints: moduleSources, |
| trackNeededDillLibraries: trackNeededDillLibraries); |
| Component c = compilerResult.component; |
| compilerResult = null; |
| c.computeCanonicalNames(); |
| List<Library> wantedLibs = <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) |
| ..setMainMethodAndMode(null, false, c.mode); |
| List<int> resultBytes = util.postProcess(result); |
| moduleResult[moduleName] = resultBytes; |
| } |
| |
| return moduleResult; |
| } |
| |
| class ExpressionCompilation { |
| final bool errors; |
| final bool warnings; |
| final String uri; |
| final String expression; |
| final String? className; |
| |
| ExpressionCompilation( |
| {required this.errors, |
| required this.warnings, |
| required this.uri, |
| required this.expression, |
| required this.className}); |
| |
| static ExpressionCompilation create(Map yaml) { |
| Set<String> keys = new Set<String>.from(yaml.keys); |
| |
| bool errors = ExpressionCompilationProperties.errors.read(yaml, keys); |
| bool warnings = ExpressionCompilationProperties.warnings.read(yaml, keys); |
| String uri = ExpressionCompilationProperties.uri.read(yaml, keys); |
| String expression = |
| ExpressionCompilationProperties.expression.read(yaml, keys); |
| String? className = |
| ExpressionCompilationProperties.className.read(yaml, keys); |
| |
| if (keys.isNotEmpty) { |
| throw "Unknown key(s) for ExpressionCompilation: $keys"; |
| } |
| return new ExpressionCompilation( |
| errors: errors, |
| warnings: warnings, |
| uri: uri, |
| expression: expression, |
| className: className); |
| } |
| } |
| |
| class World { |
| final List<String>? modules; |
| final bool updateWorldType; |
| final bool noFullComponent; |
| final bool? expectInitializeFromDill; |
| final Map<String, String?> sources; |
| final bool useBadSdk; |
| final bool enableStringReplacement; |
| final String? packageConfigFile; |
| final String? experiments; |
| final String? nnbdModeString; |
| final List<String> entries; |
| final bool outlineOnly; |
| final bool skipOutlineBodyCheck; |
| final bool fromComponent; |
| final List<String>? invalidate; |
| final bool simulateTransformer; |
| final bool? expectInitializationError; |
| final bool compareToPrevious; |
| final List<String>? uriToSourcesDoesntInclude; |
| final List<String>? uriToSourcesOnlyIncludes; |
| final bool skipClassHierarchyTest; |
| final bool expectsPlatform; |
| final int? expectedLibraryCount; |
| final int? expectedSyntheticLibraryCount; |
| final bool warnings; |
| final bool errors; |
| final List<String>? neededDillLibraries; |
| final Map<String, List<String>>? expectedContent; |
| final bool incrementalSerializationDoesWork; |
| final bool allowDuplicateErrors; |
| final bool allowDuplicateWarnings; |
| final bool printErrorsInExpect; |
| final bool reject; |
| |
| /// The expected result of the advanced invalidation. |
| final AdvancedInvalidationResult advancedInvalidation; |
| |
| final bool checkEntries; |
| final bool checkInvalidatedFiles; |
| final List<String>? expectedInvalidatedUri; |
| final int? expectSameErrorsAsWorld; |
| final List<ExpressionCompilation>? expressionCompilation; |
| final bool compareWithFromScratch; |
| |
| /// 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). |
| final bool brandNewIncrementalSerializationAllowDifferent; |
| |
| final List<String>? serializationShouldNotInclude; |
| |
| /// Whether to check if the serialized constant coverage references |
| /// points to anything in itself. |
| final bool checkConstantCoverageReferences; |
| |
| World({ |
| required this.modules, |
| required this.updateWorldType, |
| required this.noFullComponent, |
| required this.expectInitializeFromDill, |
| required this.sources, |
| required this.useBadSdk, |
| required this.enableStringReplacement, |
| required this.packageConfigFile, |
| required this.experiments, |
| required this.nnbdModeString, |
| required this.entries, |
| required this.outlineOnly, |
| required this.skipOutlineBodyCheck, |
| required this.fromComponent, |
| required this.invalidate, |
| required this.simulateTransformer, |
| required this.expectInitializationError, |
| required this.compareToPrevious, |
| required this.uriToSourcesDoesntInclude, |
| required this.uriToSourcesOnlyIncludes, |
| required this.skipClassHierarchyTest, |
| required this.expectsPlatform, |
| required this.expectedLibraryCount, |
| required this.expectedSyntheticLibraryCount, |
| required this.advancedInvalidation, |
| required this.checkEntries, |
| required this.checkInvalidatedFiles, |
| required this.expectedInvalidatedUri, |
| required this.expectSameErrorsAsWorld, |
| required this.expressionCompilation, |
| required this.compareWithFromScratch, |
| required this.brandNewIncrementalSerializationAllowDifferent, |
| required this.warnings, |
| required this.errors, |
| required this.neededDillLibraries, |
| required this.expectedContent, |
| required this.incrementalSerializationDoesWork, |
| required this.serializationShouldNotInclude, |
| required this.checkConstantCoverageReferences, |
| required this.allowDuplicateErrors, |
| required this.allowDuplicateWarnings, |
| required this.printErrorsInExpect, |
| required this.reject, |
| }); |
| |
| static World create(Map world) { |
| Set<String> keys = new Set<String>.from(world.keys); |
| |
| List<String>? modules = WorldProperties.modules.read(world, keys); |
| |
| String? worldType = WorldProperties.worldType.read(world, keys); |
| bool updateWorldType = worldType == WorldProperties.worldType_updated; |
| |
| bool noFullComponent = WorldProperties.noFullComponent.read(world, keys); |
| |
| bool? expectInitializeFromDill = |
| WorldProperties.expectInitializeFromDill.read(world, keys); |
| |
| Map<String, String?> sources = WorldProperties.sources.read(world, keys); |
| |
| bool useBadSdk = WorldProperties.badSdk.read(world, keys); |
| |
| bool enableStringReplacement = |
| WorldProperties.enableStringReplacement.read(world, keys); |
| |
| String? packageConfigFile = |
| WorldProperties.packageConfigFile.read(world, keys); |
| |
| String? experiments = WorldProperties.experiments.read(world, keys); |
| |
| String? nnbdModeString = WorldProperties.nnbdMode.read(world, keys); |
| |
| List<String> entries = WorldProperties.entry.read(world, keys); |
| |
| bool outlineOnly = WorldProperties.outlineOnly.read(world, keys); |
| |
| bool skipOutlineBodyCheck = |
| WorldProperties.skipOutlineBodyCheck.read(world, keys); |
| |
| bool fromComponent = WorldProperties.fromComponent.read(world, keys); |
| |
| List<String>? invalidate = WorldProperties.invalidate.read(world, keys); |
| |
| bool simulateTransformer = |
| WorldProperties.simulateTransformer.read(world, keys); |
| |
| bool? expectInitializationError = |
| WorldProperties.expectInitializationError.read(world, keys); |
| |
| bool compareToPrevious = |
| WorldProperties.compareToPrevious.read(world, keys); |
| |
| List<String>? uriToSourcesDoesntInclude = |
| WorldProperties.uriToSourcesDoesntInclude.read(world, keys); |
| |
| List<String>? uriToSourcesOnlyIncludes = |
| WorldProperties.uriToSourcesOnlyIncludes.read(world, keys); |
| |
| bool skipClassHierarchyTest = |
| WorldProperties.skipClassHierarchyTest.read(world, keys); |
| |
| bool expectsPlatform = WorldProperties.expectsPlatform.read(world, keys); |
| |
| int? expectedLibraryCount = |
| WorldProperties.expectedLibraryCount.read(world, keys); |
| |
| int? expectedSyntheticLibraryCount = |
| WorldProperties.expectedSyntheticLibraryCount.read(world, keys); |
| |
| AdvancedInvalidationResult advancedInvalidation = |
| WorldProperties.advancedInvalidation.read(world, keys); |
| |
| bool checkEntries = WorldProperties.checkEntries.read(world, keys); |
| |
| bool checkInvalidatedFiles = |
| WorldProperties.checkInvalidatedFiles.read(world, keys); |
| |
| List<String>? expectedInvalidatedUri = |
| WorldProperties.expectedInvalidatedUri.read(world, keys); |
| |
| int? expectSameErrorsAsWorld = |
| WorldProperties.expectSameErrorsAsWorld.read(world, keys); |
| |
| List<ExpressionCompilation>? expressionCompilation = |
| WorldProperties.expressionCompilation.read(world, keys); |
| |
| bool compareWithFromScratch = |
| WorldProperties.compareWithFromScratch.read(world, keys); |
| |
| bool brandNewIncrementalSerializationAllowDifferent = WorldProperties |
| .brandNewIncrementalSerializationAllowDifferent |
| .read(world, keys); |
| |
| bool warnings = WorldProperties.warnings.read(world, keys); |
| |
| bool errors = WorldProperties.errors.read(world, keys); |
| |
| List<String>? neededDillLibraries = |
| WorldProperties.neededDillLibraries.read(world, keys); |
| |
| Map<String, List<String>>? expectedContent = |
| WorldProperties.expectedContent.read(world, keys); |
| |
| bool incrementalSerializationDoesWork = |
| WorldProperties.incrementalSerializationDoesWork.read(world, keys); |
| |
| List<String>? serializationShouldNotInclude = |
| WorldProperties.serializationShouldNotInclude.read(world, keys); |
| |
| bool checkConstantCoverageReferences = |
| WorldProperties.checkConstantCoverageReferences.read(world, keys); |
| |
| bool allowDuplicateErrors = |
| WorldProperties.allowDuplicateErrors.read(world, keys); |
| |
| bool allowDuplicateWarnings = |
| WorldProperties.allowDuplicateWarnings.read(world, keys); |
| |
| bool printErrorsInExpect = |
| WorldProperties.printErrorsInExpect.read(world, keys); |
| |
| bool reject = WorldProperties.reject.read(world, keys); |
| |
| if (keys.isNotEmpty) { |
| throw "Unknown key(s) for World: $keys"; |
| } |
| |
| return new World( |
| modules: modules, |
| updateWorldType: updateWorldType, |
| noFullComponent: noFullComponent, |
| expectInitializeFromDill: expectInitializeFromDill, |
| sources: sources, |
| useBadSdk: useBadSdk, |
| enableStringReplacement: enableStringReplacement, |
| packageConfigFile: packageConfigFile, |
| experiments: experiments, |
| nnbdModeString: nnbdModeString, |
| entries: entries, |
| outlineOnly: outlineOnly, |
| skipOutlineBodyCheck: skipOutlineBodyCheck, |
| fromComponent: fromComponent, |
| invalidate: invalidate, |
| simulateTransformer: simulateTransformer, |
| expectInitializationError: expectInitializationError, |
| compareToPrevious: compareToPrevious, |
| uriToSourcesDoesntInclude: uriToSourcesDoesntInclude, |
| uriToSourcesOnlyIncludes: uriToSourcesOnlyIncludes, |
| skipClassHierarchyTest: skipClassHierarchyTest, |
| expectsPlatform: expectsPlatform, |
| expectedLibraryCount: expectedLibraryCount, |
| expectedSyntheticLibraryCount: expectedSyntheticLibraryCount, |
| advancedInvalidation: advancedInvalidation, |
| checkEntries: checkEntries, |
| checkInvalidatedFiles: checkInvalidatedFiles, |
| expectedInvalidatedUri: expectedInvalidatedUri, |
| expectSameErrorsAsWorld: expectSameErrorsAsWorld, |
| expressionCompilation: expressionCompilation, |
| compareWithFromScratch: compareWithFromScratch, |
| brandNewIncrementalSerializationAllowDifferent: |
| brandNewIncrementalSerializationAllowDifferent, |
| warnings: warnings, |
| errors: errors, |
| neededDillLibraries: neededDillLibraries, |
| expectedContent: expectedContent, |
| incrementalSerializationDoesWork: incrementalSerializationDoesWork, |
| serializationShouldNotInclude: serializationShouldNotInclude, |
| checkConstantCoverageReferences: checkConstantCoverageReferences, |
| allowDuplicateErrors: allowDuplicateErrors, |
| allowDuplicateWarnings: allowDuplicateWarnings, |
| printErrorsInExpect: printErrorsInExpect, |
| reject: reject, |
| ); |
| } |
| } |
| |
| class NewWorldTest { |
| final TestData data; |
| final Context context; |
| final List<World> worlds; |
| final Map<String, Map<String, String>>? modules; |
| final bool omitPlatform; |
| final bool forceLateLoweringForTesting; |
| final bool trackWidgetCreation; |
| final bool incrementalSerialization; |
| final NnbdMode nnbdMode; |
| final String? targetName; |
| |
| // 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; |
| |
| NewWorldTest({ |
| required this.data, |
| required this.context, |
| required this.worlds, |
| required this.modules, |
| required this.omitPlatform, |
| required this.forceLateLoweringForTesting, |
| required this.trackWidgetCreation, |
| required this.incrementalSerialization, |
| required this.nnbdMode, |
| required this.targetName, |
| }); |
| |
| Future<Result<TestData>> newWorldTest() async { |
| final Uri platformBinariesRoot = |
| computePlatformBinariesLocation(forceBuildDir: true); |
| |
| // This is somewhat of a hack but will do for now. |
| final Uri checkoutRoot = data.loadedFrom.resolve("../../../../"); |
| |
| TestTargetFlags targetFlags = new TestTargetFlags( |
| forceLateLoweringsForTesting: |
| forceLateLoweringForTesting ? LateLowering.all : null, |
| trackWidgetCreation: trackWidgetCreation); |
| Target target = new VmTarget(targetFlags); |
| if (targetName != null) { |
| if (targetName == NewWorldTestProperties.target_none) { |
| target = new NoneTarget(targetFlags); |
| } else if (targetName == NewWorldTestProperties.target_dartdevc) { |
| target = new DevCompilerTarget(targetFlags); |
| } else if (targetName == NewWorldTestProperties.target_dart2js) { |
| target = new Dart2jsTarget("dart2js", targetFlags); |
| } else if (targetName == NewWorldTestProperties.target_vm) { |
| // default. |
| } else { |
| throw "Unknown target name '$targetName'"; |
| } |
| } |
| Target originalTarget = target; |
| target = new TestTargetWrapper(target, targetFlags); |
| |
| String sdkSummary = computePlatformDillName( |
| target, |
| nnbdMode, |
| () => throw new UnsupportedError( |
| "No platform dill for target '${targetName}' with $nnbdMode."))!; |
| |
| final Uri base = Uri.parse("org-dartlang-test:///"); |
| final Uri sdkSummaryUri = base.resolve(sdkSummary); |
| final Uri initializeFrom = base.resolve("initializeFrom.dill"); |
| Uri platformUri = platformBinariesRoot.resolve(sdkSummary); |
| final List<int> sdkSummaryData = |
| await new File.fromUri(platformUri).readAsBytes(); |
| |
| List<int>? newestWholeComponentData; |
| TestMemoryFileSystem? fs; |
| Map<String, String?>? sourceFiles; |
| CompilerOptions? options; |
| TestIncrementalCompiler? compiler; |
| IncrementalSerializer? incrementalSerializer; |
| |
| Map<String, List<int>>? moduleData; |
| Map<String, Component>? moduleComponents; |
| |
| if (modules != null) { |
| moduleData = await createModules( |
| modules!, sdkSummaryData, target, originalTarget, sdkSummary, |
| checkoutRoot: checkoutRoot, trackNeededDillLibraries: false); |
| sdk = newestWholeComponent = new Component(); |
| new BinaryBuilder(sdkSummaryData, |
| filename: null, disableLazyReading: false) |
| .readComponent(newestWholeComponent!); |
| } |
| |
| int worldNum = 0; |
| // TODO: When needed, we can do this for warnings too. |
| List<Set<String>> worldErrors = []; |
| for (World world in worlds) { |
| worldNum++; |
| print("----------------"); |
| print("World #$worldNum"); |
| print("----------------"); |
| List<Component>? modulesToUse; |
| if (world.modules != null) { |
| moduleComponents ??= new Map<String, Component>(); |
| |
| sdk!.adoptChildren(); |
| for (Component c in moduleComponents.values) { |
| c.adoptChildren(); |
| } |
| |
| modulesToUse = <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); |
| } |
| } |
| } |
| |
| if (!world.updateWorldType) { |
| fs = new TestMemoryFileSystem(base, holePunchBase: checkoutRoot); |
| } |
| 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 (!world.updateWorldType) { |
| sourceFiles = new Map<String, String?>.from(world.sources); |
| } else { |
| sourceFiles!.addAll(new Map<String, String?>.from(world.sources)); |
| } |
| Uri? packagesUri; |
| for (String filename in sourceFiles.keys) { |
| String data = sourceFiles[filename] ?? ""; |
| Uri uri = base.resolve(filename); |
| if (filename == ".dart_tool/package_config.json") { |
| packagesUri = uri; |
| } |
| if (world.enableStringReplacement) { |
| data = replaceMarkersWithVersions(data); |
| } |
| fs.entityForUri(uri).writeAsStringSync(data); |
| } |
| if (world.packageConfigFile != null) { |
| packagesUri = base.resolve(world.packageConfigFile!); |
| } |
| |
| if (!world.updateWorldType) { |
| options = getOptions(target: target, sdkSummary: sdkSummary); |
| options.nnbdMode = nnbdMode; |
| options.fileSystem = fs; |
| options.sdkRoot = null; |
| options.sdkSummary = sdkSummaryUri; |
| if (world.useBadSdk) { |
| options.sdkSummary = sdkSummaryUri.resolve("nonexisting.dill"); |
| } |
| options.omitPlatform = omitPlatform; |
| if (world.experiments != null) { |
| Map<String, bool> flagsFromOptions = |
| parseExperimentalArguments([world.experiments!]); |
| // Ensure that we run with non-nullable turned off even when the |
| // flag is on by default. |
| Map<ExperimentalFlag, bool> explicitExperimentalFlags = |
| parseExperimentalFlags(flagsFromOptions, |
| onError: (e) => |
| throw "Error on parsing experiments flags: $e"); |
| options.explicitExperimentalFlags = explicitExperimentalFlags; |
| } else { |
| options.explicitExperimentalFlags = {}; |
| } |
| // A separate "world" can also change nnbd mode --- |
| // notice that the platform is not updated though! |
| if (world.nnbdModeString != null) { |
| String nnbdMode = world.nnbdModeString!; |
| switch (nnbdMode) { |
| case WorldProperties.nnbdMode_weak: |
| options.nnbdMode = NnbdMode.Weak; |
| break; |
| default: |
| throw "Not supported nnbd mode: $nnbdMode"; |
| } |
| } |
| } |
| if (packagesUri != null) { |
| options!.packagesFileUri = packagesUri; |
| } |
| bool gotError = false; |
| final Set<String> formattedErrors = Set<String>(); |
| bool gotWarning = false; |
| final Set<String> formattedWarnings = Set<String>(); |
| final Set<String> seenDiagnosticCodes = Set<String>(); |
| |
| 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) && !world.allowDuplicateErrors) { |
| Expect.fail("Got the same message twice: ${stringId}"); |
| } |
| } else if (message.severity == Severity.warning) { |
| gotWarning = true; |
| if (!formattedWarnings.add(stringId) && |
| !world.allowDuplicateWarnings) { |
| Expect.fail("Got the same message twice: ${stringId}"); |
| } |
| } |
| }; |
| |
| List<Uri> entries = world.entries.map((e) => base.resolve(e)).toList(); |
| if (!world.updateWorldType) { |
| if (incrementalSerialization == true) { |
| incrementalSerializer = new IncrementalSerializer(); |
| } |
| if (world.fromComponent) { |
| compiler = new TestIncrementalCompiler.fromComponent( |
| options, |
| entries.first, |
| (modulesToUse != null) ? sdk : newestWholeComponent, |
| world.outlineOnly, |
| incrementalSerializer); |
| } else { |
| compiler = new TestIncrementalCompiler(options, entries.first, |
| initializeFrom, world.outlineOnly, incrementalSerializer); |
| |
| if (modulesToUse != null) { |
| throw "You probably shouldn't do this! " |
| "Any modules will have another sdk loaded!"; |
| } |
| } |
| } |
| |
| List<Uri> invalidated = <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(); |
| } |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| IncrementalCompilerResult? compilerResult = await compiler!.computeDelta( |
| entryPoints: entries, |
| fullComponent: !world.updateWorldType |
| ? false |
| : (world.noFullComponent ? false : true), |
| trackNeededDillLibraries: modulesToUse != null, |
| simulateTransformer: world.simulateTransformer); |
| component = compilerResult.component; |
| // compilerResult is null'ed out at the end to avoid any |
| // "artificial memory leak" on that account. |
| if (world.outlineOnly && !world.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<TestData>? result = performErrorAndWarningCheck(world, data, |
| gotError, formattedErrors, gotWarning, formattedWarnings); |
| if (result != null) return result; |
| bool? expectInitializationError = world.expectInitializationError; |
| if (expectInitializationError != null) { |
| Set<String> seenInitializationError = seenDiagnosticCodes.intersection({ |
| "InitializeFromDillNotSelfContainedNoDump", |
| "InitializeFromDillNotSelfContained", |
| "InitializeFromDillUnknownProblem", |
| "InitializeFromDillUnknownProblemNoDump", |
| }); |
| if (expectInitializationError) { |
| if (seenInitializationError.isEmpty) { |
| return new Result<TestData>(data, MissingInitializationError, |
| "Expected to see an initialization error but didn't."); |
| } |
| } else { |
| if (seenInitializationError.isNotEmpty) { |
| return new Result<TestData>( |
| data, |
| UnexpectedInitializationError, |
| "Expected not to see an initialization error but did: " |
| "$seenInitializationError."); |
| } |
| } |
| } |
| 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, compilerResult.neededDillLibraries, base); |
| if (result != null) return result; |
| |
| Result? nnbdCheck = checkNNBDSettings(component!); |
| if (nnbdCheck != null) return nnbdCheck.copyWithOutput(data); |
| |
| if (!world.noFullComponent) { |
| Set<Library> allLibraries = new Set<Library>(); |
| for (Library lib in component!.libraries) { |
| computeAllReachableLibrariesFor(lib, allLibraries); |
| } |
| if (allLibraries.length != component!.libraries.length) { |
| return new Result<TestData>( |
| data, |
| ReachableLibrariesError, |
| "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) { |
| return new Result<TestData>( |
| data, |
| ReachableLibrariesError, |
| "Expected for the reachable stuff to be equal to " |
| "${component!.libraries} but these were there too: $tooMany " |
| "(and others were missing)"); |
| } |
| } |
| |
| util.postProcessComponent(component!); |
| String actualSerialized = componentToStringSdkFiltered(component!, |
| printErrors: world.printErrorsInExpect ? formattedErrors : null); |
| print("*****\n\ncomponent:\n" |
| "${actualSerialized}\n\n\n"); |
| result = checkExpectFile(data, worldNum, "", context, actualSerialized); |
| if (result != null) return result; |
| |
| if (world.compareToPrevious && newestWholeComponent != null) { |
| EquivalenceResult result = checkEquivalence( |
| newestWholeComponent!, component!, |
| strategy: const Strategy()); |
| if (!result.isEquivalent) { |
| return new Result<TestData>( |
| data, EquivalenceError, result.toString()); |
| } |
| } |
| |
| if (world.reject) { |
| util.postProcess(component!); |
| |
| // Reject: We keep the original "newest whole component" and also |
| // reject/relink it. |
| // This mimics what happens when e.g. flutter "reject"s a compile. |
| newestWholeComponent?.relink(); |
| |
| // Add errors or assert will trigger in later worlds. |
| worldErrors.add(formattedErrors.toSet()); |
| |
| // But otherwise skip other stuff - e.g. doing a leak check will find a |
| // leak because we (on purpose) have two of the same library at the same |
| // time. |
| continue; |
| } else { |
| // Save it. |
| newestWholeComponentData = util.postProcess(component!); |
| newestWholeComponent = component; |
| } |
| |
| if (world.checkConstantCoverageReferences) { |
| Result<TestData>? result = checkConstantCoverageReferences( |
| newestWholeComponentData, omitPlatform, sdkSummaryData, data); |
| if (result != null) return result; |
| } |
| |
| if (world.uriToSourcesDoesntInclude != null) { |
| for (String filename in world.uriToSourcesDoesntInclude!) { |
| Uri uri = base.resolve(filename); |
| if (component!.uriToSource[uri] != null) { |
| return new Result<TestData>( |
| data, |
| UriToSourceError, |
| "Expected no uriToSource for $uri but found " |
| "${component!.uriToSource[uri]}"); |
| } |
| } |
| } |
| if (world.uriToSourcesOnlyIncludes != null) { |
| Set<Uri> 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.isScheme("org-dartlang-sdk")) continue; |
| if (!allowed.contains(uri)) { |
| return new Result<TestData>( |
| data, |
| UriToSourceError, |
| "Expected no uriToSource for $uri but found " |
| "${component!.uriToSource[uri]}"); |
| } |
| } |
| } |
| |
| if (!world.skipClassHierarchyTest) { |
| result = checkClassHierarchy(compilerResult, data, worldNum, context); |
| if (result != null) return result; |
| } |
| |
| int nonSyntheticLibraries = countNonSyntheticLibraries(component!); |
| int nonSyntheticPlatformLibraries = |
| countNonSyntheticPlatformLibraries(component!); |
| int syntheticLibraries = countSyntheticLibraries(component!); |
| if (world.expectsPlatform) { |
| if (nonSyntheticPlatformLibraries < 5) { |
| return new Result<TestData>( |
| data, |
| MissingPlatformLibraries, |
| "Expected to have at least 5 platform libraries " |
| "(actually, the entire sdk), " |
| "but got $nonSyntheticPlatformLibraries."); |
| } |
| } else { |
| if (nonSyntheticPlatformLibraries != 0) { |
| return new Result<TestData>( |
| data, |
| UnexpectedPlatformLibraries, |
| "Expected to have 0 platform libraries " |
| "but got $nonSyntheticPlatformLibraries."); |
| } |
| } |
| if (world.expectedLibraryCount != null) { |
| if (nonSyntheticLibraries - nonSyntheticPlatformLibraries != |
| world.expectedLibraryCount!) { |
| return new Result<TestData>( |
| 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<TestData>( |
| data, |
| LibraryCountMismatch, |
| "Expected ${world.expectedSyntheticLibraryCount} synthetic " |
| "libraries, got ${syntheticLibraries}"); |
| } |
| } |
| |
| AdvancedInvalidationResult? actualAdvancedInvalidation = |
| compiler.recorderForTesting.advancedInvalidationResult; |
| if (world.advancedInvalidation != actualAdvancedInvalidation) { |
| return new Result<TestData>( |
| data, |
| UnexpectedAdvancedInvalidation, |
| "Expected advancedInvalidation: " |
| "${world.advancedInvalidation}, " |
| "advancedInvalidation: " |
| "${actualAdvancedInvalidation}."); |
| } |
| |
| if (!world.noFullComponent) { |
| if (world.checkEntries) { |
| List<Library> entryLib = component!.libraries |
| .where((Library lib) => |
| entries.contains(lib.importUri) || |
| entries.contains(lib.fileUri)) |
| .toList(); |
| if (entryLib.length != entries.length) { |
| return new Result<TestData>( |
| data, |
| UnexpectedEntryToLibraryCount, |
| "Expected the entries to become libraries. " |
| "Got ${entryLib.length} libraries for the expected " |
| "${entries.length} entries."); |
| } |
| } |
| } |
| if (compiler.initializedFromDillForTesting != expectInitializeFromDill) { |
| return new Result<TestData>( |
| data, |
| InitializedFromDillMismatch, |
| "Expected that initializedFromDill would be " |
| "$expectInitializeFromDill but was " |
| "${compiler.initializedFromDillForTesting}"); |
| } |
| |
| if (incrementalSerialization == true && |
| compiler.initializedFromDillForTesting) { |
| Expect.isTrue(compiler.initializedIncrementalSerializerForTesting); |
| } else { |
| Expect.isFalse(compiler.initializedIncrementalSerializerForTesting); |
| } |
| |
| if (world.checkInvalidatedFiles) { |
| Set<Uri>? filteredInvalidated = |
| compiler.getFilteredInvalidatedImportUrisForTesting(invalidated); |
| if (world.invalidate != null) { |
| Expect.equals( |
| world.invalidate!.length, |
| filteredInvalidated?.length ?? 0, |
| "Unexpected invalidated files: ${filteredInvalidated}, " |
| "actual: ${world.invalidate}."); |
| if (world.expectedInvalidatedUri != null) { |
| Expect.setEquals( |
| world.expectedInvalidatedUri!.map((s) => base.resolve(s)), |
| filteredInvalidated!); |
| } |
| } else { |
| Expect.isNull(filteredInvalidated); |
| Expect.isNull(world.expectedInvalidatedUri); |
| } |
| } |
| Result<List<int>?> serializationResult = checkIncrementalSerialization( |
| incrementalSerialization, component!, incrementalSerializer, world); |
| if (!serializationResult.isPass) { |
| return serializationResult.copyWithOutput(data); |
| } |
| List<int>? incrementalSerializationBytes = serializationResult.output; |
| |
| worldErrors.add(formattedErrors.toSet()); |
| assert(worldErrors.length == worldNum); |
| if (world.expectSameErrorsAsWorld != null) { |
| checkErrorsAndWarnings( |
| worldErrors[world.expectSameErrorsAsWorld! - 1], |
| formattedErrors, |
| {}, |
| {}, |
| ); |
| } |
| |
| Set<String> prevFormattedErrors = formattedErrors.toSet(); |
| Set<String> prevFormattedWarnings = formattedWarnings.toSet(); |
| |
| void clearPrevErrorsEtc() { |
| gotError = false; |
| formattedErrors.clear(); |
| gotWarning = false; |
| formattedWarnings.clear(); |
| } |
| |
| if (!world.noFullComponent) { |
| clearPrevErrorsEtc(); |
| IncrementalCompilerResult? compilerResult2 = |
| await compiler.computeDelta( |
| entryPoints: entries, |
| fullComponent: true, |
| simulateTransformer: world.simulateTransformer); |
| component2 = compilerResult2.component; |
| compilerResult2 = null; |
| Result<TestData>? result = performErrorAndWarningCheck(world, data, |
| gotError, formattedErrors, gotWarning, formattedWarnings); |
| if (result != null) return result; |
| List<int> thisWholeComponent = util.postProcess(component2!); |
| String component2String = componentToStringSdkFiltered(component2!, |
| printErrors: world.printErrorsInExpect ? formattedErrors : null); |
| print("*****\n\ncomponent2:\n$component2String\n\n\n"); |
| checkIsEqual(newestWholeComponentData, thisWholeComponent); |
| checkErrorsAndWarnings(prevFormattedErrors, formattedErrors, |
| prevFormattedWarnings, formattedWarnings); |
| newestWholeComponent = component2; |
| |
| Result<List<int>?> serializationResult = checkIncrementalSerialization( |
| incrementalSerialization, |
| component2!, |
| incrementalSerializer, |
| world); |
| if (!serializationResult.isPass) { |
| return serializationResult.copyWithOutput(data); |
| } |
| List<int>? incrementalSerializationBytes2 = serializationResult.output; |
| |
| if ((incrementalSerializationBytes == null && |
| incrementalSerializationBytes2 != null) || |
| (incrementalSerializationBytes != null && |
| incrementalSerializationBytes2 == null)) { |
| return new Result<TestData>( |
| data, |
| IncrementalSerializationError, |
| "Incremental serialization gave results in one instance, " |
| "but not another."); |
| } |
| |
| if (incrementalSerializationBytes != null) { |
| checkIsEqual( |
| incrementalSerializationBytes, incrementalSerializationBytes2!); |
| } |
| } |
| |
| if (world.expressionCompilation != null) { |
| int expressionCompilationNum = 0; |
| for (ExpressionCompilation compilation |
| in world.expressionCompilation!) { |
| expressionCompilationNum++; |
| clearPrevErrorsEtc(); |
| Uri uri = base.resolve(compilation.uri); |
| Procedure procedure = (await compiler.compileExpression( |
| compilation.expression, |
| {}, |
| [], |
| "debugExpr", |
| uri, |
| className: compilation.className, |
| ))!; |
| if (gotError && !compilation.errors) { |
| return new Result<TestData>(data, UnexpectedErrors, |
| "Got error(s) on expression compilation: ${formattedErrors}."); |
| } else if (!gotError && compilation.errors) { |
| return new Result<TestData>( |
| data, MissingErrors, "Didn't get any errors."); |
| } |
| if (gotWarning && !compilation.warnings) { |
| return new Result<TestData>( |
| data, |
| UnexpectedWarnings, |
| "Got warning(s) on expression compilation: " |
| "${formattedWarnings}."); |
| } else if (!gotWarning && compilation.warnings) { |
| return new Result<TestData>( |
| data, MissingWarnings, "Didn't get any warnings."); |
| } |
| Result<TestData>? result = checkExpectFile( |
| data, |
| worldNum, |
| ".expression.$expressionCompilationNum", |
| context, |
| nodeToString(procedure)); |
| if (result != null) return result; |
| } |
| } |
| |
| if (!world.noFullComponent && |
| (incrementalSerialization == true || world.compareWithFromScratch)) { |
| // Do compile from scratch and compare. |
| clearPrevErrorsEtc(); |
| TestIncrementalCompiler? compilerFromScratch; |
| |
| IncrementalSerializer? incrementalSerializer2; |
| if (incrementalSerialization == true) { |
| incrementalSerializer2 = new IncrementalSerializer(); |
| } |
| |
| if (world.fromComponent || modulesToUse != null) { |
| compilerFromScratch = new TestIncrementalCompiler.fromComponent( |
| options, |
| entries.first, |
| sdk, |
| world.outlineOnly, |
| incrementalSerializer2); |
| } else { |
| compilerFromScratch = new TestIncrementalCompiler(options, |
| entries.first, null, world.outlineOnly, incrementalSerializer2); |
| } |
| |
| if (modulesToUse != null) { |
| compilerFromScratch.setModulesToLoadOnNextComputeDelta(modulesToUse); |
| compilerFromScratch.invalidateAllSources(); |
| } |
| |
| Stopwatch stopwatch = new Stopwatch()..start(); |
| IncrementalCompilerResult? compilerResult3 = |
| await compilerFromScratch.computeDelta( |
| entryPoints: entries, |
| trackNeededDillLibraries: modulesToUse != null, |
| simulateTransformer: world.simulateTransformer); |
| component3 = compilerResult3.component; |
| compilerResult3 = null; |
| compilerFromScratch = null; |
| Result<TestData>? 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"); |
| |
| List<int> thisWholeComponent = util.postProcess(component3!); |
| String component3String = componentToStringSdkFiltered(component3!, |
| printErrors: world.printErrorsInExpect ? formattedErrors : null); |
| print("*****\n\ncomponent3:\n$component3String\n\n\n"); |
| if (world.compareWithFromScratch) { |
| checkIsEqual(newestWholeComponentData, thisWholeComponent); |
| } |
| checkErrorsAndWarnings(prevFormattedErrors, formattedErrors, |
| prevFormattedWarnings, formattedWarnings); |
| |
| Result<List<int>?> serializationResult = checkIncrementalSerialization( |
| incrementalSerialization, |
| component3!, |
| incrementalSerializer2, |
| world); |
| if (!serializationResult.isPass) { |
| return serializationResult.copyWithOutput(data); |
| } |
| List<int>? incrementalSerializationBytes3 = serializationResult.output; |
| |
| if ((incrementalSerializationBytes == null && |
| incrementalSerializationBytes3 != null) || |
| (incrementalSerializationBytes != null && |
| incrementalSerializationBytes3 == null)) { |
| return new Result<TestData>( |
| data, |
| IncrementalSerializationError, |
| "Incremental serialization gave results in one instance, " |
| "but not another."); |
| } |
| |
| if (incrementalSerializationBytes != null) { |
| if (world.brandNewIncrementalSerializationAllowDifferent) { |
| // 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; |
| compilerResult = null; |
| component2 = null; |
| component3 = null; |
| // Dummy tree nodes can (currently) leak though the parent pointer. |
| // To avoid that (here) (for leak testing) we'll null them out. |
| for (TreeNode treeNode in dummyTreeNodes) { |
| treeNode.parent = null; |
| } |
| |
| if (context.breakBetween) { |
| debugger(); |
| print("Continuing after debug break"); |
| } |
| } |
| return new Result<TestData>.pass(data); |
| } |
| } |
| |
| Result<TestData>? checkConstantCoverageReferences( |
| List<int> newestWholeComponentData, |
| bool omitPlatform, |
| List<int> sdkSummaryData, |
| TestData data) { |
| // Note that this is in a method to avoid "semi-leaks". |
| Component loadedComponent = new Component(); |
| if (omitPlatform) { |
| new BinaryBuilder(sdkSummaryData, filename: null) |
| .readComponent(loadedComponent); |
| } |
| new BinaryBuilder(newestWholeComponentData, filename: null) |
| .readComponent(loadedComponent); |
| |
| for (MapEntry<Uri, Source> source in loadedComponent.uriToSource.entries) { |
| Set<Reference>? references = source.value.constantCoverageConstructors; |
| if (references != null) { |
| for (Reference reference in references) { |
| if (reference.node == null) { |
| return new Result<TestData>( |
| data, |
| ConstantCoverageReferenceWithoutNode, |
| "Constant coverage reference without node: " |
| "${reference.canonicalName} from ${source.value.importUri} " |
| "indexed in ${source.key}"); |
| } |
| } |
| } |
| } |
| return null; |
| } |
| |
| class Strategy extends EquivalenceStrategy { |
| const Strategy(); |
| |
| @override |
| bool checkComponent_libraries( |
| EquivalenceVisitor visitor, Component node, Component other) { |
| return visitor.checkSets(node.libraries.toSet(), other.libraries.toSet(), |
| visitor.matchNamedNodes, visitor.checkNodes, 'libraries'); |
| } |
| |
| @override |
| bool checkClass_procedures( |
| EquivalenceVisitor visitor, Class node, Class other) { |
| // Check procedures as a set instead of a list to allow for reordering. |
| return visitor.checkSets(node.procedures.toSet(), other.procedures.toSet(), |
| visitor.matchNamedNodes, visitor.checkNodes, 'procedures'); |
| } |
| |
| @override |
| bool checkVariableDeclaration_binaryOffsetNoTag(EquivalenceVisitor visitor, |
| VariableDeclaration node, VariableDeclaration other) { |
| return true; |
| } |
| } |
| |
| 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.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<TestData>? 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?.replaceAll("\r\n", "\n") != actualSerialized) { |
| if (context.updateExpectations) { |
| file.writeAsStringSync(actualSerialized); |
| } else { |
| String extra = ""; |
| if (expected == null) extra = "Expect file did not exist.\n"; |
| return new Result<TestData>( |
| 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<TestData>? checkClassHierarchy(IncrementalCompilerResult compilerResult, |
| TestData data, int worldNum, Context context, |
| {bool checkExpectFile = false}) { |
| ClassHierarchy? classHierarchy = compilerResult.classHierarchy; |
| if (classHierarchy is! ClosedWorldClassHierarchy) { |
| return new Result<TestData>( |
| data, |
| ClassHierarchyError, |
| "Expected the class hierarchy to be ClosedWorldClassHierarchy " |
| "but it wasn't. It was ${classHierarchy.runtimeType}"); |
| } |
| List<ForTestingClassInfo> classHierarchyData = |
| classHierarchy.getTestingClassInfo(); |
| Map<Class, ForTestingClassInfo> classHierarchyMap = |
| new Map<Class, ForTestingClassInfo>(); |
| for (ForTestingClassInfo info in classHierarchyData) { |
| if (classHierarchyMap[info.classNode] != null) { |
| return new Result<TestData>( |
| data, ClassHierarchyError, "Two entries for ${info.classNode}"); |
| } |
| classHierarchyMap[info.classNode] = info; |
| } |
| |
| Component component = compilerResult.component; |
| StringBuffer sb = new StringBuffer(); |
| for (Library library in component.libraries) { |
| if (library.importUri.isScheme("dart")) continue; |
| sb.writeln("Library ${library.importUri}"); |
| for (Class c in library.classes) { |
| sb.writeln(" - Class ${c.name}"); |
| |
| Set<Class> checkedSupertypes = <Class>{}; |
| Result<TestData>? 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<TestData>(data, ClassHierarchyError, |
| "${superclass} not found as a superclass of $c"); |
| } |
| Result<TestData>? 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<TestData>? result = checkSupertype(c.asThisSupertype); |
| if (result != null) return result; |
| |
| ForTestingClassInfo? info = classHierarchyMap[c]; |
| if (info == null) { |
| return new Result<TestData>(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<Member> members = info.lazyDeclaredGettersAndCalls!.toSet(); |
| for (Field f in c.fields) { |
| if (f.isStatic) continue; |
| if (!members.remove(f)) { |
| return new Result<TestData>( |
| 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<TestData>( |
| data, |
| ClassHierarchyError, |
| "Didn't find ${p.name.text} in lazyDeclaredGettersAndCalls " |
| "for ${c.name} in ${library.importUri}"); |
| } |
| } |
| if (members.isNotEmpty) { |
| return new Result<TestData>( |
| 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<Member> 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<TestData>(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<TestData>(data, ClassHierarchyError, |
| "Didn't find $p in lazyDeclaredSetters for $c"); |
| } |
| } |
| if (members.isNotEmpty) { |
| return new Result<TestData>( |
| 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<TestData>( |
| data, |
| ClassHierarchyError, |
| "${extra}Unexpected serialized representation. " |
| "Fix or update $uri to contain the below:\n\n" |
| "$actualClassHierarchy"); |
| } |
| } |
| } |
| return null; |
| } |
| |
| void checkErrorsAndWarnings( |
| Set<String> prevFormattedErrors, |
| Set<String> formattedErrors, |
| Set<String> prevFormattedWarnings, |
| Set<String> 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<List<int>?> checkIncrementalSerialization( |
| bool? incrementalSerialization, |
| Component component, |
| IncrementalSerializer? incrementalSerializer, |
| World 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<String, Set<String>> 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<List<int>>(null, IncrementalSerializationError, |
| "Incremental serialization added libraries!"); |
| } |
| if (librariesBefore == librariesAfter && |
| world.incrementalSerializationDoesWork) { |
| return new Result<List<int>>(null, IncrementalSerializationError, |
| "Incremental serialization didn't remove any libraries!"); |
| } |
| if (librariesAfter < librariesBefore && sink.builder.isEmpty) { |
| return new Result<List<int>>( |
| null, |
| IncrementalSerializationError, |
| "Incremental serialization didn't output any bytes, " |
| "but did remove libraries"); |
| } else if (librariesAfter == librariesBefore && !sink.builder.isEmpty) { |
| return new Result<List<int>>( |
| 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<int> 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 != null) { |
| Set<Uri> includedImportUris = |
| loadedComponent.libraries.map((l) => l.importUri).toSet(); |
| for (String uriString in world.serializationShouldNotInclude!) { |
| Uri uri = Uri.parse(uriString); |
| if (includedImportUris.contains(uri)) { |
| return new Result<List<int>>( |
| null, |
| IncrementalSerializationError, |
| "Incremental serialization shouldn't include " |
| "$uriString but did."); |
| } |
| } |
| } |
| |
| // Check that it contains at least what we want. |
| Map<String, Set<String>> afterContent = |
| buildMapOfContent(loadedComponent); |
| // Remove any keys in afterContent not in the original as the written |
| // one is allowed to contain *more*. |
| Set<String> 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<List<int>?>(null); |
| |
| // Check that the result is self-contained. |
| result = checkSelfContained(loadedComponent); |
| if (result != null) return result.copyWithOutput<List<int>?>(null); |
| |
| return new Result<List<int>>.pass(bytes); |
| } |
| } |
| return new Result<List<int>?>.pass(null); |
| } |
| |
| Result? checkSelfContained(Component component) { |
| Set<Library> got = new Set<Library>.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<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 (dependency.targetLibrary.importUri.isScheme("dart")) continue; |
| if (libraries.add(dependency.targetLibrary)) { |
| workList.add(dependency.targetLibrary); |
| allLibraries.add(dependency.targetLibrary); |
| } |
| } |
| } |
| } |
| |
| Result? checkExpectedContent(World world, Component component) { |
| if (world.expectedContent != null) { |
| Map<String, Set<String>> actualContent = buildMapOfContent(component); |
| return checkExpectedContentData(actualContent, world.expectedContent!); |
| } |
| return null; |
| } |
| |
| Result? checkExpectedContentData(Map<String, Set<String>> actualContent, |
| Map<String, Iterable<String>> expectedContent) { |
| Result<TestData> 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<String> missingKeys = actualContent.keys.toSet() |
| ..removeAll(expectedContent.keys); |
| if (missingKeys.isNotEmpty) { |
| return createFailureResult(); |
| } |
| for (MapEntry<String, Iterable<String>> entry in expectedContent.entries) { |
| Set<String> expected = new Set<String>.from(entry.value); |
| Set<String> actual = actualContent[entry.key]!.toSet(); |
| if (expected.length != actual.length) { |
| return createFailureResult(); |
| } |
| actual.removeAll(expected); |
| if (actual.isNotEmpty) { |
| return createFailureResult(); |
| } |
| } |
| return null; |
| } |
| |
| Map<String, Set<String>> buildMapOfContent(Component component) { |
| 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.text}"); |
| } |
| for (Field f in lib.fields) { |
| libContent.add("Field ${f.name.text}"); |
| } |
| } |
| return actualContent; |
| } |
| |
| Result<TestData>? checkNeededDillLibraries( |
| World world, TestData data, Set<Library>? neededDillLibraries, Uri base) { |
| if (world.neededDillLibraries != null) { |
| List<Uri> actualContent = <Uri>[]; |
| for (Library lib in neededDillLibraries!) { |
| if (lib.importUri.isScheme("dart")) continue; |
| actualContent.add(lib.importUri); |
| } |
| |
| List<Uri> expectedContent = <Uri>[]; |
| for (String entry in world.neededDillLibraries!) { |
| expectedContent.add(base.resolve(entry)); |
| } |
| |
| Result<TestData> createFailureResult() { |
| return new Result<TestData>( |
| data, |
| NeededDillMismatch, |
| "Expected and actual content not the same.\n" |
| "Expected $expectedContent.\n" |
| "Got $actualContent"); |
| } |
| |
| if (actualContent.length != expectedContent.length) { |
| return createFailureResult(); |
| } |
| Set<Uri> notInExpected = |
| actualContent.toSet().difference(expectedContent.toSet()); |
| Set<Uri> 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, |
| {required final Set<String>? printErrors}) { |
| Component c = new Component(); |
| List<Uri> dartUris = <Uri>[]; |
| for (Library lib in component.libraries) { |
| if (lib.importUri.isScheme("dart")) { |
| dartUris.add(lib.importUri); |
| } else { |
| if (lib.fileUri.isScheme("holePunch")) { |
| // Skip this. |
| continue; |
| } |
| c.libraries.add(lib); |
| } |
| } |
| c.setMainMethodAndMode(component.mainMethodName, true, component.mode); |
| c.problemsAsJson = component.problemsAsJson; |
| |
| StringBuffer s = new StringBuffer(); |
| s.write(componentToString(c)); |
| |
| addConstantCoverageToExpectation(component, s, |
| skipImportUri: (Uri? importUri) => importUri?.scheme == "dart"); |
| |
| if (dartUris.isNotEmpty) { |
| s.writeln(""); |
| s.writeln("And ${dartUris.length} platform libraries:"); |
| for (Uri uri in dartUris) { |
| s.writeln(" - $uri"); |
| } |
| } |
| |
| if (printErrors != null && printErrors.isNotEmpty) { |
| s.writeln(""); |
| s.writeln("A total of ${printErrors.length} errors:"); |
| for (String error in printErrors) { |
| // Make the error more readable if it's a json. |
| Map<String, dynamic>? decoded; |
| try { |
| decoded = jsonDecode(error); |
| } catch (_) {} |
| if (decoded != null && decoded["plainTextFormatted"] is List) { |
| List plainTextFormatted = decoded["plainTextFormatted"] as List; |
| if (plainTextFormatted.isNotEmpty && |
| plainTextFormatted.first is String) { |
| error = plainTextFormatted.first; |
| } |
| } |
| s.writeln(" - $error"); |
| } |
| } |
| |
| 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.isScheme("dart")) result++; |
| } |
| return result; |
| } |
| |
| int countSyntheticLibraries(Component c) { |
| int result = 0; |
| for (Library lib in c.libraries) { |
| if (lib.isSynthetic) result++; |
| } |
| return result; |
| } |
| |
| Result<TestData>? performErrorAndWarningCheck( |
| World world, |
| TestData data, |
| bool gotError, |
| Set<String> formattedErrors, |
| bool gotWarning, |
| Set<String> formattedWarnings) { |
| if (world.errors && !gotError) { |
| return new Result<TestData>( |
| data, MissingErrors, "Expected error, but didn't get any."); |
| } else if (!world.errors && gotError) { |
| return new Result<TestData>( |
| data, UnexpectedErrors, "Got unexpected error(s): $formattedErrors."); |
| } |
| if (world.warnings && !gotWarning) { |
| return new Result<TestData>( |
| data, MissingWarnings, "Expected warning, but didn't get any."); |
| } else if (!world.warnings && gotWarning) { |
| return new Result<TestData>(data, UnexpectedWarnings, |
| "Got unexpected warnings(s): $formattedWarnings."); |
| } |
| return null; |
| } |
| |
| 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]) { |
| 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() |
| ..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<bool> normalCompile(Uri input, Uri output, |
| {CompilerOptions? options}) async { |
| options ??= getOptions(); |
| TestIncrementalCompiler compiler = |
| new TestIncrementalCompiler(options, input); |
| List<int> bytes = |
| await normalCompileToBytes(input, options: options, compiler: compiler); |
| new File.fromUri(output).writeAsBytesSync(bytes); |
| return compiler.initializedFromDillForTesting; |
| } |
| |
| Future<List<int>> normalCompileToBytes(Uri input, |
| {CompilerOptions? options, IncrementalCompiler? compiler}) async { |
| Component component = await normalCompileToComponent(input, |
| options: options, compiler: compiler); |
| return util.postProcess(component); |
| } |
| |
| Future<Component> 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<Component> normalCompilePlain(Uri input, |
| {CompilerOptions? options, IncrementalCompiler? compiler}) async { |
| options ??= getOptions(); |
| compiler ??= new TestIncrementalCompiler(options, input); |
| return (await compiler.computeDelta()).component; |
| } |
| |
| 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); |
| } |
| IncrementalCompilerResult initializedCompilerResult = |
| await compiler.computeDelta(); |
| Component initializedComponent = initializedCompilerResult.component; |
| util.throwOnEmptyMixinBodies(initializedComponent); |
| await util.throwOnInsufficientUriToSource(initializedComponent); |
| bool result = compiler.initializedFromDillForTesting; |
| 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"); |
| } |
| |
| IncrementalCompilerResult initializedFullCompilerResult = |
| await compiler.computeDelta(fullComponent: true); |
| Component initializedFullComponent = initializedFullCompilerResult.component; |
| 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); |
| } |
| |
| IncrementalCompilerResult partialResult = await compiler.computeDelta(); |
| Component partialComponent = partialResult.component; |
| 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"); |
| } |
| |
| IncrementalCompilerResult emptyResult = await compiler.computeDelta(); |
| Component emptyComponent = emptyResult.component; |
| util.throwOnEmptyMixinBodies(emptyComponent); |
| await 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 { |
| @override |
| final TestRecorderForTesting recorderForTesting = |
| new TestRecorderForTesting(); |
| 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 (recorderForTesting.invalidatedImportUrisForTesting == null) return null; |
| |
| Set<String> invalidatedFilenames = |
| invalidatedUris.map((uri) => uri.pathSegments.last).toSet(); |
| Set<Uri> result = new Set<Uri>(); |
| for (Uri uri in recorderForTesting.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 |
| Future<IncrementalCompilerResult> computeDelta( |
| {List<Uri>? entryPoints, |
| bool fullComponent = false, |
| bool trackNeededDillLibraries = false, |
| bool? simulateTransformer}) async { |
| IncrementalCompilerResult result = await super.computeDelta( |
| entryPoints: entryPoints, |
| fullComponent: fullComponent, |
| trackNeededDillLibraries: trackNeededDillLibraries); |
| |
| // We should at least have the SDK builders available. Slight smoke test. |
| if (!dillTargetForTesting!.loader.libraryImportUris |
| .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.component); |
| } |
| return result; |
| } |
| } |
| |
| class TestRecorderForTesting extends RecorderForTesting { |
| Set<Uri>? invalidatedImportUrisForTesting; |
| AdvancedInvalidationResult? advancedInvalidationResult; |
| int? rebuildBodiesCount; |
| |
| @override |
| void recordInvalidatedImportUris(List<Uri> uris) { |
| invalidatedImportUrisForTesting = uris.isEmpty ? null : uris.toSet(); |
| } |
| |
| @override |
| void recordNonFullComponent(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 recordRebuildBodiesCount(int count) { |
| rebuildBodiesCount = count; |
| assert( |
| count == 0 || |
| advancedInvalidationResult == AdvancedInvalidationResult.bodiesOnly, |
| "Unexpected successful advanced invalidation: " |
| "${advancedInvalidationResult}"); |
| } |
| |
| @override |
| void recordAdvancedInvalidationResult(AdvancedInvalidationResult result) { |
| advancedInvalidationResult = result; |
| } |
| |
| @override |
| void recordTemporaryFile(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, |
| fieldReference: lib.reference.canonicalName |
| ?.getChildFromFieldWithName(fieldName) |
| .reference, |
| getterReference: lib.reference.canonicalName |
| ?.getChildFromFieldGetterWithName(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, |
| fieldReference: lib.reference.canonicalName |
| ?.getChildFromFieldWithName(fieldName) |
| .reference, |
| getterReference: c.reference.canonicalName |
| ?.getChildFromFieldGetterWithName(fieldName) |
| .reference, |
| fileUri: c.fileUri); |
| c.addField(field); |
| } |
| } |
| } |
| |
| class TestMemoryFileSystem extends MemoryFileSystem { |
| Uri holePunchBase; |
| |
| TestMemoryFileSystem(Uri currentDirectory, {required this.holePunchBase}) |