9e8fdef17c
Closes https://github.com/dart-lang/sdk/pull/63011 GitOrigin-RevId: 926534c2f5d3cd1e9629cf25f6b028ab34c00e64 Change-Id: I88460a060faabeae8f611e88f23b2eb5fc99f21a Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/491702 Reviewed-by: Daco Harkes <dacoharkes@google.com> Reviewed-by: Jens Johansen <jensj@google.com> Reviewed-by: Slava Egorov <vegorov@google.com> Commit-Queue: Daco Harkes <dacoharkes@google.com>
3305 lines
103 KiB
Dart
3305 lines
103 KiB
Dart
// 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 'dart:typed_data';
|
|
|
|
import 'package:_fe_analyzer_shared/src/messages/diagnostic_message.dart'
|
|
show CfeDiagnosticMessage, getMessageCodeObject;
|
|
import 'package:_fe_analyzer_shared/src/messages/severity.dart'
|
|
show CfeSeverity;
|
|
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/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 'utils/suite_utils.dart';
|
|
import '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> trackCreationLocations = Property.optional(
|
|
'trackCreationLocations',
|
|
BoolValue(),
|
|
defaultValue: false,
|
|
);
|
|
|
|
static const Property<bool> incrementalSerialization = Property.optional(
|
|
'incrementalSerialization',
|
|
BoolValue(),
|
|
defaultValue: false,
|
|
);
|
|
|
|
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 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": "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 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),
|
|
trackCreationLocations: NewWorldTestProperties.trackCreationLocations
|
|
.read(map, keys),
|
|
incrementalSerialization: NewWorldTestProperties
|
|
.incrementalSerialization
|
|
.read(map, keys),
|
|
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, Uint8List>> createModules(
|
|
Map<String, Map<String, String>> module,
|
|
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, Uint8List> moduleResult = {};
|
|
|
|
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 = (CfeDiagnosticMessage 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);
|
|
Uint8List 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 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.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);
|
|
|
|
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,
|
|
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 trackCreationLocations;
|
|
final bool incrementalSerialization;
|
|
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.trackCreationLocations,
|
|
required this.incrementalSerialization,
|
|
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,
|
|
trackCreationLocations: trackCreationLocations,
|
|
);
|
|
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,
|
|
() => throw new UnsupportedError(
|
|
"No platform dill for target '${targetName}'.",
|
|
),
|
|
)!;
|
|
|
|
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 Uint8List sdkSummaryData = await new File.fromUri(
|
|
platformUri,
|
|
).readAsBytes();
|
|
|
|
Uint8List? newestWholeComponentData;
|
|
TestMemoryFileSystem? fs;
|
|
Map<String, String?>? sourceFiles;
|
|
CompilerOptions? options;
|
|
TestIncrementalCompiler? compiler;
|
|
IncrementalSerializer? incrementalSerializer;
|
|
|
|
Map<String, Uint8List>? 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.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 = {};
|
|
}
|
|
}
|
|
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 = (CfeDiagnosticMessage 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 == CfeSeverity.error) {
|
|
gotError = true;
|
|
if (!formattedErrors.add(stringId) && !world.allowDuplicateErrors) {
|
|
Expect.fail("Got the same message twice: ${stringId}");
|
|
}
|
|
} else if (message.severity == CfeSeverity.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;
|
|
|
|
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<Uint8List?> serializationResult = checkIncrementalSerialization(
|
|
incrementalSerialization,
|
|
component!,
|
|
incrementalSerializer,
|
|
world,
|
|
);
|
|
if (!serializationResult.isPass) {
|
|
return serializationResult.copyWithOutput(data);
|
|
}
|
|
Uint8List? 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(
|
|
Uint8List newestWholeComponentData,
|
|
bool omitPlatform,
|
|
Uint8List 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 checkVariableStatement_binaryOffsetNoTag(
|
|
EquivalenceVisitor visitor,
|
|
VariableDeclaration node,
|
|
VariableDeclaration other,
|
|
) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
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<Uint8List?> checkIncrementalSerialization(
|
|
bool? incrementalSerialization,
|
|
Component component,
|
|
IncrementalSerializer? incrementalSerializer,
|
|
World world,
|
|
) {
|
|
if (incrementalSerialization == true) {
|
|
Component c = new Component(nameRoot: component.root)
|
|
..setMainMethodAndMode(null, false);
|
|
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<Uint8List>(
|
|
null,
|
|
IncrementalSerializationError,
|
|
"Incremental serialization added libraries!",
|
|
);
|
|
}
|
|
if (librariesBefore == librariesAfter &&
|
|
world.incrementalSerializationDoesWork) {
|
|
return new Result<Uint8List>(
|
|
null,
|
|
IncrementalSerializationError,
|
|
"Incremental serialization didn't remove any libraries!",
|
|
);
|
|
}
|
|
if (librariesAfter < librariesBefore && sink.builder.isEmpty) {
|
|
return new Result<Uint8List>(
|
|
null,
|
|
IncrementalSerializationError,
|
|
"Incremental serialization didn't output any bytes, "
|
|
"but did remove libraries",
|
|
);
|
|
} else if (librariesAfter == librariesBefore && !sink.builder.isEmpty) {
|
|
return new Result<Uint8List>(
|
|
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);
|
|
Uint8List 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<Uint8List>(
|
|
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<Uint8List?>(null);
|
|
|
|
// Check that the result is self-contained.
|
|
result = checkSelfContained(loadedComponent);
|
|
if (result != null) return result.copyWithOutput<Uint8List?>(null);
|
|
|
|
return new Result<Uint8List>.pass(bytes);
|
|
}
|
|
}
|
|
return new Result<Uint8List?>.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 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);
|
|
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.dill';
|
|
final Uri sdkRoot = computePlatformBinariesLocation(forceBuildDir: true);
|
|
CompilerOptions options = new CompilerOptions()
|
|
..target = target
|
|
..librariesSpecificationUri = Uri.base.resolve("sdk/lib/libraries.json")
|
|
..omitPlatform = true
|
|
..onDiagnostic = (CfeDiagnosticMessage message) {
|
|
if (message.severity == CfeSeverity.error ||
|
|
message.severity == CfeSeverity.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<Uint8List> 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})
|
|
: super(currentDirectory);
|
|
|
|
@override
|
|
MemoryFileSystemEntity entityForUri(Uri uri) {
|
|
if (uri.isScheme("holePunch")) {
|
|
// "Copy" the file into the memory file system.
|
|
Uri holePunchResolved = holePunchBase.resolve(uri.path);
|
|
File f = new File.fromUri(holePunchResolved);
|
|
MemoryFileSystemEntity entity = super.entityForUri(uri);
|
|
if (f.existsSync()) {
|
|
entity.writeAsBytesSync(f.readAsBytesSync());
|
|
}
|
|
return entity;
|
|
}
|
|
// Try to "sanitize" the uri as a real file system does, namely
|
|
// "a/b.dart" and "a//b.dart" returns the same file.
|
|
if (uri.pathSegments.contains("")) {
|
|
Uri newUri = uri.replace(
|
|
pathSegments: uri.pathSegments.where((element) => element != ""),
|
|
);
|
|
return super.entityForUri(newUri);
|
|
}
|
|
return super.entityForUri(uri);
|
|
}
|
|
}
|