Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(284)

Unified Diff: pkg/testing/lib/src/chain.dart

Issue 2624373003: Move package:testing to SDK. (Closed)
Patch Set: Created 3 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « pkg/testing/lib/src/analyze.dart ('k') | pkg/testing/lib/src/discover.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: pkg/testing/lib/src/chain.dart
diff --git a/pkg/testing/lib/src/chain.dart b/pkg/testing/lib/src/chain.dart
new file mode 100644
index 0000000000000000000000000000000000000000..8925b9dc9f482ba0d3418602bb26c4b8713ba89b
--- /dev/null
+++ b/pkg/testing/lib/src/chain.dart
@@ -0,0 +1,330 @@
+// 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.chain;
+
+import 'dart:async' show
+ Future,
+ Stream;
+
+import 'dart:convert' show
+ JSON,
+ JsonEncoder;
+
+import 'dart:io' show
+ Directory,
+ File,
+ FileSystemEntity,
+ exitCode;
+
+import 'suite.dart' show
+ Suite;
+
+import '../testing.dart' show
+ TestDescription;
+
+import 'test_dart/status_file_parser.dart' show
+ Expectation,
+ ReadTestExpectations,
+ TestExpectations;
+
+import 'zone_helper.dart' show
+ runGuarded;
+
+import 'error_handling.dart' show
+ withErrorHandling;
+
+import 'log.dart' show
+ logMessage,
+ logStepComplete,
+ logStepStart,
+ logSuiteComplete,
+ logTestComplete,
+ logUnexpectedResult,
+ splitLines;
+
+import 'multitest.dart' show
+ MultitestTransformer;
+
+typedef Future<ChainContext> CreateContext(
+ Chain suite, Map<String, String> environment);
+
+/// A test suite for tool chains, for example, a compiler.
+class Chain extends Suite {
+ final Uri source;
+
+ final Uri uri;
+
+ final List<RegExp> pattern;
+
+ final List<RegExp> exclude;
+
+ final bool processMultitests;
+
+ Chain(String name, String kind, this.source, this.uri, Uri statusFile,
+ this.pattern, this.exclude, this.processMultitests)
+ : super(name, kind, statusFile);
+
+ factory Chain.fromJsonMap(
+ Uri base, Map json, String name, String kind) {
+ Uri source = base.resolve(json["source"]);
+ Uri uri = base.resolve(json["path"]);
+ Uri statusFile = base.resolve(json["status"]);
+ List<RegExp> pattern = new List<RegExp>.from(
+ json["pattern"].map((String p) => new RegExp(p)));
+ List<RegExp> exclude = new List<RegExp>.from(
+ json["exclude"].map((String p) => new RegExp(p)));
+ bool processMultitests = json["process-multitests"] ?? false;
+ return new Chain(
+ name, kind, source, uri, statusFile, pattern, exclude, processMultitests);
+ }
+
+ void writeImportOn(StringSink sink) {
+ sink.write("import '");
+ sink.write(source);
+ sink.write("' as ");
+ sink.write(name);
+ sink.writeln(";");
+ }
+
+ void writeClosureOn(StringSink sink) {
+ sink.write("await runChain(");
+ sink.write(name);
+ sink.writeln(".createContext, environment, selectors, r'''");
+ const String jsonExtraIndent = " ";
+ sink.write(jsonExtraIndent);
+ sink.writeAll(splitLines(new JsonEncoder.withIndent(" ").convert(this)),
+ jsonExtraIndent);
+ sink.writeln("''');");
+ }
+
+ Map toJson() {
+ return {
+ "name": name,
+ "kind": kind,
+ "source": "$source",
+ "path": "$uri",
+ "status": "$statusFile",
+ "process-multitests": processMultitests,
+ "pattern": []..addAll(pattern.map((RegExp r) => r.pattern)),
+ "exclude": []..addAll(exclude.map((RegExp r) => r.pattern)),
+ };
+ }
+}
+
+abstract class ChainContext {
+ const ChainContext();
+
+ List<Step> get steps;
+
+ Future<Null> run(Chain suite, Set<String> selectors) async {
+ TestExpectations expectations = await ReadTestExpectations(
+ <String>[suite.statusFile.toFilePath()], {});
+ Stream<TestDescription> stream = list(suite);
+ if (suite.processMultitests) {
+ stream = stream.transform(new MultitestTransformer());
+ }
+ List<TestDescription> descriptions = await stream.toList();
+ descriptions.sort();
+ Map<TestDescription, Result> unexpectedResults =
+ <TestDescription, Result>{};
+ Map<TestDescription, Set<Expectation>> unexpectedOutcomes =
+ <TestDescription, Set<Expectation>>{};
+ int completed = 0;
+ List<Future> futures = <Future>[];
+ for (TestDescription description in descriptions) {
+ String selector = "${suite.name}/${description.shortName}";
+ if (selectors.isNotEmpty &&
+ !selectors.contains(selector) &&
+ !selectors.contains(suite.name)) {
+ continue;
+ }
+ Set<Expectation> expectedOutcomes =
+ expectations.expectations(description.shortName);
+ Result result;
+ StringBuffer sb = new StringBuffer();
+ // Records the outcome of the last step that was run.
+ Step lastStep = null;
+ Iterator<Step> iterator = steps.iterator;
+
+ /// Performs one step of [iterator].
+ ///
+ /// If `step.isAsync` is true, the corresponding step is said to be
+ /// asynchronous.
+ ///
+ /// If a step is asynchrouns the future returned from this function will
+ /// complete after the the first asynchronous step is scheduled. This
+ /// allows us to start processing the next test while an external process
+ /// completes as steps can be interleaved. To ensure all steps are
+ /// completed, wait for [futures].
+ ///
+ /// Otherwise, the future returned will complete when all steps are
+ /// completed. This ensures that tests are run in sequence without
+ /// interleaving steps.
+ Future doStep(dynamic input) async {
+ Future future;
+ bool isAsync = false;
+ if (iterator.moveNext()) {
+ Step step = iterator.current;
+ lastStep = step;
+ isAsync = step.isAsync;
+ logStepStart(completed, unexpectedResults.length, descriptions.length,
+ suite, description, step);
+ future = runGuarded(() async {
+ try {
+ return await step.run(input, this);
+ } catch (error, trace) {
+ return step.unhandledError(error, trace);
+ }
+ }, printLineOnStdout: sb.writeln);
+ } else {
+ future = new Future.value(null);
+ }
+ future = future.then((Result currentResult) {
+ if (currentResult != null) {
+ logStepComplete(completed, unexpectedResults.length,
+ descriptions.length, suite, description, lastStep);
+ result = currentResult;
+ if (currentResult.outcome == Expectation.PASS) {
+ // The input to the next step is the output of this step.
+ return doStep(result.output);
+ }
+ }
+ if (steps.isNotEmpty && steps.last == lastStep &&
+ description.shortName.endsWith("negative_test")) {
+ if (result.outcome == Expectation.PASS) {
+ result.addLog("Negative test didn't report an error.\n");
+ } else if (result.outcome == Expectation.FAIL) {
+ result.addLog("Negative test reported an error as expeceted.\n");
+ }
+ result = result.toNegativeTestResult();
+ }
+ if (!expectedOutcomes.contains(result.outcome)) {
+ result.addLog("$sb");
+ unexpectedResults[description] = result;
+ unexpectedOutcomes[description] = expectedOutcomes;
+ logUnexpectedResult(suite, description, result, expectedOutcomes);
+ } else {
+ logMessage(sb);
+ }
+ logTestComplete(++completed, unexpectedResults.length,
+ descriptions.length, suite, description);
+ });
+ if (isAsync) {
+ futures.add(future);
+ return null;
+ } else {
+ return future;
+ }
+ }
+ // The input of the first step is [description].
+ await doStep(description);
+ }
+ await Future.wait(futures);
+ logSuiteComplete();
+ if (unexpectedResults.isNotEmpty) {
+ unexpectedResults.forEach((TestDescription description, Result result) {
+ exitCode = 1;
+ logUnexpectedResult(suite, description, result,
+ unexpectedOutcomes[description]);
+ });
+ print("${unexpectedResults.length} failed:");
+ unexpectedResults.forEach((TestDescription description, Result result) {
+ print("${suite.name}/${description.shortName}: ${result.outcome}");
+ });
+ }
+ }
+
+ Stream<TestDescription> list(Chain suite) async* {
+ Directory testRoot = new Directory.fromUri(suite.uri);
+ if (await testRoot.exists()) {
+ Stream<FileSystemEntity> files =
+ testRoot.list(recursive: true, followLinks: false);
+ await for (FileSystemEntity entity in files) {
+ if (entity is! File) continue;
+ String path = entity.uri.path;
+ if (suite.exclude.any((RegExp r) => path.contains(r))) continue;
+ if (suite.pattern.any((RegExp r) => path.contains(r))) {
+ yield new TestDescription(suite.uri, entity);
+ }
+ }
+ } else {
+ throw "${suite.uri} isn't a directory";
+ }
+ }
+}
+
+abstract class Step<I, O, C extends ChainContext> {
+ const Step();
+
+ String get name;
+
+ bool get isAsync => false;
+
+ Future<Result<O>> run(I input, C context);
+
+ Result<O> unhandledError(error, StackTrace trace) {
+ return new Result<O>.crash(error, trace);
+ }
+
+ Result<O> pass(O output) => new Result<O>.pass(output);
+
+ Result<O> crash(error, StackTrace trace) => new Result<O>.crash(error, trace);
+
+ Result<O> fail(O output, [error, StackTrace trace]) {
+ return new Result<O>.fail(output, error, trace);
+ }
+}
+
+class Result<O> {
+ final O output;
+
+ final Expectation outcome;
+
+ final error;
+
+ final StackTrace trace;
+
+ final List<String> logs = <String>[];
+
+ Result(this.output, this.outcome, this.error, this.trace);
+
+ Result.pass(O output)
+ : this(output, Expectation.PASS, null, null);
+
+ Result.crash(error, StackTrace trace)
+ : this(null, Expectation.CRASH, error, trace);
+
+ Result.fail(O output, [error, StackTrace trace])
+ : this(output, Expectation.FAIL, error, trace);
+
+ String get log => logs.join();
+
+ void addLog(String log) {
+ logs.add(log);
+ }
+
+ Result<O> toNegativeTestResult() {
+ Expectation outcome = this.outcome;
+ if (outcome == Expectation.PASS) {
+ outcome = Expectation.FAIL;
+ } else if (outcome == Expectation.FAIL) {
+ outcome = Expectation.PASS;
+ }
+ return new Result<O>(output, outcome, error, trace)
+ ..logs.addAll(logs);
+ }
+}
+
+/// This is called from generated code.
+Future<Null> runChain(
+ CreateContext f, Map<String, String> environment, Set<String> selectors,
+ String json) {
+ return withErrorHandling(() async {
+ Chain suite = new Suite.fromJsonMap(Uri.base, JSON.decode(json));
+ print("Running ${suite.name}");
+ ChainContext context = await f(suite, environment);
+ return context.run(suite, selectors);
+ });
+}
« no previous file with comments | « pkg/testing/lib/src/analyze.dart ('k') | pkg/testing/lib/src/discover.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698