From 017aadabc9b06beaa58b227a8e0d2e0dfb52a305 Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum <nweiz@google.com> Date: Wed, 25 Mar 2015 15:41:02 -0700 Subject: [PATCH] Respect top-level @TestOn declarations. Closes #6 R=kevmoo@google.com Review URL: https://codereview.chromium.org//1027193004 --- README.md | 71 +++++++++++ bin/unittest.dart | 4 +- lib/src/backend/metadata.dart | 19 ++- lib/src/backend/platform_selector.dart | 48 +++++-- lib/src/backend/suite.dart | 19 ++- lib/src/frontend/test_on.dart | 6 +- lib/src/runner/load_exception.dart | 11 +- lib/src/runner/loader.dart | 27 +++- lib/src/runner/parse_metadata.dart | 2 +- lib/src/util/io.dart | 10 ++ lib/unittest.dart | 1 + .../platform_selector/evaluate_test.dart | 2 + test/runner/browser/chrome_test.dart | 2 + .../runner/browser/compact_reporter_test.dart | 2 + test/runner/browser/compiler_pool_test.dart | 2 + test/runner/browser/loader_test.dart | 2 + test/runner/browser/runner_test.dart | 2 + test/runner/compact_reporter_test.dart | 2 + test/runner/isolate_listener_test.dart | 2 + test/runner/loader_test.dart | 15 ++- test/runner/parse_metadata_test.dart | 20 +-- test/runner/runner_test.dart | 30 +++++ test/runner/test_on_test.dart | 118 ++++++++++++++++++ 23 files changed, 376 insertions(+), 41 deletions(-) create mode 100644 test/runner/test_on_test.dart diff --git a/README.md b/README.md index 4dc8c02e..19b3482d 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,77 @@ the results will be reported on the command line just like for VM tests. In fact, you can even run tests on both platforms with a single command: `pub run unittest:unittest -p chrome -p vm path/to/test.dart`. +### Restricting Tests to Certain Platforms + +Some test files only make sense to run on particular platforms. They may use +`dart:html` or `dart:io`, they might test Windows' particular filesystem +behavior, or they might use a feature that's only available in Chrome. The +[`@TestOn`][TestOn] annotation makes it easy to declare exactly which platforms +a test file should run on. Just put it at the top of your file, before any +`library` or `import` declarations: + +```dart +@TestOn("vm") + +import "dart:io"; + +import "package:unittest/unittest.dart"; + +void main() { + // ... +} +``` + +[TestOn]: http://www.dartdocs.org/documentation/unittest/latest/index.html#unittest/unittest.TestOn + +The string you pass to `@TestOn` is what's called a "platform selector", and it +specifies exactly which platforms a test can run on. It can be as simple as the +name of a platform, or a more complex Dart-like boolean expression involving +these platform names. + +### Platform Selector Syntax + +Platform selectors can contain identifiers, parentheses, and operators. When +loading a test, each identifier is set to `true` or `false` based on the current +platform, and the test is only loaded if the platform selector returns `true`. +The operators `||`, `&&`, `!`, and `? :` all work just like they do in Dart. The +valid identifiers are: + +* `vm`: Whether the test is running on the command-line Dart VM. + +* `chrome`: Whether the test is running on Google Chrome. + +* `dart-vm`: Whether the test is running on the Dart VM in any context. For now + this is identical to `vm`, but it will also be true for Dartium in the future. + It's identical to `!js`. + +* `browser`: Whether the test is running in any browser. + +* `js`: Whether the test has been compiled to JS. This is identical to + `!dart-vm`. + +* `blink`: Whether the test is running in a browser that uses the Blink + rendering engine. + +* `windows`: Whether the test is running on Windows. If `vm` is false, this will + be `false` as well. + +* `mac-os`: Whether the test is running on Mac OS. If `vm` is false, this will + be `false` as well. + +* `linux`: Whether the test is running on Linux. If `vm` is false, this will be + `false` as well. + +* `android`: Whether the test is running on Android. If `vm` is false, this will + be `false` as well, which means that this *won't* be true if the test is + running on an Android browser. + +* `posix`: Whether the test is running on a POSIX operating system. This is + equivalent to `!windows`. + +For example, if you wanted to run a test on every browser but Chrome, you would +write `@TestOn("browser && !chrome")`. + ## Asynchronous Tests Tests written with `async`/`await` will work automatically. The test runner diff --git a/bin/unittest.dart b/bin/unittest.dart index b86cf2f1..0189c8f1 100644 --- a/bin/unittest.dart +++ b/bin/unittest.dart @@ -78,12 +78,12 @@ void main(List<String> args) { }).whenComplete(() => reporter.close()); }).catchError((error, stackTrace) { if (error is LoadException) { - // TODO(nweiz): color this message? - stderr.writeln(getErrorMessage(error)); + stderr.writeln(error.toString(color: color)); // Only print stack traces for load errors that come from the user's if (error.innerError is! IOException && error.innerError is! IsolateSpawnException && + error.innerError is! FormatException && error.innerError is! String) { stderr.write(terseChain(stackTrace)); } diff --git a/lib/src/backend/metadata.dart b/lib/src/backend/metadata.dart index 738a7cf5..d567a27c 100644 --- a/lib/src/backend/metadata.dart +++ b/lib/src/backend/metadata.dart @@ -4,13 +4,26 @@ library unittest.backend.metadata; +import 'platform_selector.dart'; + /// Metadata for a test or test suite. /// /// This metadata comes from declarations on the test itself; it doesn't include /// configuration from the user. class Metadata { - /// The expressions indicating which platforms the suite supports. - final String testOn; + /// The selector indicating which platforms the suite supports. + final PlatformSelector testOn; + + /// Creates new Metadata. + /// + /// [testOn] defaults to [PlatformSelector.all]. + Metadata({PlatformSelector testOn}) + : testOn = testOn == null ? PlatformSelector.all : testOn; - Metadata(this.testOn); + /// Parses metadata fields from strings. + /// + /// Throws a [FormatException] if any field is invalid. + Metadata.parse({String testOn}) + : this( + testOn: testOn == null ? null : new PlatformSelector.parse(testOn)); } diff --git a/lib/src/backend/platform_selector.dart b/lib/src/backend/platform_selector.dart index 46e4db27..d614dec3 100644 --- a/lib/src/backend/platform_selector.dart +++ b/lib/src/backend/platform_selector.dart @@ -23,25 +23,57 @@ final _validVariables = /// and browsers. /// /// The syntax is mostly Dart's expression syntax restricted to boolean -/// operations. See the README for full details. -class PlatformSelector { - /// The parsed AST. - final Node _selector; +/// operations. See [the README][] for full details. +/// +/// [the README]: https://github.com/dart-lang/unittest/#platform-selector-syntax +abstract class PlatformSelector { + /// A selector that declares that a test can be run on all platforms. + /// + /// This isn't representable in the platform selector syntax but it is the + /// default selector. + static const all = const _AllPlatforms(); /// Parses [selector]. /// /// This will throw a [SourceSpanFormatException] if the selector is /// malformed or if it uses an undefined variable. - PlatformSelector.parse(String selector) - : _selector = new Parser(selector).parse() { - _selector.accept(const _VariableValidator()); - } + factory PlatformSelector.parse(String selector) => + new _PlatformSelector.parse(selector); /// Returns whether the selector matches the given [platform] and [os]. /// /// [os] defaults to [OperatingSystem.none]. + bool evaluate(TestPlatform platform, {OperatingSystem os}); +} + +/// The concrete implementation of a [PlatformSelector] parsed from a string. +/// +/// This is separate from [PlatformSelector] so that [_AllPlatforms] can +/// implement [PlatformSelector] without having to implement private members. +class _PlatformSelector implements PlatformSelector{ + /// The parsed AST. + final Node _selector; + + _PlatformSelector.parse(String selector) + : _selector = new Parser(selector).parse() { + _selector.accept(const _VariableValidator()); + } + + _PlatformSelector(this._selector); + bool evaluate(TestPlatform platform, {OperatingSystem os}) => _selector.accept(new Evaluator(platform, os: os)); + + String toString() => _selector.toString(); +} + +/// A selector that matches all platforms. +class _AllPlatforms implements PlatformSelector { + const _AllPlatforms(); + + bool evaluate(TestPlatform platform, {OperatingSystem os}) => true; + + String toString() => "*"; } /// An AST visitor that ensures that all variables are valid. diff --git a/lib/src/backend/suite.dart b/lib/src/backend/suite.dart index b2f3aff7..584ae9a1 100644 --- a/lib/src/backend/suite.dart +++ b/lib/src/backend/suite.dart @@ -6,6 +6,7 @@ library unittest.backend.suite; import 'dart:collection'; +import 'metadata.dart'; import 'test.dart'; /// A test suite. @@ -20,11 +21,23 @@ class Suite { /// The path to the Dart test suite, or `null` if that path is unknown. final String path; + /// The metadata associated with this test suite. + final Metadata metadata; + /// The tests in the test suite. final List<Test> tests; - Suite(Iterable<Test> tests, {String path, String platform}) - : path = path, - platform = platform, + Suite(Iterable<Test> tests, {this.path, this.platform, Metadata metadata}) + : metadata = metadata == null ? new Metadata() : metadata, tests = new UnmodifiableListView<Test>(tests.toList()); + + /// Returns a new suite with the given fields updated. + Suite change({String path, String platform, Metadata metadata, + Iterable<Test> tests}) { + if (path == null) path = this.path; + if (platform == null) platform = this.platform; + if (metadata == null) metadata = this.metadata; + if (tests == null) tests = this.tests; + return new Suite(tests, path: path, platform: platform, metadata: metadata); + } } diff --git a/lib/src/frontend/test_on.dart b/lib/src/frontend/test_on.dart index d5ed9754..9503ecb9 100644 --- a/lib/src/frontend/test_on.dart +++ b/lib/src/frontend/test_on.dart @@ -4,11 +4,11 @@ library unittest.frontend.test_on; -/// An annotation indicating which platforms a test or test suite supports. +/// An annotation indicating which platforms a test suite supports. /// -/// For the full syntax of [expression], see [the README][readme]. +/// For the full syntax of [expression], see [the README][]. /// -/// [readme]: https://github.com/dart-lang/unittest/#readme +/// [the README]: https://github.com/dart-lang/unittest/#platform-selector-syntax class TestOn { /// The expression specifying the platform. final String expression; diff --git a/lib/src/runner/load_exception.dart b/lib/src/runner/load_exception.dart index f2bc2f1e..99f5285e 100644 --- a/lib/src/runner/load_exception.dart +++ b/lib/src/runner/load_exception.dart @@ -7,6 +7,7 @@ library unittest.runner.load_exception; import 'dart:isolate'; import 'package:path/path.dart' as p; +import 'package:source_span/source_span.dart'; import '../utils.dart'; @@ -17,8 +18,11 @@ class LoadException implements Exception { LoadException(this.path, this.innerError); - String toString() { - var buffer = new StringBuffer('Failed to load "$path":'); + String toString({bool color: false}) { + var buffer = new StringBuffer(); + if (color) buffer.write('\u001b[31m'); // red + buffer.write('Failed to load "$path":'); + if (color) buffer.write('\u001b[0m'); // no color var innerString = getErrorMessage(innerError); if (innerError is IsolateSpawnException) { @@ -33,6 +37,9 @@ class LoadException implements Exception { "Uncaught Error: Load Error: FileSystemException: ", ""); innerString = innerString.split("Stack Trace:\n").first.trim(); + } if (innerError is SourceSpanException) { + innerString = innerError.toString(color: color) + .replaceFirst(" of $path", ""); } buffer.write(innerString.contains("\n") ? "\n" : " "); diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart index 6188cd5b..be393633 100644 --- a/lib/src/runner/loader.dart +++ b/lib/src/runner/loader.dart @@ -8,8 +8,10 @@ import 'dart:async'; import 'dart:io'; import 'dart:isolate'; +import 'package:analyzer/analyzer.dart'; import 'package:path/path.dart' as p; +import '../backend/metadata.dart'; import '../backend/suite.dart'; import '../backend/test_platform.dart'; import '../util/dart.dart'; @@ -18,6 +20,7 @@ import '../util/remote_exception.dart'; import '../utils.dart'; import 'browser/server.dart'; import 'load_exception.dart'; +import 'parse_metadata.dart'; import 'vm/isolate_test.dart'; /// A class for finding test files and loading them into a runnable form. @@ -82,11 +85,27 @@ class Loader { /// /// This will throw a [LoadException] if the file fails to load. Future<List<Suite>> loadFile(String path) { + var metadata; + try { + metadata = parseMetadata(path); + } on AnalyzerErrorGroup catch (_) { + // Ignore the analyzer's error, since its formatting is much worse than + // the VM's or dart2js's. + metadata = new Metadata(); + } on FormatException catch (error) { + throw new LoadException(path, error); + } + return Future.wait(_platforms.map((platform) { - if (platform == TestPlatform.chrome) return _loadBrowserFile(path); - assert(platform == TestPlatform.vm); - return _loadVmFile(path); - })); + return new Future.sync(() { + if (!metadata.testOn.evaluate(platform, os: currentOS)) return null; + + if (platform == TestPlatform.chrome) return _loadBrowserFile(path); + assert(platform == TestPlatform.vm); + return _loadVmFile(path); + }).then((suite) => + suite == null ? null : suite.change(metadata: metadata)); + })).then((suites) => suites.where((suite) => suite != null).toList()); } /// Load the test suite at [path] in a browser. diff --git a/lib/src/runner/parse_metadata.dart b/lib/src/runner/parse_metadata.dart index 5599922b..f0bba021 100644 --- a/lib/src/runner/parse_metadata.dart +++ b/lib/src/runner/parse_metadata.dart @@ -99,7 +99,7 @@ Metadata parseMetadata(String path) { testOn = args.first.stringValue; } - return new Metadata(testOn); + return new Metadata.parse(testOn: testOn); } /// Creates a [SourceSpan] for [node]. diff --git a/lib/src/util/io.dart b/lib/src/util/io.dart index 9a00ad64..c14e32f5 100644 --- a/lib/src/util/io.dart +++ b/lib/src/util/io.dart @@ -10,12 +10,22 @@ import 'dart:mirrors'; import 'package:path/path.dart' as p; +import '../backend/operating_system.dart'; import '../runner/load_exception.dart'; /// The root directory of the Dart SDK. final String sdkDir = p.dirname(p.dirname(Platform.executable)); +/// Returns the current operating system. +final OperatingSystem currentOS = (() { + var name = Platform.operatingSystem; + var os = OperatingSystem.findByIoName(name); + if (os != null) return os; + + throw new UnsupportedError('Unsupported operating system "$name".'); +})(); + /// The path to the `lib` directory of the `unittest` package. String libDir({String packageRoot}) { var pathToIo = libraryPath(#unittest.util.io, packageRoot: packageRoot); diff --git a/lib/unittest.dart b/lib/unittest.dart index 19f9095c..77a3b676 100644 --- a/lib/unittest.dart +++ b/lib/unittest.dart @@ -24,6 +24,7 @@ export 'src/frontend/expect.dart'; export 'src/frontend/expect_async.dart'; export 'src/frontend/future_matchers.dart'; export 'src/frontend/prints_matcher.dart'; +export 'src/frontend/test_on.dart'; export 'src/frontend/throws_matcher.dart'; export 'src/frontend/throws_matchers.dart'; diff --git a/test/backend/platform_selector/evaluate_test.dart b/test/backend/platform_selector/evaluate_test.dart index cca15600..840d7089 100644 --- a/test/backend/platform_selector/evaluate_test.dart +++ b/test/backend/platform_selector/evaluate_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:unittest/unittest.dart'; diff --git a/test/runner/browser/chrome_test.dart b/test/runner/browser/chrome_test.dart index 3d4fd7ce..aaea8019 100644 --- a/test/runner/browser/chrome_test.dart +++ b/test/runner/browser/chrome_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:async'; import 'dart:io'; diff --git a/test/runner/browser/compact_reporter_test.dart b/test/runner/browser/compact_reporter_test.dart index 6af92fe5..c24cebe4 100644 --- a/test/runner/browser/compact_reporter_test.dart +++ b/test/runner/browser/compact_reporter_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; diff --git a/test/runner/browser/compiler_pool_test.dart b/test/runner/browser/compiler_pool_test.dart index b08d65ff..34da6236 100644 --- a/test/runner/browser/compiler_pool_test.dart +++ b/test/runner/browser/compiler_pool_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; diff --git a/test/runner/browser/loader_test.dart b/test/runner/browser/loader_test.dart index e2ff2688..a556c5d3 100644 --- a/test/runner/browser/loader_test.dart +++ b/test/runner/browser/loader_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; diff --git a/test/runner/browser/runner_test.dart b/test/runner/browser/runner_test.dart index 1fbaf7ad..bf902433 100644 --- a/test/runner/browser/runner_test.dart +++ b/test/runner/browser/runner_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; diff --git a/test/runner/compact_reporter_test.dart b/test/runner/compact_reporter_test.dart index a0e4f1d8..f6729717 100644 --- a/test/runner/compact_reporter_test.dart +++ b/test/runner/compact_reporter_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; diff --git a/test/runner/isolate_listener_test.dart b/test/runner/isolate_listener_test.dart index 60b47ad2..df02ec4f 100644 --- a/test/runner/isolate_listener_test.dart +++ b/test/runner/isolate_listener_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:async'; import 'dart:isolate'; diff --git a/test/runner/loader_test.dart b/test/runner/loader_test.dart index de83e820..4f2a1ddf 100644 --- a/test/runner/loader_test.dart +++ b/test/runner/loader_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; @@ -84,14 +86,11 @@ void main() { test("throws a nice error if the package root doesn't exist", () { var loader = new Loader([TestPlatform.vm]); - expect(() { - try { - loader.loadFile(p.join(_sandbox, 'a_test.dart')); - } finally { - loader.close(); - } - }, throwsA(isLoadException( - "Directory ${p.join(_sandbox, 'packages')} does not exist."))); + expect( + loader.loadFile(p.join(_sandbox, 'a_test.dart')) + .whenComplete(loader.close), + throwsA(isLoadException( + "Directory ${p.join(_sandbox, 'packages')} does not exist."))); }); }); diff --git a/test/runner/parse_metadata_test.dart b/test/runner/parse_metadata_test.dart index 5dfa6eaf..29c8267f 100644 --- a/test/runner/parse_metadata_test.dart +++ b/test/runner/parse_metadata_test.dart @@ -2,10 +2,14 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; import 'package:unittest/unittest.dart'; +import 'package:unittest/src/backend/platform_selector.dart'; +import 'package:unittest/src/backend/test_platform.dart'; import 'package:unittest/src/runner/parse_metadata.dart'; String _sandbox; @@ -24,33 +28,35 @@ void main() { test("returns empty metadata for an empty file", () { new File(_path).writeAsStringSync(""); var metadata = parseMetadata(_path); - expect(metadata.testOn, isNull); + expect(metadata.testOn, equals(PlatformSelector.all)); }); test("ignores irrelevant annotations", () { new File(_path).writeAsStringSync("@Fblthp\n@Fblthp.foo\nlibrary foo;"); var metadata = parseMetadata(_path); - expect(metadata.testOn, isNull); + expect(metadata.testOn, equals(PlatformSelector.all)); }); test("parses a valid annotation", () { - new File(_path).writeAsStringSync("@TestOn('foo')\nlibrary foo;"); + new File(_path).writeAsStringSync("@TestOn('vm')\nlibrary foo;"); var metadata = parseMetadata(_path); - expect(metadata.testOn, equals("foo")); + expect(metadata.testOn.evaluate(TestPlatform.vm), isTrue); + expect(metadata.testOn.evaluate(TestPlatform.chrome), isFalse); }); test("parses a prefixed annotation", () { new File(_path).writeAsStringSync( - "@foo.TestOn('foo')\n" + "@foo.TestOn('vm')\n" "import 'package:unittest/unittest.dart' as foo;"); var metadata = parseMetadata(_path); - expect(metadata.testOn, equals("foo")); + expect(metadata.testOn.evaluate(TestPlatform.vm), isTrue); + expect(metadata.testOn.evaluate(TestPlatform.chrome), isFalse); }); test("ignores a constructor named TestOn", () { new File(_path).writeAsStringSync("@foo.TestOn('foo')\nlibrary foo;"); var metadata = parseMetadata(_path); - expect(metadata.testOn, isNull); + expect(metadata.testOn, equals(PlatformSelector.all)); }); group("throws an error for", () { diff --git a/test/runner/runner_test.dart b/test/runner/runner_test.dart index 002824a9..478ff77d 100644 --- a/test/runner/runner_test.dart +++ b/test/runner/runner_test.dart @@ -2,6 +2,8 @@ // 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. +@TestOn("vm") + import 'dart:io'; import 'package:path/path.dart' as p; @@ -98,6 +100,34 @@ $_usage""")); expect(result.exitCode, equals(exit_codes.data)); }); + // This is slightly different from the above test because it's an error + // that's caught first by the analyzer when it's used to parse the file. + test("a test file fails to parse", () { + var testPath = p.join(_sandbox, "test.dart"); + new File(testPath).writeAsStringSync("@TestOn)"); + var result = _runUnittest(["test.dart"]); + + expect(result.stderr, equals( + 'Failed to load "${p.relative(testPath, from: _sandbox)}":\n' + "line 1 pos 8: unexpected token ')'\n" + "@TestOn)\n" + " ^\n")); + expect(result.exitCode, equals(exit_codes.data)); + }); + + test("an annotation's structure is invalid", () { + var testPath = p.join(_sandbox, "test.dart"); + new File(testPath).writeAsStringSync("@TestOn()\nlibrary foo;"); + var result = _runUnittest(["test.dart"]); + + expect(result.stderr, equals( + 'Failed to load "${p.relative(testPath, from: _sandbox)}":\n' + "Error on line 1, column 8: TestOn takes one argument.\n" + "@TestOn()\n" + " ^^\n")); + expect(result.exitCode, equals(exit_codes.data)); + }); + test("a test file throws", () { var testPath = p.join(_sandbox, "test.dart"); new File(testPath).writeAsStringSync("void main() => throw 'oh no';"); diff --git a/test/runner/test_on_test.dart b/test/runner/test_on_test.dart new file mode 100644 index 00000000..547f758c --- /dev/null +++ b/test/runner/test_on_test.dart @@ -0,0 +1,118 @@ +// Copyright (c) 2015, 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. + +@TestOn("vm") + +import 'dart:io'; + +import 'package:path/path.dart' as p; +import 'package:unittest/src/util/io.dart'; +import 'package:unittest/unittest.dart'; + +import '../io.dart'; + +String _sandbox; + +final _vm = """ +@TestOn("vm") + +import 'package:unittest/unittest.dart'; + +void main() { + test("success", () {}); +} +"""; + +final _chrome = """ +@TestOn("chrome") + +// Make sure that loading this test file on the VM will break. +import 'dart:html'; + +import 'package:unittest/unittest.dart'; + +void main() { + test("success", () {}); +} +"""; + +final _thisOS = """ +@TestOn("$currentOS") + +import 'package:unittest/unittest.dart'; + +void main() { + test("success", () {}); +} +"""; + +final _otherOS = """ +@TestOn("${Platform.isWindows ? "mac-os" : "windows"}") + +// Make sure that loading this test file on the VM will break. +import 'dart:html'; + +import 'package:unittest/unittest.dart'; + +void main() { + test("success", () {}); +} +"""; + +void main() { + setUp(() { + _sandbox = Directory.systemTemp.createTempSync('unittest_').path; + }); + + tearDown(() { + new Directory(_sandbox).deleteSync(recursive: true); + }); + + test("runs a test suite on a matching platform", () { + new File(p.join(_sandbox, "vm_test.dart")).writeAsStringSync(_vm); + + var result = _runUnittest(["vm_test.dart"]); + expect(result.stdout, contains("All tests passed!")); + expect(result.exitCode, equals(0)); + }); + + test("doesn't run a test suite on a non-matching platform", () { + new File(p.join(_sandbox, "vm_test.dart")).writeAsStringSync(_vm); + + var result = _runUnittest(["--platform", "chrome", "vm_test.dart"]); + expect(result.stdout, contains("No tests ran.")); + expect(result.exitCode, equals(0)); + }); + + test("runs a test suite on a matching operating system", () { + new File(p.join(_sandbox, "os_test.dart")).writeAsStringSync(_thisOS); + + var result = _runUnittest(["os_test.dart"]); + expect(result.stdout, contains("All tests passed!")); + expect(result.exitCode, equals(0)); + }); + + test("doesn't run a test suite on a non-matching operating system", () { + new File(p.join(_sandbox, "os_test.dart")).writeAsStringSync(_otherOS); + + var result = _runUnittest(["os_test.dart"]); + expect(result.stdout, contains("No tests ran.")); + expect(result.exitCode, equals(0)); + }); + + test("only loads matching files when loading as a group", () { + new File(p.join(_sandbox, "vm_test.dart")).writeAsStringSync(_vm); + new File(p.join(_sandbox, "chrome_test.dart")).writeAsStringSync(_chrome); + new File(p.join(_sandbox, "this_os_test.dart")).writeAsStringSync(_thisOS); + new File(p.join(_sandbox, "other_os_test.dart")) + .writeAsStringSync(_otherOS); + + var result = _runUnittest(["."]); + expect(result.stdout, contains("+2: All tests passed!")); + expect(result.exitCode, equals(0)); + }); +} + +ProcessResult _runUnittest(List<String> args) => + runUnittest(args, workingDirectory: _sandbox); -- GitLab