// Copyright (c) 2016, 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.md file. // @dart = 2.9 library fasta.testing.suite; import 'dart:convert' show jsonDecode, utf8; import 'dart:io' show Directory, File, Platform; import 'dart:typed_data' show Uint8List; import 'package:_fe_analyzer_shared/src/scanner/token.dart' show LanguageVersionToken, Token; import 'package:_fe_analyzer_shared/src/util/colors.dart' as colors; import 'package:compiler/src/kernel/dart2js_target.dart'; import 'package:dev_compiler/dev_compiler.dart'; import 'package:front_end/src/api_prototype/compiler_options.dart' show CompilerOptions, DiagnosticMessage, parseExperimentalArguments, parseExperimentalFlags; import 'package:front_end/src/api_prototype/compiler_options.dart' show CompilerOptions, DiagnosticMessage; import 'package:front_end/src/api_prototype/constant_evaluator.dart' show ConstantEvaluator, ErrorReporter, EvaluationMode; import 'package:front_end/src/api_prototype/experimental_flags.dart' show AllowedExperimentalFlags, ExperimentalFlag, defaultAllowedExperimentalFlags, isExperimentEnabled; import 'package:front_end/src/api_prototype/file_system.dart' show FileSystem, FileSystemEntity, FileSystemException; import 'package:front_end/src/api_prototype/standard_file_system.dart' show StandardFileSystem; import 'package:front_end/src/base/libraries_specification.dart' show LibraryInfo; import 'package:front_end/src/base/processed_options.dart' show ProcessedOptions; import 'package:front_end/src/compute_platform_binaries_location.dart' show computePlatformBinariesLocation, computePlatformDillName; import 'package:front_end/src/base/command_line_options.dart' show Flags; import 'package:front_end/src/base/nnbd_mode.dart' show NnbdMode; import 'package:front_end/src/fasta/builder/library_builder.dart' show LibraryBuilder; import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext; import 'package:front_end/src/fasta/dill/dill_target.dart' show DillTarget; import 'package:front_end/src/fasta/incremental_compiler.dart' show IncrementalCompiler; import 'package:front_end/src/fasta/kernel/class_hierarchy_builder.dart' show ClassHierarchyNode; import 'package:front_end/src/fasta/kernel/kernel_builder.dart' show ClassHierarchyBuilder; import 'package:front_end/src/fasta/kernel/kernel_target.dart' show KernelTarget; import 'package:front_end/src/fasta/messages.dart' show LocatedMessage; import 'package:front_end/src/fasta/ticker.dart' show Ticker; import 'package:front_end/src/fasta/uri_translator.dart' show UriTranslator; import 'package:front_end/src/fasta/kernel/verifier.dart' show verifyComponent; import 'package:front_end/src/fasta/util/direct_parser_ast.dart' show DirectParserASTContentVisitor, getAST; import 'package:front_end/src/fasta/util/direct_parser_ast_helper.dart'; import 'package:kernel/ast.dart' show AwaitExpression, BasicLiteral, Component, Constant, ConstantExpression, Expression, FileUriExpression, FileUriNode, InvalidExpression, Library, LibraryPart, Member, Node, NonNullableByDefaultCompiledMode, TreeNode, UnevaluatedConstant, Version, Visitor, VisitorVoidMixin; import 'package:kernel/class_hierarchy.dart' show ClassHierarchy; import 'package:kernel/core_types.dart' show CoreTypes; import 'package:kernel/kernel.dart' show RecursiveResultVisitor, loadComponentFromBytes; import 'package:kernel/reference_from_index.dart' show ReferenceFromIndex; import 'package:kernel/target/changed_structure_notifier.dart' show ChangedStructureNotifier; import 'package:kernel/target/targets.dart' show ConstantsBackend, DiagnosticReporter, NoneConstantsBackend, NoneTarget, LateLowering, Target, TargetFlags; import 'package:kernel/target/targets.dart' show ConstantsBackend, DiagnosticReporter, NoneConstantsBackend, NoneTarget, NumberSemantics, Target, TargetFlags; import 'package:kernel/type_environment.dart' show StaticTypeContext, TypeEnvironment; import 'package:testing/testing.dart' show Chain, ChainContext, Expectation, ExpectationSet, Result, Step, TestDescription, StdioProcess; import 'package:vm/target/vm.dart' show VmTarget; import 'package:vm/transformations/type_flow/transformer.dart' as type_flow; import 'package:vm/transformations/pragma.dart' as type_flow; import '../../testing_utils.dart' show checkEnvironment; import '../../utils/kernel_chain.dart' show ComponentResult, KernelTextSerialization, MatchContext, MatchExpectation, Print, TypeCheck, WriteDill; import '../../utils/validating_instrumentation.dart' show ValidatingInstrumentation; export 'package:testing/testing.dart' show Chain, runMe; const String ENABLE_FULL_COMPILE = " full compile "; const String UPDATE_EXPECTATIONS = "updateExpectations"; const String UPDATE_COMMENTS = "updateComments"; const String EXPECTATIONS = ''' [ { "name": "ExpectationFileMismatch", "group": "Fail" }, { "name": "ExpectationFileMismatchSerialized", "group": "Fail" }, { "name": "ExpectationFileMissing", "group": "Fail" }, { "name": "InstrumentationMismatch", "group": "Fail" }, { "name": "TypeCheckError", "group": "Fail" }, { "name": "VerificationError", "group": "Fail" }, { "name": "TransformVerificationError", "group": "Fail" }, { "name": "TextSerializationFailure", "group": "Fail" }, { "name": "SemiFuzzFailure", "group": "Fail" }, { "name": "SemiFuzzCrash", "group": "Fail" } ] '''; const String KERNEL_TEXT_SERIALIZATION = " kernel text serialization "; final Expectation runtimeError = ExpectationSet.Default["RuntimeError"]; const String experimentalFlagOptions = '--enable-experiment='; const String overwriteCurrentSdkVersion = '--overwrite-current-sdk-version='; const String noVerifyCmd = '--no-verify'; final ExpectationSet staticExpectationSet = new ExpectationSet.fromJsonList(jsonDecode(EXPECTATIONS)); final Expectation semiFuzzFailure = staticExpectationSet["SemiFuzzFailure"]; final Expectation semiFuzzCrash = staticExpectationSet["SemiFuzzCrash"]; /// Options used for all tests within a given folder. /// /// This is used for instance for defining target, mode, and experiment specific /// test folders. class FolderOptions { final Map _explicitExperimentalFlags; final int forceLateLowerings; final bool forceLateLoweringSentinel; final bool forceStaticFieldLowering; final bool forceNoExplicitGetterCalls; final bool nnbdAgnosticMode; final Map defines; final bool noVerify; final String target; final String overwriteCurrentSdkVersion; FolderOptions(this._explicitExperimentalFlags, {this.forceLateLowerings: LateLowering.none, this.forceLateLoweringSentinel: false, this.forceStaticFieldLowering: false, this.forceNoExplicitGetterCalls: false, this.nnbdAgnosticMode: false, this.defines: const {}, this.noVerify: false, this.target: "vm", // can be null this.overwriteCurrentSdkVersion}) : assert(forceLateLowerings != null), assert(forceLateLoweringSentinel != null), assert(forceStaticFieldLowering != null), assert(forceNoExplicitGetterCalls != null), assert(nnbdAgnosticMode != null), assert( // no this doesn't make any sense but left to underline // that this is allowed to be null! defines != null || defines == null), assert(noVerify != null), assert(target != null); Map computeExplicitExperimentalFlags( Map forcedExperimentalFlags) { Map flags = {}; flags.addAll(_explicitExperimentalFlags); flags.addAll(forcedExperimentalFlags); return flags; } } /// Options for a single test located within its own subfolder. /// /// This is used for instance for defining custom link dependencies and /// setting up custom experimental flag defaults for a single test. class TestOptions { final Set linkDependencies; final NnbdMode nnbdMode; final AllowedExperimentalFlags allowedExperimentalFlags; final Map experimentEnabledVersion; final Map experimentReleasedVersion; Component component; List> errors; TestOptions(this.linkDependencies, {this.nnbdMode, this.allowedExperimentalFlags, this.experimentEnabledVersion, this.experimentReleasedVersion}) : assert(linkDependencies != null); } class FastaContext extends ChainContext with MatchContext { final Uri baseUri; final List steps; final Uri vm; final bool onlyCrashes; final Map explicitExperimentalFlags; final bool skipVm; final bool semiFuzz; final bool verify; final bool soundNullSafety; final Uri platformBinaries; final Map _uriTranslators = {}; final Map _folderOptions = {}; final Map _testOptions = {}; final Map _librariesJson = {}; @override final bool updateExpectations; @override String get updateExpectationsOption => '${UPDATE_EXPECTATIONS}=true'; @override bool get canBeFixWithUpdateExpectations => true; @override final ExpectationSet expectationSet = staticExpectationSet; Map _platforms = {}; FastaContext( this.baseUri, this.vm, this.platformBinaries, this.onlyCrashes, this.explicitExperimentalFlags, bool ignoreExpectations, this.updateExpectations, bool updateComments, this.skipVm, this.semiFuzz, bool kernelTextSerialization, bool fullCompile, this.verify, this.soundNullSafety) : steps = [ new Outline(fullCompile, updateComments: updateComments), const Print(), new Verify(fullCompile) ] { String fullPrefix; String outlinePrefix; if (soundNullSafety) { fullPrefix = '.strong'; outlinePrefix = '.strong.outline'; } else { fullPrefix = '.weak'; outlinePrefix = '.weak.outline'; } if (!fullCompile) { // If not doing a full compile this is the only expect file so we run the // extra constant evaluation now. If we do a full compilation, we'll do // if after the transformation. That also ensures we don't get the same // 'extra constant evaluation' output twice (in .transformed and not). steps.add(const StressConstantEvaluatorStep()); } if (!ignoreExpectations) { steps.add(new MatchExpectation( fullCompile ? "$fullPrefix.expect" : "$outlinePrefix.expect", serializeFirst: false, isLastMatchStep: false)); steps.add(new MatchExpectation( fullCompile ? "$fullPrefix.expect" : "$outlinePrefix.expect", serializeFirst: true, isLastMatchStep: true)); } steps.add(const TypeCheck()); steps.add(const EnsureNoErrors()); if (kernelTextSerialization) { steps.add(const KernelTextSerialization()); } if (fullCompile) { steps.add(const Transform()); steps.add(const Verify(true)); steps.add(const StressConstantEvaluatorStep()); if (!ignoreExpectations) { steps.add(new MatchExpectation("$fullPrefix.transformed.expect", serializeFirst: false, isLastMatchStep: updateExpectations)); if (!updateExpectations) { steps.add(new MatchExpectation("$fullPrefix.transformed.expect", serializeFirst: true, isLastMatchStep: true)); } } steps.add(const EnsureNoErrors()); if (!skipVm) { steps.add(const WriteDill()); } if (semiFuzz) { steps.add(const FuzzCompiles()); } // Notice: The below steps will run async, i.e. the next test will run // intertwined with this/these step(s). That for instance means that they // should not touch any ASTs! if (!skipVm) { steps.add(const Run()); } } } FolderOptions _computeFolderOptions(Directory directory) { FolderOptions folderOptions = _folderOptions[directory.uri]; if (folderOptions == null) { int forceLateLowering = LateLowering.none; bool forceLateLoweringSentinel = false; bool forceStaticFieldLowering = false; bool forceNoExplicitGetterCalls = false; bool nnbdAgnosticMode = false; bool noVerify = false; Map defines = {}; String target = "vm"; if (directory.uri == baseUri) { folderOptions = new FolderOptions({}, forceLateLowerings: forceLateLowering, forceLateLoweringSentinel: forceLateLoweringSentinel, forceStaticFieldLowering: forceStaticFieldLowering, forceNoExplicitGetterCalls: forceNoExplicitGetterCalls, nnbdAgnosticMode: nnbdAgnosticMode, defines: defines, noVerify: noVerify, target: target); } else { File optionsFile = new File.fromUri(directory.uri.resolve('folder.options')); if (optionsFile.existsSync()) { List experimentalFlagsArguments = []; String overwriteCurrentSdkVersionArgument = null; for (String line in optionsFile.readAsStringSync().split('\n')) { line = line.trim(); if (line.startsWith(experimentalFlagOptions)) { experimentalFlagsArguments = line.substring(experimentalFlagOptions.length).split(','); } else if (line.startsWith(overwriteCurrentSdkVersion)) { overwriteCurrentSdkVersionArgument = line.substring(overwriteCurrentSdkVersion.length); } else if (line.startsWith(Flags.forceLateLoweringSentinel)) { forceLateLoweringSentinel = true; } else if (line.startsWith('${Flags.forceLateLowering}=')) { int mask = int.parse( line.substring('${Flags.forceLateLowering}='.length)); forceLateLowering = mask; } else if (line.startsWith(Flags.forceLateLowering)) { forceLateLowering = LateLowering.all; } else if (line.startsWith(Flags.forceStaticFieldLowering)) { forceStaticFieldLowering = true; } else if (line.startsWith(Flags.forceNoExplicitGetterCalls)) { forceNoExplicitGetterCalls = true; } else if (line.startsWith(Flags.forceNoExplicitGetterCalls)) { forceNoExplicitGetterCalls = true; } else if (line.startsWith(Flags.nnbdAgnosticMode)) { nnbdAgnosticMode = true; } else if (line.startsWith(Flags.noDefines)) { if (defines == null) { throw "Specifying ${Flags.noDefines} several times " "is unsupported."; } if (defines.isNotEmpty) { throw "Can't have no defines and specific defines " "at the same time."; } defines = null; } else if (line.startsWith("-D")) { if (defines == null) { throw "Can't have no defines and specific defines " "at the same time."; } String define = line.substring(2); // removes "-D". int index = define.indexOf('='); String name; String expression; if (index != -1) { name = define.substring(0, index); expression = define.substring(index + 1); } else { name = define; expression = define; } if (defines.containsKey(name)) { throw "Defining '$name' several times is unsupported."; } defines[name] = expression; } else if (line.startsWith(noVerifyCmd)) { noVerify = true; } else if (line.startsWith(Flags.target) && line.indexOf('=') == Flags.target.length) { target = line.substring(Flags.target.length + 1); } else if (line.isNotEmpty) { throw new UnsupportedError("Unsupported test option '$line'"); } } folderOptions = new FolderOptions( parseExperimentalFlags( parseExperimentalArguments(experimentalFlagsArguments), onError: (String message) => throw new ArgumentError(message), onWarning: (String message) => throw new ArgumentError(message)), forceLateLowerings: forceLateLowering, forceLateLoweringSentinel: forceLateLoweringSentinel, forceStaticFieldLowering: forceStaticFieldLowering, forceNoExplicitGetterCalls: forceNoExplicitGetterCalls, nnbdAgnosticMode: nnbdAgnosticMode, defines: defines, noVerify: noVerify, target: target, overwriteCurrentSdkVersion: overwriteCurrentSdkVersionArgument); } else { folderOptions = _computeFolderOptions(directory.parent); } } _folderOptions[directory.uri] = folderOptions; } return folderOptions; } /// Computes the experimental flag for [description]. /// /// [forcedExperimentalFlags] is used to override the default flags for /// [description]. FolderOptions computeFolderOptions(TestDescription description) { Directory directory = new File.fromUri(description.uri).parent; return _computeFolderOptions(directory); } Future computeUriTranslator( TestDescription description) async { UriConfiguration uriConfiguration = computeUriConfiguration(description); UriTranslator uriTranslator = _uriTranslators[uriConfiguration]; if (uriTranslator == null) { Uri sdk = Uri.base.resolve("sdk/"); Uri packages = Uri.base.resolve(".packages"); FolderOptions folderOptions = computeFolderOptions(description); CompilerOptions compilerOptions = new CompilerOptions() ..onDiagnostic = (DiagnosticMessage message) { throw message.plainTextFormatted.join("\n"); } ..sdkRoot = sdk ..packagesFileUri = uriConfiguration.packageConfigUri ?? packages ..environmentDefines = folderOptions.defines ..explicitExperimentalFlags = folderOptions .computeExplicitExperimentalFlags(explicitExperimentalFlags) ..nnbdMode = soundNullSafety ? (folderOptions.nnbdAgnosticMode ? NnbdMode.Agnostic : NnbdMode.Strong) : NnbdMode.Weak ..librariesSpecificationUri = uriConfiguration.librariesSpecificationUri; if (folderOptions.overwriteCurrentSdkVersion != null) { compilerOptions.currentSdkVersion = folderOptions.overwriteCurrentSdkVersion; } ProcessedOptions options = new ProcessedOptions(options: compilerOptions); uriTranslator = await options.getUriTranslator(); _uriTranslators[uriConfiguration] = uriTranslator; } return uriTranslator; } /// Computes the test for [description]. TestOptions computeTestOptions(TestDescription description) { Directory directory = new File.fromUri(description.uri).parent; TestOptions testOptions = _testOptions[directory.uri]; if (testOptions == null) { File optionsFile = new File.fromUri(directory.uri.resolve('test.options')); Set linkDependencies = new Set(); NnbdMode nnbdMode; AllowedExperimentalFlags allowedExperimentalFlags; Map experimentEnabledVersion; Map experimentReleasedVersion; if (optionsFile.existsSync()) { for (String line in optionsFile.readAsStringSync().split('\n')) { line = line.trim(); if (line.isEmpty) continue; if (line.startsWith(Flags.nnbdAgnosticMode)) { if (nnbdMode != null) { throw new UnsupportedError( 'Nnbd mode $nnbdMode already specified.'); } nnbdMode = NnbdMode.Agnostic; } else if (line.startsWith(Flags.nnbdStrongMode)) { if (nnbdMode != null) { throw new UnsupportedError( 'Nnbd mode $nnbdMode already specified.'); } nnbdMode = NnbdMode.Strong; } else if (line.startsWith(Flags.nnbdWeakMode)) { if (nnbdMode != null) { throw new UnsupportedError( 'Nnbd mode $nnbdMode already specified.'); } nnbdMode = NnbdMode.Weak; } else if (line == '--fix-nnbd-release-version') { // Allow package:allowed_package to use nnbd features from version // 2.9. allowedExperimentalFlags = new AllowedExperimentalFlags( sdkDefaultExperiments: defaultAllowedExperimentalFlags.sdkDefaultExperiments, sdkLibraryExperiments: defaultAllowedExperimentalFlags.sdkLibraryExperiments, packageExperiments: { ...defaultAllowedExperimentalFlags.packageExperiments, 'allowed_package': {ExperimentalFlag.nonNullable} }); experimentEnabledVersion = const { ExperimentalFlag.nonNullable: const Version(2, 10) }; experimentReleasedVersion = const { ExperimentalFlag.nonNullable: const Version(2, 9) }; } else { Uri uri = description.uri.resolve(line); if (uri.scheme != 'package') { File f = new File.fromUri(uri); if (!f.existsSync()) { throw new UnsupportedError("No file found: $f ($line)"); } uri = f.uri; } linkDependencies.add(uri); } } } testOptions = new TestOptions(linkDependencies, nnbdMode: nnbdMode, allowedExperimentalFlags: allowedExperimentalFlags, experimentEnabledVersion: experimentEnabledVersion, experimentReleasedVersion: experimentReleasedVersion); _testOptions[directory.uri] = testOptions; } return testOptions; } /// Libraries json for [description]. Uri computeLibrariesSpecificationUri(TestDescription description) { Directory directory = new File.fromUri(description.uri).parent; if (_librariesJson.containsKey(directory.uri)) { return _librariesJson[directory.uri]; } else { Uri librariesJson; File jsonFile = new File.fromUri(directory.uri.resolve('libraries.json')); if (jsonFile.existsSync()) { librariesJson = jsonFile.uri; } return _librariesJson[directory.uri] = librariesJson; } } /// Custom package config used for [description]. Uri computePackageConfigUri(TestDescription description) { Uri packageConfig = description.uri.resolve(".dart_tool/package_config.json"); return new File.fromUri(packageConfig).existsSync() ? packageConfig : null; } UriConfiguration computeUriConfiguration(TestDescription description) { Uri librariesSpecificationUri = computeLibrariesSpecificationUri(description); Uri packageConfigUri = computePackageConfigUri(description); return new UriConfiguration(librariesSpecificationUri, packageConfigUri); } Expectation get verificationError => expectationSet["VerificationError"]; Uri _getPlatformUri(Target target, NnbdMode nnbdMode) { String fileName = computePlatformDillName( target, nnbdMode, () => throw new UnsupportedError( "No platform dill for target '${target.name}' with $nnbdMode.")); return platformBinaries.resolve(fileName); } Future loadPlatform(Target target, NnbdMode nnbdMode) async { Uri uri = _getPlatformUri(target, nnbdMode); return _platforms.putIfAbsent(uri, () { return loadComponentFromBytes(new File.fromUri(uri).readAsBytesSync()); }); } void clearPlatformCache(Target target, NnbdMode nnbdMode) async { Uri uri = _getPlatformUri(target, nnbdMode); _platforms.remove(uri); } @override Result processTestResult( TestDescription description, Result result, bool last) { if (onlyCrashes) { Expectation outcome = result.outcome; if (outcome == Expectation.Crash || outcome == verificationError) { return result; } return result.copyWithOutcome(Expectation.Pass); } return super.processTestResult(description, result, last); } @override Set processExpectedOutcomes( Set outcomes, TestDescription description) { // Remove outcomes related to phases not currently in effect. Set result; // If skipping VM we can't get a runtime error. if (skipVm && outcomes.contains(runtimeError)) { result ??= new Set.from(outcomes); result.remove(runtimeError); } // If not semi-fuzzing we can't get semi-fuzz errors. if (!semiFuzz && (outcomes.contains(semiFuzzFailure) || outcomes.contains(semiFuzzCrash))) { result ??= new Set.from(outcomes); result.remove(semiFuzzFailure); result.remove(semiFuzzCrash); } // Fast-path: no changes made. if (result == null) return outcomes; // Changes made: No expectations left. This happens when all expected // outcomes are removed above. // We have to put in the implicit assumption that it will pass then. if (result.isEmpty) return {Expectation.Pass}; // Changes made with at least one expectation left. That's out result! return result; } static Future create( Chain suite, Map environment) async { const Set knownEnvironmentKeys = { "enableExtensionMethods", "enableNonNullable", "soundNullSafety", "onlyCrashes", "ignoreExpectations", UPDATE_EXPECTATIONS, UPDATE_COMMENTS, "skipVm", "semiFuzz", "verify", KERNEL_TEXT_SERIALIZATION, "platformBinaries", ENABLE_FULL_COMPILE, }; checkEnvironment(environment, knownEnvironmentKeys); String resolvedExecutable = Platform.environment['resolvedExecutable'] ?? Platform.resolvedExecutable; Uri vm = Uri.base.resolveUri(new Uri.file(resolvedExecutable)); Map experimentalFlags = {}; void addForcedExperimentalFlag(String name, ExperimentalFlag flag) { if (environment.containsKey(name)) { experimentalFlags[flag] = environment[name] == "true"; } } addForcedExperimentalFlag( "enableExtensionMethods", ExperimentalFlag.extensionMethods); addForcedExperimentalFlag( "enableNonNullable", ExperimentalFlag.nonNullable); bool soundNullSafety = environment["soundNullSafety"] == "true"; bool onlyCrashes = environment["onlyCrashes"] == "true"; bool ignoreExpectations = environment["ignoreExpectations"] == "true"; bool updateExpectations = environment[UPDATE_EXPECTATIONS] == "true"; bool updateComments = environment[UPDATE_COMMENTS] == "true"; bool skipVm = environment["skipVm"] == "true"; bool semiFuzz = environment["semiFuzz"] == "true"; bool verify = environment["verify"] != "false"; bool kernelTextSerialization = environment.containsKey(KERNEL_TEXT_SERIALIZATION); String platformBinaries = environment["platformBinaries"]; if (platformBinaries != null && !platformBinaries.endsWith('/')) { platformBinaries = '$platformBinaries/'; } return new FastaContext( suite.uri, vm, platformBinaries == null ? computePlatformBinariesLocation(forceBuildDir: true) : Uri.base.resolve(platformBinaries), onlyCrashes, experimentalFlags, ignoreExpectations, updateExpectations, updateComments, skipVm, semiFuzz, kernelTextSerialization, environment.containsKey(ENABLE_FULL_COMPILE), verify, soundNullSafety); } } class Run extends Step { const Run(); String get name => "run"; /// WARNING: Every subsequent step in this test will run async as well! bool get isAsync => true; Future> run( ComponentResult result, FastaContext context) async { FolderOptions folderOptions = context.computeFolderOptions(result.description); Map experimentalFlags = folderOptions .computeExplicitExperimentalFlags(context.explicitExperimentalFlags); switch (folderOptions.target) { case "vm": if (context._platforms.isEmpty) { throw "Executed `Run` step before initializing the context."; } File generated = new File.fromUri(result.outputUri); StdioProcess process; try { var args = []; if (experimentalFlags[ExperimentalFlag.nonNullable] == true) { if (context.soundNullSafety) { args.add("--sound-null-safety"); } } args.add(generated.path); process = await StdioProcess.run(context.vm.toFilePath(), args); print(process.output); } finally { await generated.parent.delete(recursive: true); } Result runResult = process.toResult(); if (result.component.mode == NonNullableByDefaultCompiledMode.Invalid) { // In this case we expect and want a runtime error. if (runResult.outcome == ExpectationSet.Default["RuntimeError"]) { // We convert this to pass because that's exactly what we'd expect. return pass(result); } else { // Different outcome - that's a failure! return new Result(result, ExpectationSet.Default["MissingRuntimeError"], runResult.error); } } return new Result( result, runResult.outcome, runResult.error); case "aot": case "none": case "dart2js": case "dartdevc": // TODO(johnniwinther): Support running vm aot, dart2js and/or dartdevc. return pass(result); default: throw new ArgumentError( "Unsupported run target '${folderOptions.target}'."); } } } class StressConstantEvaluatorStep extends Step { const StressConstantEvaluatorStep(); String get name => "stress constant evaluator"; Future> run( ComponentResult result, FastaContext context) async { KernelTarget target = result.sourceTarget; ConstantsBackend constantsBackend = target.backendTarget.constantsBackend(target.loader.coreTypes); TypeEnvironment environment = new TypeEnvironment(target.loader.coreTypes, target.loader.hierarchy); StressConstantEvaluatorVisitor stressConstantEvaluatorVisitor = new StressConstantEvaluatorVisitor( constantsBackend, result.options.environmentDefines, target.isExperimentEnabledGlobally(ExperimentalFlag.tripleShift), environment, !target.backendTarget.supportsSetLiterals, result.options.errorOnUnevaluatedConstant, target.getConstantEvaluationModeForTesting(), ); for (Library lib in result.component.libraries) { if (!result.isUserLibrary(lib)) continue; lib.accept(stressConstantEvaluatorVisitor); } if (stressConstantEvaluatorVisitor.success > 0) { result.extraConstantStrings.addAll(stressConstantEvaluatorVisitor.output); result.extraConstantStrings.add("Extra constant evaluation: " "evaluated: ${stressConstantEvaluatorVisitor.tries}, " "effectively constant: ${stressConstantEvaluatorVisitor.success}"); } return pass(result); } } class StressConstantEvaluatorVisitor extends RecursiveResultVisitor implements ErrorReporter { ConstantEvaluator constantEvaluator; ConstantEvaluator constantEvaluatorWithEmptyEnvironment; int tries = 0; int success = 0; List output = []; StressConstantEvaluatorVisitor( ConstantsBackend backend, Map environmentDefines, bool enableTripleShift, TypeEnvironment typeEnvironment, bool desugarSets, bool errorOnUnevaluatedConstant, EvaluationMode evaluationMode) { constantEvaluator = new ConstantEvaluator( backend, environmentDefines, typeEnvironment, this, enableTripleShift: enableTripleShift, errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, evaluationMode: evaluationMode); constantEvaluatorWithEmptyEnvironment = new ConstantEvaluator( backend, {}, typeEnvironment, this, enableTripleShift: enableTripleShift, errorOnUnevaluatedConstant: errorOnUnevaluatedConstant, evaluationMode: evaluationMode); } Library currentLibrary; Library visitLibrary(Library node) { currentLibrary = node; node.visitChildren(this); currentLibrary = null; return node; } Member currentMember; Node defaultMember(Member node) { Member prevCurrentMember = currentMember; currentMember = node; node.visitChildren(this); currentMember = prevCurrentMember; return node; } Node defaultExpression(Expression node) { if (node is BasicLiteral) return node; if (node is InvalidExpression) return node; if (node is ConstantExpression) { bool evaluate = false; if (node.constant is UnevaluatedConstant) { UnevaluatedConstant unevaluatedConstant = node.constant; if (unevaluatedConstant.expression is! InvalidExpression) { evaluate = true; } } if (!evaluate) return node; if (constantEvaluator.environmentDefines != null) { throw "Unexpected UnevaluatedConstant " "when the environment is not null."; } } // Try to evaluate it as a constant. tries++; StaticTypeContext staticTypeContext; if (currentMember == null) { staticTypeContext = new StaticTypeContext.forAnnotations( currentLibrary, constantEvaluator.typeEnvironment); } else { staticTypeContext = new StaticTypeContext( currentMember, constantEvaluator.typeEnvironment); } Constant x = constantEvaluator.evaluate(staticTypeContext, node); bool evaluatedWithEmptyEnvironment = false; if (x is UnevaluatedConstant && x.expression is! InvalidExpression) { // try with an environment if (constantEvaluator.environmentDefines != null) { throw "Unexpected UnevaluatedConstant (with an InvalidExpression in " "it) when the environment is not null."; } x = constantEvaluatorWithEmptyEnvironment.evaluate( new StaticTypeContext( currentMember, constantEvaluator.typeEnvironment), new ConstantExpression(x)); evaluatedWithEmptyEnvironment = true; } if (x is UnevaluatedConstant) { if (x.expression is! InvalidExpression && x.expression is! FileUriExpression) { throw "Unexpected ${x.runtimeType} with " "${x.expression.runtimeType} inside."; } node.visitChildren(this); } else { success++; if (!evaluatedWithEmptyEnvironment) { output .add("Evaluated: ${node.runtimeType} @ ${getLocation(node)} -> $x"); // Don't recurse into children - theoretically we could replace this // node with a constant expression. } else { output.add("Evaluated with empty environment: " "${node.runtimeType} @ ${getLocation(node)} -> $x"); // Here we (for now) recurse into children. node.visitChildren(this); } } return node; } String getLocation(TreeNode node) { try { return node.location.toString(); } catch (e) { TreeNode n = node; while (n != null && n is! FileUriNode) { n = n.parent; } if (n == null) return "(unknown location)"; FileUriNode fileUriNode = n; return ("(unknown position in ${fileUriNode.fileUri})"); } } @override void report(LocatedMessage message, List context) { // ignored. } @override void reportInvalidExpression(InvalidExpression node) { // ignored. } } class CompilationSetup { final TestOptions testOptions; final FolderOptions folderOptions; final CompilerOptions compilerOptions; final ProcessedOptions options; final List> errors; final CompilerOptions Function( NnbdMode nnbdMode, AllowedExperimentalFlags allowedExperimentalFlags, Map experimentEnabledVersion, Map experimentReleasedVersion) createCompilerOptions; final ProcessedOptions Function(CompilerOptions compilerOptions) createProcessedOptions; CompilationSetup( this.testOptions, this.folderOptions, this.compilerOptions, this.options, this.errors, this.createCompilerOptions, this.createProcessedOptions); } CompilationSetup createCompilationSetup( TestDescription description, FastaContext context) { List> errors = >[]; Uri librariesSpecificationUri = context.computeLibrariesSpecificationUri(description); TestOptions testOptions = context.computeTestOptions(description); FolderOptions folderOptions = context.computeFolderOptions(description); Map experimentalFlags = folderOptions .computeExplicitExperimentalFlags(context.explicitExperimentalFlags); NnbdMode nnbdMode = !context.soundNullSafety || !isExperimentEnabled(ExperimentalFlag.nonNullable, explicitExperimentalFlags: experimentalFlags) ? NnbdMode.Weak : (folderOptions.nnbdAgnosticMode ? NnbdMode.Agnostic : NnbdMode.Strong); List inputs = [description.uri]; CompilerOptions createCompilerOptions( NnbdMode nnbdMode, AllowedExperimentalFlags allowedExperimentalFlags, Map experimentEnabledVersion, Map experimentReleasedVersion) { CompilerOptions compilerOptions = new CompilerOptions() ..onDiagnostic = (DiagnosticMessage message) { errors.add(message.plainTextFormatted); } ..environmentDefines = folderOptions.defines ..explicitExperimentalFlags = experimentalFlags ..nnbdMode = nnbdMode ..librariesSpecificationUri = librariesSpecificationUri ..allowedExperimentalFlagsForTesting = allowedExperimentalFlags ..experimentEnabledVersionForTesting = experimentEnabledVersion ..experimentReleasedVersionForTesting = experimentReleasedVersion ..skipPlatformVerification = true ..target = createTarget(folderOptions, context); if (folderOptions.overwriteCurrentSdkVersion != null) { compilerOptions.currentSdkVersion = folderOptions.overwriteCurrentSdkVersion; } return compilerOptions; } ProcessedOptions createProcessedOptions(CompilerOptions compilerOptions) { return new ProcessedOptions(options: compilerOptions, inputs: inputs); } // Disable colors to ensure that expectation files are the same across // platforms and independent of stdin/stderr. colors.enableColors = false; CompilerOptions compilerOptions = createCompilerOptions( nnbdMode, testOptions.allowedExperimentalFlags, testOptions.experimentEnabledVersion, testOptions.experimentReleasedVersion); ProcessedOptions options = createProcessedOptions(compilerOptions); return new CompilationSetup(testOptions, folderOptions, compilerOptions, options, errors, createCompilerOptions, createProcessedOptions); } class FuzzCompiles extends Step { const FuzzCompiles(); String get name { return "semifuzz"; } Future> run( ComponentResult result, FastaContext context) async { bool originalFlag = context.explicitExperimentalFlags[ ExperimentalFlag.alternativeInvalidationStrategy]; context.explicitExperimentalFlags[ ExperimentalFlag.alternativeInvalidationStrategy] = true; CompilationSetup compilationSetup = createCompilationSetup(result.description, context); Target backendTarget = compilationSetup.options.target; if (backendTarget is TestTarget) { // For the fuzzing we want to run the VM transformations, i.e. have the // incremental compiler behave as normal. backendTarget.performModularTransformations = true; } UriTranslator uriTranslator = await context.computeUriTranslator(result.description); Component platform = await context.loadPlatform( backendTarget, compilationSetup.options.nnbdMode); Result passResult = await performFileInvalidation( compilationSetup, platform, uriTranslator, result, context, originalFlag); if (passResult != null) return passResult; passResult = await performChunkReordering(compilationSetup, platform, uriTranslator, result, context, originalFlag); if (passResult != null) return passResult; return pass(result); } /// Perform a number of compilations where each user-file is invalidated /// one at a time, and the code recompiled after each invalidation. /// Verifies that either it's an error in all cases or in no cases. /// Verifies that the same libraries comes out as a result. Future> performFileInvalidation( CompilationSetup compilationSetup, Component platform, UriTranslator uriTranslator, ComponentResult result, FastaContext context, bool originalFlag) async { compilationSetup.errors.clear(); IncrementalCompiler incrementalCompiler = new IncrementalCompiler.fromComponent( new CompilerContext(compilationSetup.options), platform); final Component component = await incrementalCompiler.computeDelta(); final Set userLibraries = createUserLibrariesImportUriSet(component, uriTranslator); final bool expectErrors = compilationSetup.errors.isNotEmpty; List> originalErrors = new List>.from(compilationSetup.errors); Set intersectionUserLibraries = result.userLibraries.intersection(userLibraries); if (intersectionUserLibraries.length != userLibraries.length || userLibraries.length != result.userLibraries.length) { return new Result( result, semiFuzzFailure, "Got a different amount of user libraries on first compile " "compared to 'original' compilation:\n\n" "This compile:\n" "${userLibraries.map((e) => e.toString()).join("\n")}\n\n" "Original compile:\n" "${result.userLibraries.map((e) => e.toString()).join("\n")}"); } compilationSetup.errors.clear(); for (Uri importUri in userLibraries) { incrementalCompiler.invalidate(importUri); final Component newComponent = await incrementalCompiler.computeDelta(fullComponent: true); final Set newUserLibraries = createUserLibrariesImportUriSet(newComponent, uriTranslator); final bool gotErrors = compilationSetup.errors.isNotEmpty; if (expectErrors != gotErrors) { if (expectErrors) { String errorsString = originalErrors.map((error) => error.join('\n')).join('\n\n'); return new Result( result, semiFuzzFailure, "Expected these errors:\n${errorsString}\n\n" "but didn't get any after invalidating $importUri"); } else { String errorsString = compilationSetup.errors .map((error) => error.join('\n')) .join('\n\n'); return new Result( result, semiFuzzFailure, "Unexpected errors:\n${errorsString}\n\n" "after invalidating $importUri"); } } Set intersectionUserLibraries = userLibraries.intersection(newUserLibraries); if (intersectionUserLibraries.length != newUserLibraries.length || newUserLibraries.length != userLibraries.length) { return new Result( result, semiFuzzFailure, "Got a different amount of user libraries on recompile " "compared to 'original' compilation after having invalidated " "$importUri.\n\n" "This compile:\n" "${newUserLibraries.map((e) => e.toString()).join("\n")}\n\n" "Original compile:\n" "${result.userLibraries.map((e) => e.toString()).join("\n")}"); } } context.explicitExperimentalFlags[ ExperimentalFlag.alternativeInvalidationStrategy] = originalFlag; return null; } /// Perform a number of compilations where each user-file is in turn sorted /// in both ascending and descending order (i.e. the procedures and classes /// etc are sorted). /// Verifies that either it's an error in all cases or in no cases. /// Verifies that the same libraries comes out as a result. Future> performChunkReordering( CompilationSetup compilationSetup, Component platform, UriTranslator uriTranslator, ComponentResult result, FastaContext context, bool originalFlag) async { compilationSetup.errors.clear(); FileSystem orgFileSystem = compilationSetup.options.fileSystem; compilationSetup.options.clearFileSystemCache(); _FakeFileSystem fs = new _FakeFileSystem(orgFileSystem); compilationSetup.compilerOptions.fileSystem = fs; IncrementalCompiler incrementalCompiler = new IncrementalCompiler.fromComponent( new CompilerContext(compilationSetup.options), platform); await incrementalCompiler.computeDelta(); final bool expectErrors = compilationSetup.errors.isNotEmpty; List> originalErrors = new List>.from(compilationSetup.errors); compilationSetup.errors.clear(); // Create lookup-table from file uri to whatever. Map builders = {}; for (LibraryBuilder builder in incrementalCompiler.userCode.loader.builders.values) { if (builder.importUri.scheme == "dart" && !builder.isSynthetic) continue; builders[builder.fileUri] = builder; for (LibraryPart part in builder.library.parts) { Uri thisPartUri = builder.importUri.resolve(part.partUri); if (thisPartUri.scheme == "package") { thisPartUri = incrementalCompiler.userCode.uriTranslator.translate(thisPartUri); } builders[thisPartUri] = builder; } } for (Uri uri in fs.data.keys) { print("Work on $uri"); LibraryBuilder builder = builders[uri]; if (builder == null) { print("Skipping $uri -- couldn't find builder for it."); continue; } Uint8List orgData = fs.data[uri]; FuzzAstVisitorSorter fuzzAstVisitorSorter; try { fuzzAstVisitorSorter = new FuzzAstVisitorSorter(orgData, builder.isNonNullableByDefault); } on FormatException catch (e, st) { // UTF-16-LE formatted test crashes `utf8.decode(bytes)` --- catch that return new Result( result, semiFuzzCrash, "$e\n\n" "$st"); } // Sort ascending and then compile. Then sort descending and try again. for (void Function() sorter in [ () => fuzzAstVisitorSorter.sortAscending(), () => fuzzAstVisitorSorter.sortDescending(), ]) { sorter(); StringBuffer sb = new StringBuffer(); for (FuzzAstVisitorSorterChunk chunk in fuzzAstVisitorSorter.chunks) { sb.writeln(chunk.getSource()); } Uint8List sortedData = utf8.encode(sb.toString()); fs.data[uri] = sortedData; incrementalCompiler = new IncrementalCompiler.fromComponent( new CompilerContext(compilationSetup.options), platform); try { await incrementalCompiler.computeDelta(); } catch (e, st) { return new Result( result, semiFuzzCrash, "Crashed with '$e' after reordering '$uri' to\n\n" "$sb\n\n" "$st"); } final bool gotErrors = compilationSetup.errors.isNotEmpty; String errorsString = compilationSetup.errors .map((error) => error.join('\n')) .join('\n\n'); compilationSetup.errors.clear(); // TODO(jensj): When we get errors we should try to verify it's // "the same" errors (note, though, that they will naturally be at a // changed location --- some will likely have different wording). if (expectErrors != gotErrors) { if (expectErrors) { String errorsString = originalErrors.map((error) => error.join('\n')).join('\n\n'); return new Result( result, semiFuzzFailure, "Expected these errors:\n${errorsString}\n\n" "but didn't get any after reordering $uri " "to have this content:\n\n" "$sb"); } else { return new Result( result, semiFuzzFailure, "Unexpected errors:\n${errorsString}\n\n" "after reordering $uri to have this content:\n\n" "$sb"); } } } } compilationSetup.options.clearFileSystemCache(); compilationSetup.compilerOptions.fileSystem = orgFileSystem; return null; } } class FuzzAstVisitorSorterChunk { final String data; final String metadataAndComments; final int layer; FuzzAstVisitorSorterChunk(this.data, this.metadataAndComments, this.layer); String toString() { return "FuzzAstVisitorSorterChunk[${getSource()}]"; } String getSource() { if (metadataAndComments != null) { return "$metadataAndComments\n$data"; } return "$data"; } } enum FuzzSorterState { nonSortable, importExportSortable, sortableRest } class FuzzAstVisitorSorter extends DirectParserASTContentVisitor { final Uint8List bytes; final String asString; final bool nnbd; FuzzAstVisitorSorter(this.bytes, this.nnbd) : asString = utf8.decode(bytes) { DirectParserASTContentCompilationUnitEnd ast = getAST(bytes, includeBody: false, includeComments: true, enableExtensionMethods: true, enableNonNullable: nnbd); accept(ast); if (metadataStart != null) { String metadata = asString.substring( metadataStart.charOffset, metadataEndInclusive.charEnd); layer++; chunks.add(new FuzzAstVisitorSorterChunk( "", metadata, layer, )); } } void sortAscending() { chunks.sort(_ascendingSorter); } void sortDescending() { chunks.sort(_descendingSorter); } int _ascendingSorter( FuzzAstVisitorSorterChunk a, FuzzAstVisitorSorterChunk b) { if (a.layer < b.layer) return -1; if (a.layer > b.layer) return 1; return a.data.compareTo(b.data); } int _descendingSorter( FuzzAstVisitorSorterChunk a, FuzzAstVisitorSorterChunk b) { // Only sort layers differently internally. if (a.layer < b.layer) return -1; if (a.layer > b.layer) return 1; return b.data.compareTo(a.data); } List chunks = []; Token metadataStart; Token metadataEndInclusive; int layer = 0; FuzzSorterState state = null; /// If there's any LanguageVersionToken in the comment preceding the given /// token add it as a separate chunk to keep it in place. void _chunkOutLanguageVersionComment(Token fromToken) { Token comment = fromToken.precedingComments; bool hasLanguageVersion = comment is LanguageVersionToken; while (comment.next != null) { comment = comment.next; hasLanguageVersion |= comment is LanguageVersionToken; } if (hasLanguageVersion) { layer++; chunks.add(new FuzzAstVisitorSorterChunk( asString.substring( fromToken.precedingComments.charOffset, comment.charEnd), null, layer, )); layer++; } } void handleData( FuzzSorterState thisState, Token startInclusive, Token endInclusive) { // Non-sortable things always gets a new layer. if (state != thisState || thisState == FuzzSorterState.nonSortable) { state = thisState; layer++; } // "Chunk out" any language version at the top, i.e. if there are no other // chunks and there is a metadata, any language version chunk on the // metadata will be "chunked out". If there is no metadata, any language // version on the non-metadata will be "chunked out". // Note that if there is metadata and there is a language version on the // non-metadata it will not be chunked out as it's in an illegal place // anyway, so possibly allowing it to be sorted (and put in another place) // won't make it more or less illegal. if (metadataStart != null && metadataStart.precedingComments != null && chunks.isEmpty) { _chunkOutLanguageVersionComment(metadataStart); } else if (metadataStart == null && startInclusive.precedingComments != null && chunks.isEmpty) { _chunkOutLanguageVersionComment(startInclusive); } String metadata; if (metadataStart != null || metadataEndInclusive != null) { metadata = asString.substring( metadataStart.charOffset, metadataEndInclusive.charEnd); } chunks.add(new FuzzAstVisitorSorterChunk( asString.substring(startInclusive.charOffset, endInclusive.charEnd), metadata, layer, )); metadataStart = null; metadataEndInclusive = null; } @override void visitExport(DirectParserASTContentExportEnd node, Token startInclusive, Token endInclusive) { handleData( FuzzSorterState.importExportSortable, startInclusive, endInclusive); } @override void visitImport(DirectParserASTContentImportEnd node, Token startInclusive, Token endInclusive) { handleData( FuzzSorterState.importExportSortable, startInclusive, endInclusive); } @override void visitClass(DirectParserASTContentClassDeclarationEnd node, Token startInclusive, Token endInclusive) { // TODO(jensj): Possibly sort stuff inside of this too. handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitEnum(DirectParserASTContentEnumEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitExtension(DirectParserASTContentExtensionDeclarationEnd node, Token startInclusive, Token endInclusive) { // TODO(jensj): Possibly sort stuff inside of this too. handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitLibraryName(DirectParserASTContentLibraryNameEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.nonSortable, startInclusive, endInclusive); } @override void visitMetadata(DirectParserASTContentMetadataEnd node, Token startInclusive, Token endInclusive) { if (metadataStart == null) { metadataStart = startInclusive; metadataEndInclusive = endInclusive; } else { metadataEndInclusive = endInclusive; } } @override void visitMixin(DirectParserASTContentMixinDeclarationEnd node, Token startInclusive, Token endInclusive) { // TODO(jensj): Possibly sort stuff inside of this too. handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitNamedMixin(DirectParserASTContentNamedMixinApplicationEnd node, Token startInclusive, Token endInclusive) { // TODO(jensj): Possibly sort stuff inside of this too. handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitPart(DirectParserASTContentPartEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.nonSortable, startInclusive, endInclusive); } @override void visitPartOf(DirectParserASTContentPartOfEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.nonSortable, startInclusive, endInclusive); } @override void visitTopLevelFields(DirectParserASTContentTopLevelFieldsEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitTopLevelMethod(DirectParserASTContentTopLevelMethodEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } @override void visitTypedef(DirectParserASTContentFunctionTypeAliasEnd node, Token startInclusive, Token endInclusive) { handleData(FuzzSorterState.sortableRest, startInclusive, endInclusive); } } class _FakeFileSystem extends FileSystem { bool redirectAndRecord = true; final Map data = {}; final FileSystem fs; _FakeFileSystem(this.fs); @override FileSystemEntity entityForUri(Uri uri) { return new _FakeFileSystemEntity(this, uri); } } class _FakeFileSystemEntity extends FileSystemEntity { final _FakeFileSystem fs; final Uri uri; _FakeFileSystemEntity(this.fs, this.uri); Future _ensureCachedIfOk() async { if (fs.data.containsKey(uri)) return; if (!fs.redirectAndRecord) { throw "Asked for file in non-recording mode that wasn't known"; } FileSystemEntity f = fs.fs.entityForUri(uri); if (!await f.exists()) { fs.data[uri] = null; return; } fs.data[uri] = await f.readAsBytes(); } @override Future exists() async { await _ensureCachedIfOk(); Uint8List data = fs.data[uri]; if (data == null) return false; return true; } @override Future existsAsyncIfPossible() => exists(); @override Future> readAsBytes() async { await _ensureCachedIfOk(); Uint8List data = fs.data[uri]; if (data == null) throw new FileSystemException(uri, "File doesn't exist."); return data; } @override Future> readAsBytesAsyncIfPossible() => readAsBytes(); @override Future readAsString() async { await _ensureCachedIfOk(); Uint8List data = fs.data[uri]; if (data == null) throw new FileSystemException(uri, "File doesn't exist."); return utf8.decode(data); } } Target createTarget(FolderOptions folderOptions, FastaContext context) { TargetFlags targetFlags = new TargetFlags( forceLateLoweringsForTesting: folderOptions.forceLateLowerings, forceLateLoweringSentinelForTesting: folderOptions.forceLateLoweringSentinel, forceStaticFieldLoweringForTesting: folderOptions.forceStaticFieldLowering, forceNoExplicitGetterCallsForTesting: folderOptions.forceNoExplicitGetterCalls, enableNullSafety: context.soundNullSafety, ); Target target; switch (folderOptions.target) { case "vm": target = new TestVmTarget(targetFlags); break; case "aot": target = new TestVmAotTarget(targetFlags); break; case "none": target = new NoneTarget(targetFlags); break; case "dart2js": target = new TestDart2jsTarget('dart2js', targetFlags); break; case "dartdevc": target = new TestDevCompilerTarget(targetFlags); break; default: throw new ArgumentError( "Unsupported test target '${folderOptions.target}'."); } return target; } Set createUserLibrariesImportUriSet( Component component, UriTranslator uriTranslator) { Set knownUris = component.libraries.map((Library library) => library.importUri).toSet(); Set userLibraries = component.libraries .where((Library library) => library.importUri.scheme != 'dart' && library.importUri.scheme != 'package') .map((Library library) => library.importUri) .toSet(); // Mark custom "dart:" libraries defined in the test-specific libraries.json // file as user libraries. // Note that this method takes a uriTranslator directly because of // inconsistencies with targets (namely that test-specific libraries.json // specifies target 'none' even if the target is 'vm', which works because // the normal testing pipeline use target 'none' for the dill loader). userLibraries.addAll(uriTranslator.dartLibraries.allLibraries .map((LibraryInfo info) => info.importUri)); return userLibraries.intersection(knownUris); } class Outline extends Step { final bool fullCompile; const Outline(this.fullCompile, {this.updateComments: false}); final bool updateComments; String get name { return fullCompile ? "compile" : "outline"; } bool get isCompiler => fullCompile; Future> run( TestDescription description, FastaContext context) async { CompilationSetup compilationSetup = createCompilationSetup(description, context); if (compilationSetup.testOptions.linkDependencies.isNotEmpty && compilationSetup.testOptions.component == null) { // Compile linked dependency. ProcessedOptions linkOptions = compilationSetup.options; if (compilationSetup.testOptions.nnbdMode != null) { linkOptions = compilationSetup.createProcessedOptions( compilationSetup.createCompilerOptions( compilationSetup.testOptions.nnbdMode, compilationSetup.testOptions.allowedExperimentalFlags, compilationSetup.testOptions.experimentEnabledVersion, compilationSetup.testOptions.experimentReleasedVersion)); } await CompilerContext.runWithOptions(linkOptions, (_) async { KernelTarget sourceTarget = await outlineInitialization( context, description, linkOptions, compilationSetup.testOptions.linkDependencies.toList()); if (compilationSetup.testOptions.errors != null) { compilationSetup.errors.addAll(compilationSetup.testOptions.errors); } Component p = await sourceTarget.buildOutlines(); if (fullCompile) { p = await sourceTarget.buildComponent( verify: compilationSetup.folderOptions.noVerify ? false : context.verify); } // To avoid possible crash in mixin transformation in the transformation // of the user of this linked dependency we have to transform this too. // We do that now. Target backendTarget = sourceTarget.backendTarget; if (backendTarget is TestTarget) { backendTarget.performModularTransformations = true; } try { if (sourceTarget.loader.coreTypes != null) { sourceTarget.runBuildTransformations(); } } finally { if (backendTarget is TestTarget) { backendTarget.performModularTransformations = false; } } compilationSetup.testOptions.component = p; List keepLibraries = []; for (Library lib in p.libraries) { if (compilationSetup.testOptions.linkDependencies .contains(lib.importUri)) { keepLibraries.add(lib); } } p.libraries.clear(); p.libraries.addAll(keepLibraries); compilationSetup.testOptions.errors = compilationSetup.errors.toList(); compilationSetup.errors.clear(); }); } return await CompilerContext.runWithOptions(compilationSetup.options, (_) async { Component alsoAppend = compilationSetup.testOptions.component; if (description.uri.pathSegments.last.endsWith(".no_link.dart")) { alsoAppend = null; } KernelTarget sourceTarget = await outlineInitialization(context, description, compilationSetup.options, [description.uri], alsoAppend: alsoAppend); ValidatingInstrumentation instrumentation = new ValidatingInstrumentation(); await instrumentation.loadExpectations(description.uri); sourceTarget.loader.instrumentation = instrumentation; Component p = await sourceTarget.buildOutlines(); Set userLibraries = createUserLibrariesImportUriSet(p, sourceTarget.uriTranslator); if (fullCompile) { p = await sourceTarget.buildComponent( verify: compilationSetup.folderOptions.noVerify ? false : context.verify); instrumentation.finish(); if (instrumentation.hasProblems) { if (updateComments) { await instrumentation.fixSource(description.uri, false); } else { return new Result( new ComponentResult(description, p, userLibraries, compilationSetup, sourceTarget), context.expectationSet["InstrumentationMismatch"], instrumentation.problemsAsString, autoFixCommand: '${UPDATE_COMMENTS}=true', canBeFixWithUpdateExpectations: true); } } } return pass(new ComponentResult( description, p, userLibraries, compilationSetup, sourceTarget)); }); } Future outlineInitialization( FastaContext context, TestDescription description, ProcessedOptions options, List entryPoints, {Component alsoAppend}) async { Component platform = await context.loadPlatform(options.target, options.nnbdMode); Ticker ticker = new Ticker(); UriTranslator uriTranslator = await context.computeUriTranslator(description); DillTarget dillTarget = new DillTarget( ticker, uriTranslator, options.target, ); dillTarget.loader.appendLibraries(platform); if (alsoAppend != null) { dillTarget.loader.appendLibraries(alsoAppend); } KernelTarget sourceTarget = new KernelTarget( StandardFileSystem.instance, false, dillTarget, uriTranslator); sourceTarget.setEntryPoints(entryPoints); await dillTarget.buildOutlines(); return sourceTarget; } } class Transform extends Step { const Transform(); String get name => "transform component"; Future> run( ComponentResult result, FastaContext context) async { return await CompilerContext.runWithOptions(result.options, (_) async { Component component = result.component; KernelTarget sourceTarget = result.sourceTarget; Target backendTarget = sourceTarget.backendTarget; if (backendTarget is TestTarget) { backendTarget.performModularTransformations = true; } try { if (sourceTarget.loader.coreTypes != null) { sourceTarget.runBuildTransformations(); } } finally { if (backendTarget is TestTarget) { backendTarget.performModularTransformations = false; } } List errors = VerifyTransformed.verify(component, backendTarget); if (errors.isNotEmpty) { return new Result( result, context.expectationSet["TransformVerificationError"], errors.join('\n')); } if (backendTarget is TestTarget && backendTarget.hasGlobalTransformation) { component = backendTarget.performGlobalTransformations(sourceTarget, component); // Clear the currently cached platform since the global transformation // might have modified it. context.clearPlatformCache( backendTarget, result.compilationSetup.options.nnbdMode); } return pass(new ComponentResult(result.description, component, result.userLibraries, result.compilationSetup, sourceTarget)); }); } } class Verify extends Step { final bool fullCompile; const Verify(this.fullCompile); String get name => "verify"; Future> run( ComponentResult result, FastaContext context) async { FolderOptions folderOptions = context.computeFolderOptions(result.description); if (folderOptions.noVerify) { return pass(result); } Component component = result.component; StringBuffer messages = new StringBuffer(); ProcessedOptions options = new ProcessedOptions( options: new CompilerOptions() ..onDiagnostic = (DiagnosticMessage message) { if (messages.isNotEmpty) { messages.write("\n"); } messages.writeAll(message.plainTextFormatted, "\n"); }); return await CompilerContext.runWithOptions(options, (compilerContext) async { compilerContext.uriToSource.addAll(component.uriToSource); List verificationErrors = verifyComponent(component, isOutline: !fullCompile, skipPlatform: true); assert(verificationErrors.isEmpty || messages.isNotEmpty); if (messages.isEmpty) { return pass(result); } else { return new Result( null, context.expectationSet["VerificationError"], "$messages"); } }, errorOnMissingInput: false); } } /// Visitor that checks that the component has been transformed properly. // TODO(johnniwinther): Add checks for all nodes that are unsupported after // transformation. class VerifyTransformed extends Visitor with VisitorVoidMixin { final Target target; List errors = []; VerifyTransformed(this.target); @override void defaultNode(Node node) { node.visitChildren(this); } @override void visitAwaitExpression(AwaitExpression node) { if (target is VmTarget) { errors.add("ERROR: Untransformed await expression: $node"); } } static List verify(Component component, Target target) { VerifyTransformed visitor = new VerifyTransformed(target); component.accept(visitor); return visitor.errors; } } mixin TestTarget on Target { bool performModularTransformations = false; @override void performModularTransformationsOnLibraries( Component component, CoreTypes coreTypes, ClassHierarchy hierarchy, List libraries, Map environmentDefines, DiagnosticReporter diagnosticReporter, ReferenceFromIndex referenceFromIndex, {void logger(String msg), ChangedStructureNotifier changedStructureNotifier}) { if (performModularTransformations) { super.performModularTransformationsOnLibraries( component, coreTypes, hierarchy, libraries, environmentDefines, diagnosticReporter, referenceFromIndex, logger: logger); } } bool get hasGlobalTransformation => false; Component performGlobalTransformations( KernelTarget kernelTarget, Component component) => component; } class TestVmTarget extends VmTarget with TestTarget { TestVmTarget(TargetFlags flags) : super(flags); } class TestVmAotTarget extends TestVmTarget { TestVmAotTarget(TargetFlags flags) : super(flags); @override bool get hasGlobalTransformation => true; @override Component performGlobalTransformations( KernelTarget kernelTarget, Component component) { return type_flow.transformComponent( this, kernelTarget.loader.coreTypes, component, matcher: new type_flow.ConstantPragmaAnnotationParser( kernelTarget.loader.coreTypes)); } } class EnsureNoErrors extends Step { const EnsureNoErrors(); String get name => "check errors"; Future> run( ComponentResult result, FastaContext context) async { List> errors = result.compilationSetup.errors; return errors.isEmpty ? pass(result) : fail( result, "Unexpected errors:\n" "${errors.map((error) => error.join('\n')).join('\n\n')}"); } } class MatchHierarchy extends Step { const MatchHierarchy(); String get name => "check hierarchy"; Future> run( ComponentResult result, FastaContext context) async { Component component = result.component; Uri uri = component.uriToSource.keys.firstWhere((uri) => uri?.scheme == "file"); KernelTarget target = result.sourceTarget; ClassHierarchyBuilder hierarchy = target.loader.builderHierarchy; StringBuffer sb = new StringBuffer(); for (ClassHierarchyNode node in hierarchy.nodes.values) { sb.writeln(node); } return context.match( ".hierarchy.expect", "$sb", uri, result); } } class UriConfiguration { final Uri librariesSpecificationUri; final Uri packageConfigUri; UriConfiguration(this.librariesSpecificationUri, this.packageConfigUri); @override int get hashCode => librariesSpecificationUri.hashCode * 13 + packageConfigUri.hashCode * 17; @override bool operator ==(Object other) { if (identical(this, other)) return true; return other is UriConfiguration && librariesSpecificationUri == other.librariesSpecificationUri && packageConfigUri == other.packageConfigUri; } } class NoneConstantsBackendWithJs extends NoneConstantsBackend { const NoneConstantsBackendWithJs({bool supportsUnevaluatedConstants}) : super(supportsUnevaluatedConstants: supportsUnevaluatedConstants); @override NumberSemantics get numberSemantics => NumberSemantics.js; } class TestDart2jsTarget extends Dart2jsTarget with TestTarget { TestDart2jsTarget(String name, TargetFlags flags) : super(name, flags); } class TestDevCompilerTarget extends DevCompilerTarget with TestTarget { TestDevCompilerTarget(TargetFlags flags) : super(flags); }