blob: 9fe6f562eca87f94159285262b0b346598ca7155 [file] [log] [blame]
// 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:io' show File, IOSink;
import 'package:front_end/src/base/processed_options.dart'
show ProcessedOptions;
import 'package:front_end/src/fasta/builder/builder.dart' show Declaration;
import 'package:front_end/src/fasta/compiler_context.dart' show CompilerContext;
import 'package:front_end/src/fasta/messages.dart'
show LocatedMessage, templateUnspecified;
import 'package:front_end/src/fasta/parser.dart' show Parser;
import 'package:front_end/src/fasta/scanner.dart' show Token;
import 'package:front_end/src/fasta/severity.dart' show Severity;
import 'package:front_end/src/fasta/source/type_promotion_look_ahead_listener.dart'
show
TypePromotionLookAheadListener,
TypePromotionState,
UnspecifiedDeclaration;
import 'package:front_end/src/fasta/testing/kernel_chain.dart'
show openWrite, runDiff;
import 'package:front_end/src/fasta/testing/scanner_chain.dart'
show Read, Scan, ScannedFile;
import 'package:kernel/ast.dart' show Source;
import 'package:testing/testing.dart';
const String EXPECTATIONS = '''
[
{
"name": "ExpectationFileMismatch",
"group": "Fail"
},
{
"name": "ExpectationFileMissing",
"group": "Fail"
}
]
''';
Future<ChainContext> createContext(
Chain suite, Map<String, String> environment) async {
CompilerContext context =
await CompilerContext.runWithOptions<CompilerContext>(
new ProcessedOptions(),
(CompilerContext context) =>
new Future<CompilerContext>.value(context),
errorOnMissingInput: false);
context.disableColors();
return new TypePromotionLookAheadContext(
context, environment["updateExpectations"] == "true");
}
class TypePromotionLookAheadContext extends ChainContext {
final CompilerContext context;
final List<Step> steps = const <Step>[
const Read(),
const Scan(),
const TypePromotionLookAheadStep(),
const CheckTypePromotionResult(),
];
final bool updateExpectations;
final ExpectationSet expectationSet =
new ExpectationSet.fromJsonList(jsonDecode(EXPECTATIONS));
TypePromotionLookAheadContext(this.context, this.updateExpectations);
Expectation get expectationFileMismatch =>
expectationSet["ExpectationFileMismatch"];
Expectation get expectationFileMissing =>
expectationSet["ExpectationFileMissing"];
}
class TypePromotionLookAheadStep extends Step<ScannedFile, TypePromotionResult,
TypePromotionLookAheadContext> {
const TypePromotionLookAheadStep();
String get name => "Type Promotion Look Ahead";
Future<Result<TypePromotionResult>> run(
ScannedFile file, TypePromotionLookAheadContext context) async {
return context.context
.runInContext<Result<TypePromotionResult>>((CompilerContext c) async {
Uri uri = file.file.uri;
c.uriToSource[uri] = new Source(file.result.lineStarts, file.file.bytes);
StringBuffer buffer = new StringBuffer();
Parser parser = new Parser(new TestListener(uri, buffer));
try {
parser.parseUnit(file.result.tokens);
} finally {
c.uriToSource.remove(uri);
}
return pass(new TypePromotionResult(uri, "$buffer"));
});
}
}
class TestState extends TypePromotionState {
final StringBuffer buffer;
TestState(Uri uri, this.buffer) : super(uri);
void note(String message, Token token) {
buffer.writeln(CompilerContext.current.format(
debugMessage(message, uri, token.charOffset, token.lexeme.length),
Severity.context));
}
@override
void checkEmpty(Token token) {
if (stack.isNotEmpty) {
throw CompilerContext.current.format(
debugMessage("Stack not empty", uri, token?.charOffset ?? -1,
token?.length ?? 1),
Severity.internalProblem);
}
}
@override
void declareIdentifier(Token token) {
super.declareIdentifier(token);
trace("Declared ${token.lexeme}", token);
}
@override
Declaration nullValue(String name, Token token) {
return new DebugDeclaration(name, uri, token?.charOffset ?? -1);
}
@override
void registerWrite(UnspecifiedDeclaration declaration, Token token) {
note("Write to ${declaration.name}@${declaration.charOffset}", token);
}
@override
void registerPromotionCandidate(
UnspecifiedDeclaration declaration, Token token) {
note("Possible promotion of ${declaration.name}@${declaration.charOffset}",
token);
}
@override
void report(LocatedMessage message, Severity severity,
{List<LocatedMessage> context}) {
CompilerContext.current.report(message, severity, context: context);
}
@override
void trace(String message, Token token) {
report(
debugMessage(message, uri, token?.charOffset ?? -1, token?.length ?? 1),
Severity.warning);
for (Object o in stack) {
String s = " $o";
int index = s.indexOf("\n");
if (index != -1) {
s = s.substring(0, index) + "...";
}
print(s);
}
print('------------------\n');
}
}
LocatedMessage debugMessage(String text, Uri uri, int offset, int length) {
return templateUnspecified
.withArguments(text)
.withLocation(uri, offset, length);
}
class TestListener extends TypePromotionLookAheadListener {
TestListener(Uri uri, StringBuffer buffer)
: super(new TestState(uri, buffer));
@override
void debugEvent(String name, Token token) {
state.trace(name, token);
}
}
class DebugDeclaration extends Declaration {
final String name;
@override
final Uri fileUri;
@override
int charOffset;
DebugDeclaration(this.name, this.fileUri, this.charOffset);
Declaration get parent => null;
String get fullNameForErrors => name;
String toString() => "<<$name@$charOffset>>";
}
class TypePromotionResult {
final Uri uri;
final String trace;
const TypePromotionResult(this.uri, this.trace);
}
class CheckTypePromotionResult
extends Step<TypePromotionResult, Null, TypePromotionLookAheadContext> {
const CheckTypePromotionResult();
String get name => "Check Type Promotion Result";
Future<Result<Null>> run(
TypePromotionResult result, TypePromotionLookAheadContext context) async {
Uri uri = result.uri;
String actual = result.trace.trim();
if (actual.isNotEmpty) {
actual += "\n";
}
File expectedFile = new File("${uri.toFilePath()}.type_promotion.expect");
if (await expectedFile.exists()) {
String expected = await expectedFile.readAsString();
if (expected != actual) {
if (context.updateExpectations) {
return updateExpectationFile(expectedFile.uri, actual);
}
String diff = await runDiff(expectedFile.uri, actual);
return new Result<Null>(null, context.expectationFileMismatch,
"$uri doesn't match ${expectedFile.uri}\n$diff", null);
}
return pass(null);
} else {
if (actual.isEmpty) return pass(null);
if (context.updateExpectations) {
return updateExpectationFile(expectedFile.uri, actual);
}
return new Result<Null>(
null,
context.expectationFileMissing,
"""
Please create file ${expectedFile.path} with this content:
$actual""",
null);
}
}
Future<Result<Null>> updateExpectationFile(Uri uri, String actual) async {
if (actual.isEmpty) {
await new File.fromUri(uri).delete();
} else {
await openWrite(uri, (IOSink sink) {
sink.write(actual);
});
}
return pass(null);
}
}
main([List<String> arguments = const []]) =>
runMe(arguments, createContext, "../../testing.json");