blob: 62bb63e550df53c09480056b42596b3619e03c47 [file] [log] [blame]
// Copyright (c) 2016, 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.md file.
library testing.multitest;
import 'dart:async' show Stream, StreamTransformerBase;
import 'dart:io' show Directory, File;
import 'log.dart' show splitLines;
import 'test_description.dart' show FileBasedTestDescription, TestDescription;
bool isError(Set<String> expectations) {
if (expectations.contains("compile-time error")) return true;
if (expectations.contains("runtime error")) return true;
return false;
}
bool isCheckedModeError(Set<String> expectations) {
if (expectations.contains("checked mode compile-time error")) return true;
if (expectations.contains("dynamic type error")) return true;
return isError(expectations);
}
class MultitestTransformer
extends StreamTransformerBase<TestDescription, TestDescription> {
static RegExp multitestMarker = new RegExp(r"//[#/]");
static int _multitestMarkerLength = 3;
static const List<String> validOutcomesList = const <String>[
"ok",
"syntax error",
"compile-time error",
"runtime error",
"static type warning",
"dynamic type error",
"checked mode compile-time error",
];
static final Set<String> validOutcomes =
new Set<String>.from(validOutcomesList);
Stream<TestDescription> bind(Stream<TestDescription> stream) async* {
List<String> errors = <String>[];
reportError(String error) {
errors.add(error);
print(error);
}
nextTest:
await for (TestDescription test in stream) {
FileBasedTestDescription? multitest;
String? contents;
if (test is FileBasedTestDescription) {
contents = await test.file.readAsString();
if (contents.contains(multitestMarker)) {
multitest = test;
}
}
if (multitest == null) {
yield test;
continue nextTest;
}
// Note: this is modified in the loop below.
List<String> linesWithoutAnnotations = <String>[];
Map<String, List<String>> testsAsLines = <String, List<String>>{
"none": linesWithoutAnnotations,
};
Map<String, Set<String>> outcomes = <String, Set<String>>{
"none": new Set<String>(),
};
int lineNumber = 0;
for (String line in splitLines(contents!)) {
lineNumber++;
int index = line.indexOf(multitestMarker);
String? subtestName;
List<String>? subtestOutcomesList;
if (index != -1) {
String annotationText =
line.substring(index + _multitestMarkerLength).trim();
index = annotationText.indexOf(":");
if (index != -1) {
subtestName = annotationText.substring(0, index).trim();
subtestOutcomesList = annotationText
.substring(index + 1)
.split(",")
.map((s) => s.trim())
.toList();
if (subtestOutcomesList.isEmpty) {
reportError(multitest
.formatError("$lineNumber: Expected <testname>:<outcomes>"));
continue nextTest;
}
}
}
if (subtestName != null) {
List<String> lines = testsAsLines.putIfAbsent(subtestName,
() => new List<String>.from(linesWithoutAnnotations));
lines.add(line);
Set<String> subtestOutcomes =
outcomes.putIfAbsent(subtestName, () => new Set<String>());
if (subtestOutcomesList!.length != 1 ||
subtestOutcomesList.single != "continued") {
for (String outcome in subtestOutcomesList) {
if (validOutcomes.contains(outcome)) {
subtestOutcomes.add(outcome);
} else {
reportError(multitest.formatError(
"$lineNumber: '$outcome' isn't a recognized outcome."));
continue nextTest;
}
}
}
} else {
for (List<String> lines in testsAsLines.values) {
// This will also modify [linesWithoutAnnotations].
lines.add(line);
}
}
}
Uri root = Uri.base.resolve("generated/");
Directory generated =
new Directory.fromUri(root.resolve(multitest.shortName));
generated = await generated.create(recursive: true);
for (MapEntry<String, List<String>> entry in testsAsLines.entries) {
String name = entry.key;
List<String> lines = entry.value;
Uri uri = generated.uri.resolve("${name}_generated.dart");
FileBasedTestDescription subtest =
new FileBasedTestDescription(root, new File.fromUri(uri));
subtest.multitestExpectations = outcomes[name];
await subtest.file.writeAsString(lines.join(""));
yield subtest;
}
}
if (errors.isNotEmpty) {
throw "Error: ${errors.join("\n")}";
}
}
}