diff --git a/bin/unittest.dart b/bin/unittest.dart
index 53cfcde65c72ed1620efd288366ab1e2bc3b636a..b86cf2f1d14270005bc41c6eb2ddb4898b6b601c 100644
--- a/bin/unittest.dart
+++ b/bin/unittest.dart
@@ -11,8 +11,8 @@ import 'dart:isolate';
 import 'package:args/args.dart';
 import 'package:stack_trace/stack_trace.dart';
 
+import 'package:unittest/src/backend/test_platform.dart';
 import 'package:unittest/src/runner/reporter/compact.dart';
-import 'package:unittest/src/runner/test_platform.dart';
 import 'package:unittest/src/runner/load_exception.dart';
 import 'package:unittest/src/runner/loader.dart';
 import 'package:unittest/src/util/exit_codes.dart' as exit_codes;
diff --git a/lib/src/backend/operating_system.dart b/lib/src/backend/operating_system.dart
new file mode 100644
index 0000000000000000000000000000000000000000..cf964306931337c534faf6ed9e22bfcd521fea47
--- /dev/null
+++ b/lib/src/backend/operating_system.dart
@@ -0,0 +1,66 @@
+// 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.
+
+library unittest.backend.operating_system;
+
+/// An enum of all operating systems supported by Dart.
+///
+/// This is used for selecting which operating systems a test can run on. Even
+/// for browser tests, this indicates the operating system of the machine
+/// running the test runner.
+class OperatingSystem {
+  /// Microsoft Windows.
+  static const windows = const OperatingSystem._("windows");
+
+  /// Mac OS X.
+  static const macOS = const OperatingSystem._("mac-os");
+
+  /// GNU/Linux.
+  static const linux = const OperatingSystem._("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");
+
+  /// 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");
+
+  /// A list of all instances of [OperatingSystem] other than [none].
+  static const all = const [windows, macOS, linux, android];
+
+  /// Finds an operating system by its name.
+  ///
+  /// If no operating system is found, returns [none].
+  static OperatingSystem find(String name) =>
+      all.firstWhere((platform) => platform.name == name, orElse: () => null);
+
+  /// Finds an operating system by the return value from `dart:io`'s
+  /// `Platform.operatingSystem`.
+  ///
+  /// If no operating system is found, returns [none].
+  static OperatingSystem findByIoName(String name) {
+    switch (name) {
+      case "windows": return windows;
+      case "macos": return macOS;
+      case "linux": return linux;
+      case "android": return android;
+      default: return none;
+    }
+  }
+
+  /// The name of the operating system.
+  final String name;
+
+  /// Whether this is a POSIX-ish operating system.
+  bool get isPosix => this != windows && this != none;
+
+  const OperatingSystem._(this.name);
+
+  String toString() => name;
+}
diff --git a/lib/src/backend/platform_selector.dart b/lib/src/backend/platform_selector.dart
new file mode 100644
index 0000000000000000000000000000000000000000..46e4db27aabc796340893b94f8db261585d2ac9e
--- /dev/null
+++ b/lib/src/backend/platform_selector.dart
@@ -0,0 +1,59 @@
+// 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.
+
+library unittest.backend.platform_selector;
+
+import 'package:source_span/source_span.dart';
+
+import 'operating_system.dart';
+import 'platform_selector/ast.dart';
+import 'platform_selector/evaluator.dart';
+import 'platform_selector/parser.dart';
+import 'platform_selector/visitor.dart';
+import 'test_platform.dart';
+
+/// The set of all valid variable names.
+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));
+
+/// An expression for selecting certain platforms, including operating systems
+/// 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;
+
+  /// 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());
+  }
+
+  /// Returns whether the selector matches the given [platform] and [os].
+  ///
+  /// [os] defaults to [OperatingSystem.none].
+  bool evaluate(TestPlatform platform, {OperatingSystem os}) =>
+      _selector.accept(new Evaluator(platform, os: os));
+}
+
+/// An AST visitor that ensures that all variables are valid.
+///
+/// This isn't done when evaluating to ensure that errors are eagerly detected,
+/// and it isn't done when parsing to avoid coupling the syntax too tightly to
+/// the semantics.
+class _VariableValidator extends RecursiveVisitor {
+  const _VariableValidator();
+
+  void visitVariable(VariableNode node) {
+    if (_validVariables.contains(node.name)) return;
+    throw new SourceSpanFormatException("Undefined variable.", node.span);
+  }
+}
diff --git a/lib/src/backend/platform_selector/ast.dart b/lib/src/backend/platform_selector/ast.dart
index d1eb0aac16f50b30f33a9256e171cb48dd405313..c76fe18096598fa13056f6b41525607b6b3e461f 100644
--- a/lib/src/backend/platform_selector/ast.dart
+++ b/lib/src/backend/platform_selector/ast.dart
@@ -6,6 +6,8 @@ library unittest.backend.platform_selector.ast;
 
 import 'package:source_span/source_span.dart';
 
+import 'visitor.dart';
+
 /// The superclass of nodes in the platform selector abstract syntax tree.
 abstract class Node {
   /// The span indicating where this node came from.
@@ -16,6 +18,9 @@ abstract class Node {
   ///
   /// This may be `null` for nodes without source information.
   FileSpan get span;
+
+  /// Calls the appropriate [Visitor] method on [this] and returns the result.
+  accept(Visitor visitor);
 }
 
 /// A single variable.
@@ -27,6 +32,8 @@ class VariableNode implements Node {
 
   VariableNode(this.name, [this.span]);
 
+  accept(Visitor visitor) => visitor.visitVariable(this);
+
   String toString() => name;
 }
 
@@ -39,6 +46,8 @@ class NotNode implements Node {
 
   NotNode(this.child, [this.span]);
 
+  accept(Visitor visitor) => visitor.visitNot(this);
+
   String toString() => child is VariableNode || child is NotNode
       ? "!$child"
       : "!($child)";
@@ -56,6 +65,8 @@ class OrNode implements Node {
 
   OrNode(this.left, this.right);
 
+  accept(Visitor visitor) => visitor.visitOr(this);
+
   String toString() {
     var string1 = left is AndNode || left is ConditionalNode
         ? "($left)"
@@ -80,6 +91,8 @@ class AndNode implements Node {
 
   AndNode(this.left, this.right);
 
+  accept(Visitor visitor) => visitor.visitAnd(this);
+
   String toString() {
     var string1 = left is OrNode || left is ConditionalNode
         ? "($left)"
@@ -107,6 +120,8 @@ class ConditionalNode implements Node {
 
   ConditionalNode(this.condition, this.whenTrue, this.whenFalse);
 
+  accept(Visitor visitor) => visitor.visitConditional(this);
+
   String toString() {
     var conditionString =
         condition is ConditionalNode ? "($condition)" : condition;
diff --git a/lib/src/backend/platform_selector/evaluator.dart b/lib/src/backend/platform_selector/evaluator.dart
new file mode 100644
index 0000000000000000000000000000000000000000..da035a3deed401778ee9a7228e6bba5891fdc0ac
--- /dev/null
+++ b/lib/src/backend/platform_selector/evaluator.dart
@@ -0,0 +1,51 @@
+// 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.
+
+library unittest.backend.platform_selector.evaluator;
+
+import 'package:source_span/source_span.dart';
+
+import '../operating_system.dart';
+import '../test_platform.dart';
+import 'ast.dart';
+import 'visitor.dart';
+
+/// A visitor for evaluating platform selectors against a specific
+/// [TestPlatform] and [OperatingSystem].
+class Evaluator implements Visitor<bool> {
+  /// The platform to test against.
+  final TestPlatform _platform;
+
+  /// The operating system to test against.
+  final OperatingSystem _os;
+
+  Evaluator(this._platform, {OperatingSystem os})
+      : _os = os == null ? OperatingSystem.none : os;
+
+  bool visitVariable(VariableNode node) {
+    if (node.name == _platform.identifier) return true;
+    if (node.name == _os.name) return true;
+
+    switch (node.name) {
+      case "dart-vm": return _platform.isDartVm;
+      case "browser": return _platform.isBrowser;
+      case "js": return _platform.isJS;
+      case "blink": return _platform.isBlink;
+      case "posix": return _os.isPosix;
+      default: return false;
+    }
+  }
+
+  bool visitNot(NotNode node) => !node.child.accept(this);
+
+  bool visitOr(OrNode node) =>
+      node.left.accept(this) || node.right.accept(this);
+
+  bool visitAnd(AndNode node) =>
+      node.left.accept(this) && node.right.accept(this);
+
+  bool visitConditional(ConditionalNode node) => node.condition.accept(this)
+      ? node.whenTrue.accept(this)
+      : node.whenFalse.accept(this);
+}
diff --git a/lib/src/backend/platform_selector/visitor.dart b/lib/src/backend/platform_selector/visitor.dart
new file mode 100644
index 0000000000000000000000000000000000000000..09951e49fc1b22f5d860cecdd8c766e4f07d466b
--- /dev/null
+++ b/lib/src/backend/platform_selector/visitor.dart
@@ -0,0 +1,46 @@
+// 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.
+
+library unittest.backend.platform_selector.visitor;
+
+import 'ast.dart';
+
+/// The interface for visitors of the platform selector AST.
+abstract class Visitor<T> {
+  T visitVariable(VariableNode node);
+  T visitNot(NotNode node);
+  T visitOr(OrNode node);
+  T visitAnd(AndNode node);
+  T visitConditional(ConditionalNode node);
+}
+
+/// An abstract superclass for side-effect-based visitors.
+///
+/// The default implementations of this visitor's methods just traverse the AST
+/// and do nothing with it.
+abstract class RecursiveVisitor implements Visitor {
+  const RecursiveVisitor();
+
+  void visitVariable(VariableNode node) {}
+
+  void visitNot(NotNode node) {
+    node.child.accept(this);
+  }
+
+  void visitOr(OrNode node) {
+    node.left.accept(this);
+    node.right.accept(this);
+  }
+
+  void visitAnd(AndNode node) {
+    node.left.accept(this);
+    node.right.accept(this);
+  }
+
+  void visitConditional(ConditionalNode node) {
+    node.condition.accept(this);
+    node.whenTrue.accept(this);
+    node.whenFalse.accept(this);
+  }
+}
diff --git a/lib/src/runner/test_platform.dart b/lib/src/backend/test_platform.dart
similarity index 52%
rename from lib/src/runner/test_platform.dart
rename to lib/src/backend/test_platform.dart
index 406dad0cefc4ebef5222cde9bb661282007de23d..a52d3075fa91011cd0e8b2891cc76c96c8c5b3e8 100644
--- a/lib/src/runner/test_platform.dart
+++ b/lib/src/backend/test_platform.dart
@@ -2,14 +2,20 @@
 // 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 unittest.backend.test_platform;
+
 // TODO(nweiz): support pluggable platforms.
 /// An enum of all platforms on which tests can run.
 class TestPlatform {
+  // When adding new platforms, be sure to update the baseline and derived
+  // variable tests in test/backend/platform_selector/evaluate_test.
+
   /// The command-line Dart VM.
-  static const vm = const TestPlatform._("VM", "vm");
+  static const vm = const TestPlatform._("VM", "vm", isDartVm: true);
 
   /// Google Chrome.
-  static const chrome = const TestPlatform._("Chrome", "chrome");
+  static const chrome = const TestPlatform._("Chrome", "chrome",
+      isBrowser: true, isJS: true, isBlink: true);
 
   /// A list of all instances of [TestPlatform].
   static const all = const [vm, chrome];
@@ -27,7 +33,20 @@ class TestPlatform {
   /// The identifier used to look up the platform.
   final String identifier;
 
-  const TestPlatform._(this.name, this.identifier);
+  /// Whether this platform runs the Dart VM in any capacity.
+  final bool isDartVm;
+
+  /// Whether this platform is a browser.
+  final bool isBrowser;
+
+  /// Whether this platform runs Dart compiled to JavaScript.
+  final bool isJS;
+
+  /// Whether this platform uses the Blink rendering engine.
+  final bool isBlink;
+
+  const TestPlatform._(this.name, this.identifier, {this.isDartVm: false,
+      this.isBrowser: false, this.isJS: false, this.isBlink: false});
 
   String toString() => name;
 }
diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart
index f9517124b06751d8a36881b03232187ffb0e192e..6188cd5bd47cafa2c962b1a0a1cf590b60cb7461 100644
--- a/lib/src/runner/loader.dart
+++ b/lib/src/runner/loader.dart
@@ -11,7 +11,7 @@ import 'dart:isolate';
 import 'package:path/path.dart' as p;
 
 import '../backend/suite.dart';
-import '../runner/test_platform.dart';
+import '../backend/test_platform.dart';
 import '../util/dart.dart';
 import '../util/io.dart';
 import '../util/remote_exception.dart';
diff --git a/test/backend/platform_selector/evaluate_test.dart b/test/backend/platform_selector/evaluate_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..cca156008a2b9fe8274397875cb8f6a7e3db4cad
--- /dev/null
+++ b/test/backend/platform_selector/evaluate_test.dart
@@ -0,0 +1,140 @@
+// 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.
+
+import 'dart:io';
+
+import 'package:unittest/unittest.dart';
+import 'package:unittest/src/backend/operating_system.dart';
+import 'package:unittest/src/backend/platform_selector.dart';
+import 'package:unittest/src/backend/test_platform.dart';
+
+void main() {
+  test("new PlatformSelector.parse() disallows invalid variables", () {
+    expect(() => new PlatformSelector.parse("undefined"),
+        throwsFormatException);
+  });
+
+  group("operator:", () {
+    test("conditional", () {
+      _expectEval("vm ? vm : browser", true);
+      _expectEval("vm ? browser : vm", false);
+      _expectEval("browser ? vm : browser", false);
+      _expectEval("browser ? browser : vm", true);
+    });
+
+    test("or", () {
+      _expectEval("vm || vm", true);
+      _expectEval("vm || browser", true);
+      _expectEval("browser || vm", true);
+      _expectEval("browser || browser", false);
+    });
+
+    test("and", () {
+      _expectEval("vm && vm", true);
+      _expectEval("vm && browser", false);
+      _expectEval("browser && vm", false);
+      _expectEval("browser && browser", false);
+    });
+
+    test("not", () {
+      _expectEval("!vm", false);
+      _expectEval("!browser", true);
+    });
+  });
+
+  group("baseline variable:", () {
+    test("vm", () {
+      _expectEval("vm", true, platform: TestPlatform.vm);
+      _expectEval("vm", false, platform: TestPlatform.chrome);
+    });
+
+    test("chrome", () {
+      _expectEval("chrome", true, platform: TestPlatform.chrome);
+      _expectEval("chrome", false, platform: TestPlatform.vm);
+    });
+
+    test("windows", () {
+      _expectEval("windows", true, os: OperatingSystem.windows);
+      _expectEval("windows", false, os: OperatingSystem.linux);
+      _expectEval("windows", false, os: OperatingSystem.none);
+    });
+
+    test("mac-os", () {
+      _expectEval("mac-os", true, os: OperatingSystem.macOS);
+      _expectEval("mac-os", false, os: OperatingSystem.linux);
+      _expectEval("mac-os", false, os: OperatingSystem.none);
+    });
+
+    test("linux", () {
+      _expectEval("linux", true, os: OperatingSystem.linux);
+      _expectEval("linux", false, os: OperatingSystem.android);
+      _expectEval("linux", false, os: OperatingSystem.none);
+    });
+
+    test("android", () {
+      _expectEval("android", true, os: OperatingSystem.android);
+      _expectEval("android", false, os: OperatingSystem.linux);
+      _expectEval("android", false, os: OperatingSystem.none);
+    });
+  });
+
+  group("derived variable:", () {
+    test("dart-vm", () {
+      _expectEval("dart-vm", true, platform: TestPlatform.vm);
+      _expectEval("dart-vm", false, platform: TestPlatform.chrome);
+    });
+
+    test("browser", () {
+      _expectEval("browser", true, platform: TestPlatform.chrome);
+      _expectEval("browser", false, platform: TestPlatform.vm);
+    });
+
+    test("js", () {
+      _expectEval("js", true, platform: TestPlatform.chrome);
+      _expectEval("js", false, platform: TestPlatform.vm);
+    });
+
+    test("blink", () {
+      _expectEval("blink", true, platform: TestPlatform.chrome);
+      _expectEval("blink", false, platform: TestPlatform.vm);
+    });
+
+    test("posix", () {
+      _expectEval("posix", false, os: OperatingSystem.windows);
+      _expectEval("posix", true, os: OperatingSystem.macOS);
+      _expectEval("posix", true, os: OperatingSystem.linux);
+      _expectEval("posix", true, os: OperatingSystem.android);
+      _expectEval("posix", false, os: OperatingSystem.none);
+    });
+  });
+}
+
+/// Asserts that [expression] evaluates to [result] on [platform] and [os].
+///
+/// [platform] defaults to [TestPlatform.vm]; [os] defaults to the current
+/// operating system.
+void _expectEval(String expression, bool result, {TestPlatform platform,
+    OperatingSystem os}) {
+
+  var reason = 'Expected "$expression" to evaluate to $result';
+  if (platform != null && os != null) {
+    reason += ' on $platform and $os.';
+  } else if (platform != null || os != null) {
+    reason += ' on ${platform == null ? os : platform}';
+  }
+
+  expect(_eval(expression, platform: platform, os: os), equals(result),
+      reason: '$reason.');
+}
+
+/// Returns the result of evaluating [expression] on [platform] and [os].
+///
+/// [platform] defaults to [TestPlatform.vm]; [os] defaults to the current
+/// operating system.
+bool _eval(String expression, {TestPlatform platform, OperatingSystem os}) {
+  if (platform == null) platform = TestPlatform.vm;
+  if (os == null) os = OperatingSystem.findByIoName(Platform.operatingSystem);
+  var selector = new PlatformSelector.parse(expression);
+  return selector.evaluate(platform, os: os);
+}
diff --git a/test/runner/browser/loader_test.dart b/test/runner/browser/loader_test.dart
index 796b0c2318557194e35d43cb3f7eeeb8d15abe53..e2ff2688b4167772de5a2fcedc0263499410660b 100644
--- a/test/runner/browser/loader_test.dart
+++ b/test/runner/browser/loader_test.dart
@@ -6,7 +6,7 @@ import 'dart:io';
 
 import 'package:path/path.dart' as p;
 import 'package:unittest/src/backend/state.dart';
-import 'package:unittest/src/runner/test_platform.dart';
+import 'package:unittest/src/backend/test_platform.dart';
 import 'package:unittest/src/runner/loader.dart';
 import 'package:unittest/unittest.dart';