blob: eaf1ac2c5d538d7251ce2c8fb8e4e01cf6f353e1 [file] [log] [blame]
// 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');
}
}