blob: a8724344e19ec8e1b21c7384967fdd28616e1ed4 [file] [log] [blame]
// Copyright (c) 2020, 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.
library fasta.test.textual_outline_test;
import 'dart:io';
import 'dart:typed_data';
import 'package:_fe_analyzer_shared/src/scanner/abstract_scanner.dart'
show ScannerConfiguration;
import 'package:_fe_analyzer_shared/src/scanner/token.dart';
import 'package:dart_style/dart_style.dart' show DartFormatter;
import 'package:front_end/src/api_prototype/experimental_flags.dart';
import 'package:front_end/src/util/textual_outline.dart';
import 'package:testing/testing.dart'
show
Chain,
ChainContext,
Expectation,
ExpectationSet,
Result,
Step,
TestDescription;
import '../utils/kernel_chain.dart' show MatchContext;
import 'suite_utils.dart';
import '../testing/environment_keys.dart';
import '../testing/folder_options.dart';
const int minSupportedMajorVersion = 2;
const int minSupportedMinorVersion = 12;
const List<Map<String, String>> EXPECTATIONS = [
{
"name": "ExpectationFileMismatch",
"group": "Fail",
},
{
"name": "ExpectationFileMissing",
"group": "Fail",
},
{
"name": "EmptyOutput",
"group": "Fail",
},
{
"name": "UnknownChunk",
"group": "Fail",
},
{
"name": "FormatterCrash",
"group": "Fail",
},
];
Future<Context> createContext(Chain suite, Map<String, String> environment) {
return new Future.value(new Context(suite.root, environment));
}
void main([List<String> arguments = const []]) => internalMain(
createContext,
arguments: arguments,
displayName: "textual outline suite",
);
class Context extends ChainContext with MatchContext {
final SuiteFolderOptions suiteFolderOptions;
final Map<ExperimentalFlag, bool> forcedExperimentalFlags;
@override
final bool updateExpectations;
@override
String get updateExpectationsOption =>
'${EnvironmentKeys.updateExpectations}=true';
@override
bool get canBeFixWithUpdateExpectations => true;
Context(Uri baseUri, Map<String, String> environment)
: suiteFolderOptions = new SuiteFolderOptions(baseUri),
updateExpectations =
environment[EnvironmentKeys.updateExpectations] == "true",
forcedExperimentalFlags =
SuiteFolderOptions.computeForcedExperimentalFlags(environment);
@override
final List<Step> steps = const <Step>[
const TextualOutline(),
];
@override
final ExpectationSet expectationSet =
new ExpectationSet.fromJsonList(EXPECTATIONS);
}
class TextualOutline extends Step<TestDescription, TestDescription, Context> {
const TextualOutline();
@override
String get name => "TextualOutline";
@override
Future<Result<TestDescription>> run(
TestDescription description, Context context) async {
FolderOptions folderOptions =
context.suiteFolderOptions.computeFolderOptions(description);
Map<ExperimentalFlag, bool> experimentalFlags = folderOptions
.computeExplicitExperimentalFlags(context.forcedExperimentalFlags);
Map<ExperimentalFlag, bool> experimentalFlagsExplicit =
folderOptions.computeExplicitExperimentalFlags(const {});
Uint8List bytes = new File.fromUri(description.uri).readAsBytesSync();
for (bool modelled in [false, true]) {
TextualOutlineInfoForTesting info = new TextualOutlineInfoForTesting();
String? result = textualOutline(
bytes,
new ScannerConfiguration(
enableExtensionMethods: isExperimentEnabled(
ExperimentalFlag.extensionMethods,
explicitExperimentalFlags: experimentalFlags),
enableNonNullable: isExperimentEnabled(ExperimentalFlag.nonNullable,
explicitExperimentalFlags: experimentalFlags),
enableTripleShift: isExperimentEnabled(ExperimentalFlag.tripleShift,
explicitExperimentalFlags: experimentalFlags),
),
throwOnUnexpected: true,
performModelling: modelled,
returnNullOnError: false,
enablePatterns: isExperimentEnabled(ExperimentalFlag.patterns,
explicitExperimentalFlags: experimentalFlags),
infoForTesting: info,
);
if (result == null) {
return new Result(
null, context.expectationSet["EmptyOutput"], description.uri);
}
bool containsUnknownChunk = info.hasUnknownChunk;
bool tryFormat = !containsUnknownChunk;
for (LanguageVersionToken version in info.languageVersionTokens) {
if (version.major < minSupportedMajorVersion) {
tryFormat = false;
} else if (version.major == minSupportedMajorVersion &&
version.minor < minSupportedMinorVersion) {
tryFormat = false;
}
}
dynamic formatterException;
StackTrace? formatterExceptionSt;
if (tryFormat) {
try {
List<String> experimentFlags = [];
for (MapEntry<ExperimentalFlag, bool> entry
in experimentalFlags.entries) {
if (entry.value) {
experimentFlags.add(entry.key.name);
}
}
// Default to the latest language version. If the test should be at
// an older language version, it will contain a `// @dart=x.y`
// comment, which takes precedence over this argument.
result = new DartFormatter(
languageVersion: DartFormatter.latestLanguageVersion,
experimentFlags: experimentFlags)
.format(result);
} catch (e, st) {
formatterException = e;
formatterExceptionSt = st;
}
}
String filename = ".textual_outline.expect";
if (modelled) {
filename = ".textual_outline_modelled.expect";
}
Result<TestDescription> expectMatch =
await context.match<TestDescription>(
filename, result!, description.uri, description);
if (expectMatch.outcome != Expectation.pass) return expectMatch;
if (containsUnknownChunk) {
return new Result(
null, context.expectationSet["UnknownChunk"], description.uri);
}
if (formatterException != null && !info.hasParserErrors) {
bool hasUnreleasedExperiment = false;
for (MapEntry<ExperimentalFlag, bool> entry
in experimentalFlagsExplicit.entries) {
if (entry.value) {
if (!entry.key.isEnabledByDefault) {
hasUnreleasedExperiment = true;
break;
}
}
}
if (!hasUnreleasedExperiment) {
return new Result(null, context.expectationSet["FormatterCrash"],
formatterException,
trace: formatterExceptionSt);
}
}
}
return new Result.pass(description);
}
}