Add a platform selector variable for Google internal tests (#779)

See the internal bug b/73723892
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d9cb8d6..18e5325 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,9 @@
 * Add an `include` configuration field which specifies the path to another
   configuration file whose configuration should be used.
 
+* Add a `google` platform selector variable that's only true on Google's
+  internal infrastructure.
+
 ## 0.12.31
 
 * Add a `headless` configuration option for Chrome.
diff --git a/lib/src/backend/platform_selector.dart b/lib/src/backend/platform_selector.dart
index 7cc21b3..35b063d 100644
--- a/lib/src/backend/platform_selector.dart
+++ b/lib/src/backend/platform_selector.dart
@@ -10,10 +10,10 @@
 import 'suite_platform.dart';
 
 /// The set of variable names that are valid for all platform selectors.
-final _universalValidVariables =
-    new Set<String>.from(["posix", "dart-vm", "browser", "js", "blink"])
-      ..addAll(Runtime.builtIn.map((runtime) => runtime.identifier))
-      ..addAll(OperatingSystem.all.map((os) => os.identifier));
+final _universalValidVariables = new Set<String>.from(
+    ["posix", "dart-vm", "browser", "js", "blink", "google"])
+  ..addAll(Runtime.builtIn.map((runtime) => runtime.identifier))
+  ..addAll(OperatingSystem.all.map((os) => os.identifier));
 
 /// An expression for selecting certain platforms, including operating systems
 /// and browsers.
@@ -88,6 +88,8 @@
           return platform.runtime.isBlink;
         case "posix":
           return platform.os.isPosix;
+        case "google":
+          return platform.inGoogle;
         default:
           return false;
       }
diff --git a/lib/src/backend/suite_platform.dart b/lib/src/backend/suite_platform.dart
index 0f4a5b1..5ac8e33 100644
--- a/lib/src/backend/suite_platform.dart
+++ b/lib/src/backend/suite_platform.dart
@@ -2,6 +2,9 @@
 // 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.
 
+// Prefix this import to avoid accidentally using IO stuff in cross-platform
+// contexts.
+import '../util/io.dart' as io;
 import 'operating_system.dart';
 import 'runtime.dart';
 
@@ -16,27 +19,45 @@
   /// true.
   final OperatingSystem os;
 
+  /// Whether we're running on Google-internal infrastructure.
+  final bool inGoogle;
+
   /// Creates a new platform with the given [runtime] and [os], which defaults
   /// to [OperatingSystem.none].
   ///
   /// Throws an [ArgumentError] if [runtime] is a browser and [os] is not
   /// `null` or [OperatingSystem.none].
-  SuitePlatform(this.runtime, {OperatingSystem os})
+  SuitePlatform(this.runtime, {OperatingSystem os, this.inGoogle: false})
       : os = os ?? OperatingSystem.none {
     if (runtime.isBrowser && this.os != OperatingSystem.none) {
       throw new ArgumentError('No OS should be passed for runtime "$runtime".');
     }
   }
 
+  /// Creates a new platform with the given [runtime] and [os] and [inGoogle]
+  /// determined using `dart:io`.
+  ///
+  /// If [runtime] is a browser, this will set [os] to [OperatingSystem.none].
+  ///
+  /// Throws an [UnsupportedError] if called in a context where `dart:io` is
+  /// unavailable.
+  SuitePlatform.current(this.runtime)
+      : os = runtime.isBrowser ? OperatingSystem.none : io.currentOS,
+        inGoogle = io.inGoogle;
+
   /// Converts a JSON-safe representation generated by [serialize] back into a
   /// [SuitePlatform].
   factory SuitePlatform.deserialize(Object serialized) {
     var map = serialized as Map;
     return new SuitePlatform(new Runtime.deserialize(map['runtime']),
-        os: OperatingSystem.find(map['os']));
+        os: OperatingSystem.find(map['os']), inGoogle: map['inGoogle']);
   }
 
   /// Converts [this] into a JSON-safe object that can be converted back to a
   /// [SuitePlatform] using [new SuitePlatform.deserialize].
-  Object serialize() => {'runtime': runtime.serialize(), 'os': os.identifier};
+  Object serialize() => {
+        'runtime': runtime.serialize(),
+        'os': os.identifier,
+        'inGoogle': inGoogle
+      };
 }
diff --git a/lib/src/runner.dart b/lib/src/runner.dart
index 202ea0e..236ab0f 100644
--- a/lib/src/runner.dart
+++ b/lib/src/runner.dart
@@ -132,8 +132,7 @@
         .map(_loader.findRuntime)
         .where((runtime) =>
             runtime != null &&
-            !testOn.evaluate(new SuitePlatform(runtime,
-                os: runtime.isBrowser ? null : currentOS)))
+            !testOn.evaluate(new SuitePlatform.current(runtime)))
         .toList();
     if (unsupportedRuntimes.isEmpty) return;
 
@@ -148,7 +147,8 @@
     if (unsupportedBrowsers.isNotEmpty) {
       var supportsAnyBrowser = _loader.allRuntimes
           .where((runtime) => runtime.isBrowser)
-          .any((runtime) => testOn.evaluate(new SuitePlatform(runtime)));
+          .any(
+              (runtime) => testOn.evaluate(new SuitePlatform.current(runtime)));
 
       if (supportsAnyBrowser) {
         unsupportedNames
@@ -161,8 +161,8 @@
     // 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 (unsupportedRuntimes.contains(Runtime.vm)) {
-      var supportsAnyOS = OperatingSystem.all
-          .any((os) => testOn.evaluate(new SuitePlatform(Runtime.vm, os: os)));
+      var supportsAnyOS = OperatingSystem.all.any((os) => testOn
+          .evaluate(new SuitePlatform(Runtime.vm, os: os, inGoogle: inGoogle)));
 
       if (supportsAnyOS) {
         unsupportedNames.add(currentOS.name);
diff --git a/lib/src/runner/browser/browser_manager.dart b/lib/src/runner/browser/browser_manager.dart
index 8743378..a2d80c4 100644
--- a/lib/src/runner/browser/browser_manager.dart
+++ b/lib/src/runner/browser/browser_manager.dart
@@ -242,7 +242,7 @@
       });
 
       try {
-        controller = deserializeSuite(path, new SuitePlatform(_runtime),
+        controller = deserializeSuite(path, new SuitePlatform.current(_runtime),
             suiteConfig, await _environment, suiteChannel, message);
 
         controller.channel("test.browser.mapper").sink.add(mapper?.serialize());
diff --git a/lib/src/runner/load_suite.dart b/lib/src/runner/load_suite.dart
index 2d19f56..3743c8b 100644
--- a/lib/src/runner/load_suite.dart
+++ b/lib/src/runner/load_suite.dart
@@ -15,7 +15,6 @@
 import '../backend/suite_platform.dart';
 import '../backend/test.dart';
 import '../backend/runtime.dart';
-import '../util/io.dart';
 import '../utils.dart';
 import 'configuration/suite.dart';
 import 'load_exception.dart';
@@ -123,7 +122,7 @@
     return new LoadSuite(
         "loading ${exception.path}",
         config ?? SuiteConfiguration.empty,
-        platform ?? new SuitePlatform(Runtime.vm, os: currentOS),
+        platform ?? new SuitePlatform.current(Runtime.vm),
         () => new Future.error(exception, stackTrace),
         path: exception.path);
   }
diff --git a/lib/src/runner/loader.dart b/lib/src/runner/loader.dart
index 5f1d798..cb3cec2 100644
--- a/lib/src/runner/loader.dart
+++ b/lib/src/runner/loader.dart
@@ -223,8 +223,7 @@
       var runtime = findRuntime(runtimeName);
       assert(runtime != null, 'Unknown platform "$runtimeName".');
 
-      var platform =
-          new SuitePlatform(runtime, os: runtime.isBrowser ? null : currentOS);
+      var platform = new SuitePlatform.current(runtime);
       if (!suiteConfig.metadata.testOn.evaluate(platform)) {
         continue;
       }
diff --git a/lib/src/util/io.dart b/lib/src/util/io.dart
index 90fd561..c2883d2 100644
--- a/lib/src/util/io.dart
+++ b/lib/src/util/io.dart
@@ -22,6 +22,9 @@
 /// stdout.
 const _defaultLineLength = 200;
 
+/// Whether the test runner is running on Google-internal infrastructure.
+final bool inGoogle = Platform.version.contains("(google3)");
+
 /// The maximum line length for output.
 final int lineLength = () {
   try {
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 60d55e5..bb5f785 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -10,12 +10,14 @@
 import 'package:async/async.dart';
 import 'package:collection/collection.dart';
 import 'package:matcher/matcher.dart';
-import 'package:path/path.dart' as p;
 import 'package:stream_channel/stream_channel.dart';
 import 'package:term_glyph/term_glyph.dart' as glyph;
 
 import 'backend/invoker.dart';
 import 'backend/operating_system.dart';
+// Prefix this import to avoid accidentally using IO stuff in cross-platform
+// contexts.
+import 'util/io.dart' as io;
 
 /// A typedef for a possibly-asynchronous function.
 ///
@@ -49,24 +51,16 @@
 /// A regular expression matching a single vowel.
 final _vowel = new RegExp('[aeiou]');
 
-/// Directories that are specific to OS X.
-///
-/// This is used to try to distinguish OS X and Linux in [currentOSGuess].
-final _macOSDirectories = new Set<String>.from(
-    ["/Applications", "/Library", "/Network", "/System", "/Users"]);
-
-/// Returns the best guess for the current operating system without using
+/// Returns the best guess for the current operating system without relying on
 /// `dart:io`.
 ///
 /// This is useful for running test files directly and skipping tests as
-/// appropriate. The only OS-specific information we have is the current path,
-/// which we try to use to figure out the OS.
-final OperatingSystem currentOSGuess = (() {
-  if (p.style == p.Style.url) return OperatingSystem.none;
-  if (p.style == p.Style.windows) return OperatingSystem.windows;
-  if (_macOSDirectories.any(p.current.startsWith)) return OperatingSystem.macOS;
-  return OperatingSystem.linux;
-})();
+/// appropriate.
+final currentOSGuess = _ifSupported(() => io.currentOS, OperatingSystem.none);
+
+/// Returns the best guess for whether we're running on internal Google
+/// infrastructure without relying on `dart:io`.
+final inGoogleGuess = _ifSupported(() => io.inGoogle, false);
 
 /// A regular expression matching a hyphenated identifier.
 ///
@@ -96,6 +90,16 @@
   int get hashCode => first.hashCode ^ last.hashCode;
 }
 
+/// Returns [callback]'s return value, unless it throws an [UnsupportedError] in
+/// which case returns [fallback].
+T _ifSupported<T>(T callback(), T fallback) {
+  try {
+    return callback();
+  } on UnsupportedError {
+    return fallback;
+  }
+}
+
 /// Get a string description of an exception.
 ///
 /// Many exceptions include the exception class name at the beginning of their
diff --git a/lib/test.dart b/lib/test.dart
index cc84803..9dbafe3 100644
--- a/lib/test.dart
+++ b/lib/test.dart
@@ -62,7 +62,8 @@
         const PluginEnvironment(),
         SuiteConfiguration.empty,
         _globalDeclarer.build(),
-        new SuitePlatform(Runtime.vm, os: currentOSGuess),
+        new SuitePlatform(Runtime.vm,
+            os: currentOSGuess, inGoogle: inGoogleGuess),
         path: p.prettyUri(Uri.base));
 
     var engine = new Engine();
diff --git a/pubspec.yaml b/pubspec.yaml
index 3d4adc3..4e3ee3c 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: test
-version: 0.12.32-dev
+version: 0.12.32
 author: Dart Team <misc@dartlang.org>
 description: A library for writing dart unit tests.
 homepage: https://github.com/dart-lang/test