From bfc313427dd46417ca5fad5a1adee228383ec42f Mon Sep 17 00:00:00 2001 From: Natalie Weizenbaum <nweiz@google.com> Date: Tue, 23 Feb 2016 14:02:48 -0800 Subject: [PATCH] Warn when an unsupported platform is passed. If the config file contains a top-level test_on field, the runner will now warn if the runner is invoked with an unsupported platform. R=kevmoo@google.com Review URL: https://codereview.chromium.org//1715523003 . --- README.md | 5 + doc/package_config.md | 18 ++- lib/src/backend/operating_system.dart | 17 ++- lib/src/backend/platform_selector.dart | 4 +- lib/src/runner.dart | 52 ++++++++ lib/src/utils.dart | 8 +- pubspec.yaml | 2 +- test/io.dart | 3 + test/runner/configuration/top_level_test.dart | 123 ++++++++++++++---- test/runner/test_on_test.dart | 2 - 10 files changed, 188 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 30157a8a..80c4654d 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,11 @@ 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. +You can also declare that your entire package only works on certain platforms by +adding a [`test_on` field][test_on] to your package config file. + +[test_on]: https://github.com/dart-lang/test/blob/master/doc/package_config.md#test_on + ### Platform Selectors Platform selectors use the [boolean selector syntax][] defined in the diff --git a/doc/package_config.md b/doc/package_config.md index 23db939b..bead8c79 100644 --- a/doc/package_config.md +++ b/doc/package_config.md @@ -111,10 +111,10 @@ tags: ### `test_on` -This field declares which platforms a test supports. It's usually applied to -[specific tags](#configuring-tags) rather than used at the top level. It takes a -[platform selector][] and only allows a test to run on platforms that match the -selector. +This field declares which platforms a test supports. It takes a +[platform selector][] and only allows tests to run on platforms that match the +selector. It's often used with [specific tags](#configuring-tags) to ensure that +certain features will only be tested on supported platforms. [platform selector]: https://github.com/dart-lang/test/blob/master/README.md#platform-selectors @@ -124,6 +124,16 @@ tags: promises: {test_on: "browser && !ie"} ``` +The field can also be used at the top level of the configuration file to +indicate that the entire package only supports a particular platform. If someone +tries to run the tests on an unsupported platform, the runner will print a +warning and skip that platform. + +```yaml +# This package uses dart:io. +test_on: vm +``` + ## Runner Configuration Unlike [test configuration](#test-configuration), runner configuration affects diff --git a/lib/src/backend/operating_system.dart b/lib/src/backend/operating_system.dart index 4ed251d3..610f3b58 100644 --- a/lib/src/backend/operating_system.dart +++ b/lib/src/backend/operating_system.dart @@ -9,25 +9,25 @@ /// running the test runner. class OperatingSystem { /// Microsoft Windows. - static const windows = const OperatingSystem._("windows"); + static const windows = const OperatingSystem._("Windows", "windows"); /// Mac OS X. - static const macOS = const OperatingSystem._("mac-os"); + static const macOS = const OperatingSystem._("OS X", "mac-os"); /// GNU/Linux. - static const linux = const OperatingSystem._("linux"); + static const linux = const OperatingSystem._("Linux", "linux"); /// Android. /// /// Since this is the operating system the test runner is running on, this /// won't be true when testing remotely on an Android browser. - static const android = const OperatingSystem._("android"); + static const android = const OperatingSystem._("Android", "android"); /// No operating system. /// /// This is used when running in the browser, or if an unrecognized operating /// system is used. It can't be referenced by name in platform selectors. - static const none = const OperatingSystem._("none"); + static const none = const OperatingSystem._("none", "none"); /// A list of all instances of [OperatingSystem] other than [none]. static const all = const [windows, macOS, linux, android]; @@ -52,13 +52,16 @@ class OperatingSystem { } } - /// The name of the operating system. + /// The human-friendly of the operating system. final String name; + /// The identifier used to look up the operating system. + final String identifier; + /// Whether this is a POSIX-ish operating system. bool get isPosix => this != windows && this != none; - const OperatingSystem._(this.name); + const OperatingSystem._(this.name, this.identifier); String toString() => name; } diff --git a/lib/src/backend/platform_selector.dart b/lib/src/backend/platform_selector.dart index af55257b..10969f75 100644 --- a/lib/src/backend/platform_selector.dart +++ b/lib/src/backend/platform_selector.dart @@ -12,7 +12,7 @@ import 'test_platform.dart'; final _validVariables = new Set<String>.from(["posix", "dart-vm", "browser", "js", "blink"]) ..addAll(TestPlatform.all.map((platform) => platform.identifier)) - ..addAll(OperatingSystem.all.map((os) => os.name)); + ..addAll(OperatingSystem.all.map((os) => os.identifier)); /// An expression for selecting certain platforms, including operating systems /// and browsers. @@ -46,7 +46,7 @@ class PlatformSelector { return _inner.evaluate((variable) { if (variable == platform.identifier) return true; - if (variable == os.name) return true; + if (variable == os.identifier) return true; switch (variable) { case "dart-vm": return platform.isDartVM; case "browser": return platform.isBrowser; diff --git a/lib/src/runner.dart b/lib/src/runner.dart index 4da2710b..1eb6f2c7 100644 --- a/lib/src/runner.dart +++ b/lib/src/runner.dart @@ -9,6 +9,8 @@ import 'package:async/async.dart'; import 'backend/group.dart'; import 'backend/group_entry.dart'; +import 'backend/operating_system.dart'; +import 'backend/platform_selector.dart'; import 'backend/suite.dart'; import 'backend/test.dart'; import 'backend/test_platform.dart'; @@ -105,6 +107,8 @@ class Runner { throw new StateError("run() may not be called on a closed Runner."); } + _warnForUnsupportedPlatforms(); + var suites = _loadSuites(); var success; @@ -139,6 +143,54 @@ class Runner { return success == true; } + /// Emits a warning if the user is trying to run on a platform that's + /// unsupported for the entire package. + void _warnForUnsupportedPlatforms() { + if (_config.testOn == PlatformSelector.all) return; + + var unsupportedPlatforms = _config.platforms.where((platform) { + return !_config.testOn.evaluate(platform, os: currentOS); + }).toList(); + if (unsupportedPlatforms.isEmpty) return; + + // Human-readable names for all unsupported platforms. + var unsupportedNames = []; + + // If the user tried to run on one or moe unsupported browsers, figure out + // whether we should warn about the individual browsers or whether all + // browsers are unsupported. + var unsupportedBrowsers = unsupportedPlatforms + .where((platform) => platform.isBrowser); + if (unsupportedBrowsers.isNotEmpty) { + var supportsAnyBrowser = TestPlatform.all + .where((platform) => platform.isBrowser) + .any((platform) => _config.testOn.evaluate(platform)); + + if (supportsAnyBrowser) { + unsupportedNames.addAll( + unsupportedBrowsers.map((platform) => platform.name)); + } else { + unsupportedNames.add("browsers"); + } + } + + // If the user tried to run on the VM and it's not supported, figure out if + // that's because of the current OS or whether the VM is unsupported. + if (unsupportedPlatforms.contains(TestPlatform.vm)) { + var supportsAnyOS = OperatingSystem.all.any((os) => + _config.testOn.evaluate(TestPlatform.vm, os: os)); + + if (supportsAnyOS) { + unsupportedNames.add(currentOS.name); + } else { + unsupportedNames.add("the Dart VM"); + } + } + + warn("this package doesn't support running tests on " + + toSentence(unsupportedNames, conjunction: "or") + "."); + } + /// Closes the runner. /// /// This stops any future test suites from running. It will wait for any diff --git a/lib/src/utils.dart b/lib/src/utils.dart index da3f9af4..6d54faf0 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -103,12 +103,14 @@ String indent(String str) => /// Returns a sentence fragment listing the elements of [iter]. /// /// This converts each element of [iter] to a string and separates them with -/// commas and/or "and" where appropriate. -String toSentence(Iterable iter) { +/// commas and/or [conjunction] where appropriate. The [conjunction] defaults to +/// "and". +String toSentence(Iterable iter, {String conjunction}) { if (iter.length == 1) return iter.first.toString(); + var result = iter.take(iter.length - 1).join(", "); if (iter.length > 2) result += ","; - return "$result and ${iter.last}"; + return "$result ${conjunction ?? 'and'} ${iter.last}"; } /// Returns [name] if [number] is 1, or the plural of [name] otherwise. diff --git a/pubspec.yaml b/pubspec.yaml index a4e6781e..3574b5cf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: test -version: 0.12.11-dev +version: 0.12.11 author: Dart Team <misc@dartlang.org> description: A library for writing dart unit tests. homepage: https://github.com/dart-lang/test diff --git a/test/io.dart b/test/io.dart index 1db00ed7..f7ead650 100644 --- a/test/io.dart +++ b/test/io.dart @@ -33,6 +33,9 @@ final String noSuchFileMessage = Platform.isWindows final _servingRegExp = new RegExp(r'^Serving myapp [a-z]+ on http://localhost:(\d+)$'); +/// An operating system name that's different than the current operating system. +final otherOS = Platform.isWindows ? "mac-os" : "windows"; + /// A future that will return the port of a pub serve instance run via /// [runPubServe]. /// diff --git a/test/runner/configuration/top_level_test.dart b/test/runner/configuration/top_level_test.dart index 62103dbd..0069cf87 100644 --- a/test/runner/configuration/top_level_test.dart +++ b/test/runner/configuration/top_level_test.dart @@ -10,6 +10,7 @@ import 'package:path/path.dart' as p; import 'package:scheduled_test/descriptor.dart' as d; import 'package:scheduled_test/scheduled_stream.dart'; import 'package:scheduled_test/scheduled_test.dart'; +import 'package:test/src/util/io.dart'; import '../../io.dart'; @@ -109,40 +110,108 @@ void main() { test.shouldExit(0); }); - test("runs tests on a platform that matches test_on", () { - d.file("dart_test.yaml", JSON.encode({ - "test_on": "vm" - })).create(); + group("test_on", () { + test("runs tests on a platform matching platform", () { + d.file("dart_test.yaml", JSON.encode({ + "test_on": "vm" + })).create(); - d.file("test.dart", """ - import 'package:test/test.dart'; + d.file("test.dart", """ + import 'package:test/test.dart'; - void main() { - test("test", () {}); - } - """).create(); + void main() { + test("test", () {}); + } + """).create(); - var test = runTest(["test.dart"]); - test.stdout.expect(consumeThrough(contains('All tests passed!'))); - test.shouldExit(0); - }); + var test = runTest(["test.dart"]); + test.stdout.expect(consumeThrough(contains('All tests passed!'))); + test.shouldExit(0); + }); - test("doesn't run tests on a platform that doesn't match test_on", () { - d.file("dart_test.yaml", JSON.encode({ - "test_on": "chrome" - })).create(); + test("warns about the VM when no OSes are supported", () { + d.file("dart_test.yaml", JSON.encode({ + "test_on": "chrome" + })).create(); - d.file("test.dart", """ - import 'package:test/test.dart'; + d.file("test.dart", """ + import 'package:test/test.dart'; - void main() { - test("test", () {}); - } - """).create(); + void main() { + test("test", () {}); + } + """).create(); + + var test = runTest(["test.dart"]); + test.stderr.expect( + "Warning: this package doesn't support running tests on the Dart " + "VM."); + test.stdout.expect(consumeThrough(contains('No tests ran.'))); + test.shouldExit(0); + }); + + test("warns about the OS when some OSes are supported", () { + d.file("dart_test.yaml", JSON.encode({ + "test_on": otherOS + })).create(); + + d.file("test.dart", """ + import 'package:test/test.dart'; - var test = runTest(["test.dart"]); - test.stdout.expect(consumeThrough(contains('No tests ran.'))); - test.shouldExit(0); + void main() { + test("test", () {}); + } + """).create(); + + var test = runTest(["test.dart"]); + test.stderr.expect( + "Warning: this package doesn't support running tests on " + "${currentOS.name}."); + test.stdout.expect(consumeThrough(contains('No tests ran.'))); + test.shouldExit(0); + }); + + test("warns about browsers in general when no browsers are supported", () { + d.file("dart_test.yaml", JSON.encode({ + "test_on": "vm" + })).create(); + + d.file("test.dart", """ + import 'package:test/test.dart'; + + void main() { + test("test", () {}); + } + """).create(); + + var test = runTest(["-p", "chrome", "test.dart"]); + test.stderr.expect( + "Warning: this package doesn't support running tests on browsers."); + test.stdout.expect(consumeThrough(contains('No tests ran.'))); + test.shouldExit(0); + }); + + test("warns about specific browsers when specific browsers are " + "supported", () { + d.file("dart_test.yaml", JSON.encode({ + "test_on": "safari" + })).create(); + + d.file("test.dart", """ + import 'package:test/test.dart'; + + void main() { + test("test", () {}); + } + """).create(); + + var test = runTest(["-p", "chrome,firefox,phantomjs", "test.dart"]); + test.stderr.expect( + "Warning: this package doesn't support running tests on Chrome, " + "Firefox, or PhantomJS."); + test.stdout.expect(consumeThrough(contains('No tests ran.'))); + test.shouldExit(0); + }); }); test("uses the specified reporter", () { diff --git a/test/runner/test_on_test.dart b/test/runner/test_on_test.dart index 01b321c0..3fa8608d 100644 --- a/test/runner/test_on_test.dart +++ b/test/runner/test_on_test.dart @@ -13,8 +13,6 @@ import 'package:test/src/util/io.dart'; import '../io.dart'; -final _otherOS = Platform.isWindows ? "mac-os" : "windows"; - void main() { useSandbox(); -- GitLab