| // Copyright (c) 2021, 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:io' show File; |
| |
| import 'package:_fe_analyzer_shared/src/messages/severity.dart' show Severity; |
| import 'package:front_end/src/api_prototype/compiler_options.dart'; |
| import 'package:front_end/src/api_prototype/incremental_kernel_generator.dart'; |
| import 'package:front_end/src/api_prototype/memory_file_system.dart'; |
| import 'package:front_end/src/util/outline_extractor.dart'; |
| import 'package:kernel/ast.dart'; |
| import 'package:kernel/src/equivalence.dart'; |
| import 'package:testing/testing.dart' |
| show Chain, ChainContext, ExpectationSet, Result, Step, TestDescription; |
| |
| import 'fasta/suite_utils.dart'; |
| import 'fasta/testing/environment_keys.dart'; |
| import 'incremental_suite.dart' as helper; |
| import 'testing_utils.dart' show checkEnvironment; |
| import 'utils/kernel_chain.dart' show MatchContext; |
| |
| const String EXPECTATIONS = ''' |
| [ |
| { |
| "name": "ExpectationFileMismatch", |
| "group": "Fail" |
| }, |
| { |
| "name": "ExpectationFileMissing", |
| "group": "Fail" |
| } |
| ] |
| '''; |
| |
| void main([List<String> arguments = const []]) => internalMain(createContext, |
| arguments: arguments, |
| displayName: "outline extractor suite", |
| configurationPath: "../testing.json"); |
| |
| Future<Context> createContext( |
| Chain suite, Map<String, String> environment) async { |
| const Set<String> knownEnvironmentKeys = { |
| EnvironmentKeys.updateExpectations, |
| }; |
| checkEnvironment(environment, knownEnvironmentKeys); |
| |
| bool updateExpectations = |
| environment[EnvironmentKeys.updateExpectations] == "true"; |
| |
| return new Context(suite.name, updateExpectations); |
| } |
| |
| class Context extends ChainContext with MatchContext { |
| @override |
| final bool updateExpectations; |
| |
| @override |
| String get updateExpectationsOption => |
| '${EnvironmentKeys.updateExpectations}=true'; |
| |
| @override |
| bool get canBeFixWithUpdateExpectations => true; |
| |
| final String suiteName; |
| |
| Context(this.suiteName, this.updateExpectations); |
| |
| @override |
| final List<Step> steps = const <Step>[ |
| const OutlineExtractorStep(), |
| const CompileAndCompareStep(), |
| ]; |
| |
| @override |
| final ExpectationSet expectationSet = |
| new ExpectationSet.fromJsonList(jsonDecode(EXPECTATIONS)); |
| } |
| |
| class OutlineExtractorStep |
| extends Step<TestDescription, TestDescription, Context> { |
| const OutlineExtractorStep(); |
| |
| @override |
| String get name => "OutlineExtractorStep"; |
| |
| @override |
| Future<Result<TestDescription>> run( |
| TestDescription description, Context context) async { |
| Uri? packages = description.uri.resolve(".dart_tool/package_config.json"); |
| if (!new File.fromUri(packages).existsSync()) { |
| packages = null; |
| } |
| Map<Uri, String> result = |
| await extractOutline([description.uri], packages: packages); |
| StringBuffer sb = new StringBuffer(); |
| Uri uri = description.uri; |
| Uri base = uri.resolve("."); |
| Uri dartBase = Uri.base; |
| |
| for (MapEntry<Uri, String> entry in result.entries) { |
| sb.writeln("${entry.key}:"); |
| sb.writeln(entry.value); |
| sb.writeln("\n\n"); |
| } |
| |
| String actual = sb.toString(); |
| actual = actual.replaceAll("$base", "org-dartlang-testcase:///"); |
| actual = actual.replaceAll("$dartBase", "org-dartlang-testcase-sdk:///"); |
| actual = actual.replaceAll("\\n", "\n"); |
| |
| return context.match<TestDescription>( |
| ".outline_extracted", |
| actual, |
| description.uri, |
| description, |
| ); |
| } |
| } |
| |
| class CompileAndCompareStep |
| extends Step<TestDescription, TestDescription, Context> { |
| const CompileAndCompareStep(); |
| |
| @override |
| String get name => "CompileAndCompare"; |
| |
| @override |
| Future<Result<TestDescription>> run( |
| TestDescription description, Context context) async { |
| Uri? packages = description.uri.resolve(".dart_tool/package_config.json"); |
| if (!new File.fromUri(packages).existsSync()) { |
| packages = null; |
| } |
| Map<Uri, String> processedFiles = |
| await extractOutline([description.uri], packages: packages); |
| |
| void onDiagnostic(DiagnosticMessage message) { |
| if (message.severity == Severity.error || |
| message.severity == Severity.warning) { |
| throw ("Unexpected error: ${message.plainTextFormatted.join('\n')}"); |
| } |
| } |
| |
| Library lib1; |
| { |
| CompilerOptions options = helper.getOptions(); |
| options.onDiagnostic = onDiagnostic; |
| options.packagesFileUri = packages; |
| helper.TestIncrementalCompiler compiler = |
| new helper.TestIncrementalCompiler(options, description.uri, |
| /* initializeFrom = */ null, /* outlineOnly = */ true); |
| IncrementalCompilerResult c = await compiler.computeDelta(); |
| lib1 = c.component.libraries |
| .firstWhere((element) => element.fileUri == description.uri); |
| } |
| Library lib2; |
| { |
| CompilerOptions options = helper.getOptions(); |
| options.onDiagnostic = onDiagnostic; |
| options.packagesFileUri = packages; |
| MemoryFileSystem mfs = new MemoryFileSystem(Uri.base); |
| if (packages != null) { |
| mfs.entityForUri(packages).writeAsBytesSync( |
| await options.fileSystem.entityForUri(packages).readAsBytes()); |
| } |
| if (options.sdkSummary != null) { |
| mfs.entityForUri(options.sdkSummary!).writeAsBytesSync(await options |
| .fileSystem |
| .entityForUri(options.sdkSummary!) |
| .readAsBytes()); |
| } |
| if (options.librariesSpecificationUri != null) { |
| mfs.entityForUri(options.librariesSpecificationUri!).writeAsBytesSync( |
| await options.fileSystem |
| .entityForUri(options.librariesSpecificationUri!) |
| .readAsBytes()); |
| } |
| for (MapEntry<Uri, String> entry in processedFiles.entries) { |
| mfs.entityForUri(entry.key).writeAsStringSync(entry.value); |
| } |
| options.fileSystem = mfs; |
| helper.TestIncrementalCompiler compiler = |
| new helper.TestIncrementalCompiler(options, description.uri, |
| /* initializeFrom = */ null, /* outlineOnly = */ true); |
| IncrementalCompilerResult c = await compiler.computeDelta(); |
| lib2 = c.component.libraries |
| .firstWhere((element) => element.fileUri == description.uri); |
| } |
| EquivalenceResult result = |
| checkEquivalence(lib1, lib2, strategy: const Strategy()); |
| |
| if (result.isEquivalent) { |
| return new Result<TestDescription>.pass(description); |
| } else { |
| print("Bad:"); |
| print(result); |
| return new Result<TestDescription>.fail( |
| description, /* error = */ result); |
| } |
| } |
| } |
| |
| class Strategy extends EquivalenceStrategy { |
| const Strategy(); |
| |
| @override |
| bool checkTreeNode_fileOffset( |
| EquivalenceVisitor visitor, TreeNode node, TreeNode other) { |
| return true; |
| } |
| |
| @override |
| bool checkAssertStatement_conditionStartOffset( |
| EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) { |
| return true; |
| } |
| |
| @override |
| bool checkAssertStatement_conditionEndOffset( |
| EquivalenceVisitor visitor, AssertStatement node, AssertStatement other) { |
| return true; |
| } |
| |
| @override |
| bool checkClass_startFileOffset( |
| EquivalenceVisitor visitor, Class node, Class other) { |
| return true; |
| } |
| |
| @override |
| bool checkClass_fileEndOffset( |
| EquivalenceVisitor visitor, Class node, Class other) { |
| return true; |
| } |
| |
| @override |
| bool checkProcedure_fileStartOffset( |
| EquivalenceVisitor visitor, Procedure node, Procedure other) { |
| return true; |
| } |
| |
| @override |
| bool checkConstructor_startFileOffset( |
| EquivalenceVisitor visitor, Constructor node, Constructor other) { |
| return true; |
| } |
| |
| @override |
| bool checkMember_fileEndOffset( |
| EquivalenceVisitor visitor, Member node, Member other) { |
| return true; |
| } |
| |
| @override |
| bool checkFunctionNode_fileEndOffset( |
| EquivalenceVisitor visitor, FunctionNode node, FunctionNode other) { |
| return true; |
| } |
| |
| @override |
| bool checkBlock_fileEndOffset( |
| EquivalenceVisitor visitor, Block node, Block other) { |
| return true; |
| } |
| |
| @override |
| bool checkLibrary_additionalExports( |
| EquivalenceVisitor visitor, Library node, Library other) { |
| return visitor.checkSets( |
| node.additionalExports.toSet(), |
| other.additionalExports.toSet(), |
| visitor.matchReferences, |
| visitor.checkReferences, |
| 'additionalExports'); |
| } |
| |
| @override |
| bool checkClass_procedures( |
| EquivalenceVisitor visitor, Class node, Class other) { |
| // Check procedures as a set instead of a list to allow for reordering. |
| List<Procedure> a = node.procedures.toList(); |
| int sorter(Procedure x, Procedure y) { |
| int result = x.name.text.compareTo(y.name.text); |
| if (result != 0) return result; |
| result = x.kind.index - y.kind.index; |
| if (result != 0) return result; |
| // other stuff? |
| return 0; |
| } |
| |
| a.sort(sorter); |
| List<Procedure> b = other.procedures.toList(); |
| b.sort(sorter); |
| // return visitor.checkSets(a.toSet(), b.toSet(), |
| // visitor.matchNamedNodes, visitor.checkNodes, 'procedures'); |
| |
| return visitor.checkLists(a, b, visitor.checkNodes, 'procedures'); |
| } |
| } |