blob: 44a4ec3e8f793cc11ff0379baa383b9302ab2f46 [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 CfeSeverity;
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 'utils/suite_utils.dart';
import '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) {
const Set<String> knownEnvironmentKeys = {EnvironmentKeys.updateExpectations};
checkEnvironment(environment, knownEnvironmentKeys);
bool updateExpectations =
environment[EnvironmentKeys.updateExpectations] == "true";
return new Future.value(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(CfeDiagnosticMessage message) {
if (message.severity == CfeSeverity.error ||
message.severity == CfeSeverity.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');
}
}