diff --git a/lib/src/runner.dart b/lib/src/runner.dart
index 3a5ce38c3f3e676ff762839027522054401c1d52..f6a66d44380b6acb1fded72b4da28842d1c2a7c2 100644
--- a/lib/src/runner.dart
+++ b/lib/src/runner.dart
@@ -244,7 +244,7 @@ class Runner {
       return loadSuite.changeSuite((suite) {
         _warnForUnknownTags(suite);
 
-        return suite.filter((test) {
+        return _shardSuite(suite.filter((test) {
           // Skip any tests that don't match all the given patterns.
           if (!_config.patterns.every(test.name.contains)) {
             return false;
@@ -257,7 +257,7 @@ class Runner {
           if (_config.excludeTags.evaluate(test.metadata.tags)) return false;
 
           return true;
-        });
+        }));
       });
     });
   }
@@ -337,6 +337,29 @@ class Runner {
     return 'the suite itself';
   }
 
+  /// If sharding is enabled, filters [suite] to only include the tests that
+  /// should be run in this shard.
+  ///
+  /// We just take a slice of the tests in each suite corresponding to the shard
+  /// index. This makes the tests pretty tests across shards, and since the
+  /// tests are continuous, makes us more likely to be able to re-use
+  /// `setUpAll()` logic.
+  Suite _shardSuite(Suite suite) {
+    if (_config.totalShards == null) return suite;
+
+    var shardSize = suite.group.testCount / _config.totalShards;
+    var shardStart = (shardSize * _config.shardIndex).round();
+    var shardEnd = (shardSize * (_config.shardIndex + 1)).round();
+
+    var count = -1;
+    var filtered = suite.filter((test) {
+      count++;
+      return count >= shardStart && count < shardEnd;
+    });
+
+    return filtered;
+  }
+
   /// Loads each suite in [suites] in order, pausing after load for platforms
   /// that support debugging.
   Future<bool> _loadThenPause(Stream<LoadSuite> suites) async {
diff --git a/lib/src/runner/configuration.dart b/lib/src/runner/configuration.dart
index bf21b0932a4f8888e37fd34ecb344bc013958e47..5f9689a8ebdb37d609bfbc431d670ef4ef5ede3c 100644
--- a/lib/src/runner/configuration.dart
+++ b/lib/src/runner/configuration.dart
@@ -92,6 +92,27 @@ class Configuration {
       pauseAfterLoad ? 1 : (_concurrency ?? defaultConcurrency);
   final int _concurrency;
 
+  /// The index of the current shard, if sharding is in use, or `null` if it's
+  /// not.
+  ///
+  /// Sharding is a technique that allows the Google internal test framework to
+  /// easily split a test run across multiple workers without requiring the
+  /// tests to be modified by the user. When sharding is in use, the runner gets
+  /// a shard index (this field) and a total number of shards, and is expected
+  /// to provide the following guarantees:
+  ///
+  /// * Running the same invocation of the runner, with the same shard index and
+  ///   total shards, will run the same set of tests.
+  /// * Across all shards, each test must be run exactly once.
+  ///
+  /// In addition, tests should be balanced across shards as much as possible.
+  final int shardIndex;
+
+  /// The total number of shards, if sharding is in use, or `null` if it's not.
+  ///
+  /// See [shardIndex] for details.
+  final int totalShards;
+
   /// The paths from which to load tests.
   List<String> get paths => _paths ?? ["test"];
   final List<String> _paths;
@@ -249,6 +270,8 @@ class Configuration {
       String reporter,
       int pubServePort,
       int concurrency,
+      int shardIndex,
+      int totalShards,
       Timeout timeout,
       Iterable<Pattern> patterns,
       Iterable<TestPlatform> platforms,
@@ -275,6 +298,8 @@ class Configuration {
         reporter: reporter,
         pubServePort: pubServePort,
         concurrency: concurrency,
+        shardIndex: shardIndex,
+        totalShards: totalShards,
         timeout: timeout,
         patterns: patterns,
         platforms: platforms,
@@ -339,6 +364,8 @@ class Configuration {
           String reporter,
           int pubServePort,
           int concurrency,
+          this.shardIndex,
+          this.totalShards,
           Timeout timeout,
           Iterable<Pattern> patterns,
           Iterable<TestPlatform> platforms,
@@ -385,6 +412,14 @@ class Configuration {
           "filename's context must match the current operating system, was "
               "${_filename.context.style}.");
     }
+
+    if ((shardIndex == null) != (totalShards == null)) {
+      throw new ArgumentError(
+          "shardIndex and totalShards may only be passed together.");
+    } else if (shardIndex != null) {
+      RangeError.checkValueInInterval(
+          shardIndex, 0, totalShards - 1, "shardIndex");
+    }
   }
 
   /// Returns a [input] as an unmodifiable list or `null`.
@@ -427,6 +462,8 @@ class Configuration {
         reporter: other._reporter ?? _reporter,
         pubServePort: (other.pubServeUrl ?? pubServeUrl)?.port,
         concurrency: other._concurrency ?? _concurrency,
+        shardIndex: other.shardIndex ?? shardIndex,
+        totalShards: other.totalShards ?? totalShards,
         timeout: timeout.merge(other.timeout),
         patterns: patterns.union(other.patterns),
         platforms: other._platforms ?? _platforms,
@@ -464,6 +501,8 @@ class Configuration {
       String reporter,
       int pubServePort,
       int concurrency,
+      int shardIndex,
+      int totalShards,
       Timeout timeout,
       Iterable<Pattern> patterns,
       Iterable<TestPlatform> platforms,
@@ -490,6 +529,8 @@ class Configuration {
         reporter: reporter ?? _reporter,
         pubServePort: pubServePort ?? pubServeUrl?.port,
         concurrency: concurrency ?? _concurrency,
+        shardIndex: shardIndex ?? this.shardIndex,
+        totalShards: totalShards ?? this.totalShards,
         timeout: timeout ?? this.timeout,
         patterns: patterns ?? this.patterns,
         platforms: platforms ?? _platforms,
diff --git a/lib/src/runner/configuration/args.dart b/lib/src/runner/configuration/args.dart
index 38a200d3a68f8530bd57f6c01aa56ba04be6191c..3f6b66f48d1e51bcab2809ca7cfc7272af5dcdfa 100644
--- a/lib/src/runner/configuration/args.dart
+++ b/lib/src/runner/configuration/args.dart
@@ -85,6 +85,12 @@ final ArgParser _parser = (() {
           'Currently only supported for browser tests.',
       negatable: false);
 
+  // These are used by the internal Google test runner, so they're hidden from
+  // the --help output but still supported as stable API surface. See
+  // [Configuration.shardIndex] for details on their semantics.
+  parser.addOption("shard-index", hide: true);
+  parser.addOption("total-shards", hide: true);
+
   parser.addSeparator("======== Output");
   parser.addOption("reporter",
       abbr: 'r',
@@ -146,6 +152,20 @@ class _Parser {
       return selector.union(tagSelector);
     });
 
+    var shardIndex = _parseOption('shard-index', int.parse);
+    var totalShards = _parseOption('total-shards', int.parse);
+    if ((shardIndex == null) != (totalShards == null)) {
+      throw new FormatException(
+          "--shard-index and --total-shards may only be passed together.");
+    } else if (shardIndex != null) {
+      if (shardIndex < 0) {
+        throw new FormatException("--shard-index may not be negative.");
+      } else if (shardIndex >= totalShards) {
+        throw new FormatException(
+            "--shard-index must be less than --total-shards.");
+      }
+    }
+
     return new Configuration(
         help: _ifParsed('help'),
         version: _ifParsed('version'),
@@ -157,6 +177,8 @@ class _Parser {
         reporter: _ifParsed('reporter'),
         pubServePort: _parseOption('pub-serve', int.parse),
         concurrency: _parseOption('concurrency', int.parse),
+        shardIndex: shardIndex,
+        totalShards: totalShards,
         timeout: _parseOption('timeout', (value) => new Timeout.parse(value)),
         patterns: patterns,
         platforms: _ifParsed('platform')?.map(TestPlatform.find),
diff --git a/test/runner/configuration/configuration_test.dart b/test/runner/configuration/configuration_test.dart
index 72b9834cf251eaa08dd415df94765c70d140e28a..cb0e0e0cfc7b635cd7ddb9a3e820ef317290978a 100644
--- a/test/runner/configuration/configuration_test.dart
+++ b/test/runner/configuration/configuration_test.dart
@@ -27,6 +27,8 @@ void main() {
         expect(merged.skipReason, isNull);
         expect(merged.pauseAfterLoad, isFalse);
         expect(merged.color, equals(canUseSpecialChars));
+        expect(merged.shardIndex, isNull);
+        expect(merged.totalShards, isNull);
         expect(merged.packageRoot, equals(p.join(p.current, 'packages')));
         expect(merged.reporter, equals(defaultReporter));
         expect(merged.pubServeUrl, isNull);
@@ -44,6 +46,8 @@ void main() {
                 skipReason: "boop",
                 pauseAfterLoad: true,
                 color: true,
+                shardIndex: 3,
+                totalShards: 10,
                 packageRoot: "root",
                 reporter: "json",
                 pubServePort: 1234,
@@ -59,6 +63,8 @@ void main() {
         expect(merged.skipReason, equals("boop"));
         expect(merged.pauseAfterLoad, isTrue);
         expect(merged.color, isTrue);
+        expect(merged.shardIndex, equals(3));
+        expect(merged.totalShards, equals(10));
         expect(merged.packageRoot, equals("root"));
         expect(merged.reporter, equals("json"));
         expect(merged.pubServeUrl.port, equals(1234));
@@ -76,6 +82,8 @@ void main() {
             skipReason: "boop",
             pauseAfterLoad: true,
             color: true,
+            shardIndex: 3,
+            totalShards: 10,
             packageRoot: "root",
             reporter: "json",
             pubServePort: 1234,
@@ -90,6 +98,8 @@ void main() {
         expect(merged.skipReason, equals("boop"));
         expect(merged.pauseAfterLoad, isTrue);
         expect(merged.color, isTrue);
+        expect(merged.shardIndex, equals(3));
+        expect(merged.totalShards, equals(10));
         expect(merged.packageRoot, equals("root"));
         expect(merged.reporter, equals("json"));
         expect(merged.pubServeUrl.port, equals(1234));
@@ -108,6 +118,8 @@ void main() {
             skipReason: "foo",
             pauseAfterLoad: true,
             color: false,
+            shardIndex: 2,
+            totalShards: 4,
             packageRoot: "root",
             reporter: "json",
             pubServePort: 1234,
@@ -122,6 +134,8 @@ void main() {
             skipReason: "bar",
             pauseAfterLoad: false,
             color: true,
+            shardIndex: 3,
+            totalShards: 10,
             packageRoot: "boot",
             reporter: "compact",
             pubServePort: 5678,
@@ -136,6 +150,8 @@ void main() {
         expect(merged.skipReason, equals("bar"));
         expect(merged.pauseAfterLoad, isFalse);
         expect(merged.color, isTrue);
+        expect(merged.shardIndex, equals(3));
+        expect(merged.totalShards, equals(10));
         expect(merged.packageRoot, equals("boot"));
         expect(merged.reporter, equals("compact"));
         expect(merged.pubServeUrl.port, equals(5678));
diff --git a/test/runner/shard_test.dart b/test/runner/shard_test.dart
new file mode 100644
index 0000000000000000000000000000000000000000..541687c0447827cd058c0da3667bcbc86a408b61
--- /dev/null
+++ b/test/runner/shard_test.dart
@@ -0,0 +1,169 @@
+// 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 file.
+
+@TestOn("vm")
+
+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/exit_codes.dart' as exit_codes;
+
+import '../io.dart';
+
+void main() {
+  useSandbox();
+
+  test("divides all the tests among the available shards", () {
+    d.file("test.dart", """
+      import 'package:test/test.dart';
+
+      void main() {
+        test("test 1", () {});
+        test("test 2", () {});
+        test("test 3", () {});
+        test("test 4", () {});
+        test("test 5", () {});
+        test("test 6", () {});
+        test("test 7", () {});
+        test("test 8", () {});
+        test("test 9", () {});
+        test("test 10", () {});
+      }
+    """).create();
+
+    var test = runTest(["test.dart", "--shard-index=0", "--total-shards=3"]);
+    test.stdout.expect(containsInOrder([
+      "+0: test 1",
+      "+1: test 2",
+      "+2: test 3",
+      "+3: All tests passed!"
+    ]));
+    test.shouldExit(0);
+
+    test = runTest(["test.dart", "--shard-index=1", "--total-shards=3"]);
+    test.stdout.expect(containsInOrder([
+      "+0: test 4",
+      "+1: test 5",
+      "+2: test 6",
+      "+3: test 7",
+      "+4: All tests passed!"
+    ]));
+    test.shouldExit(0);
+
+    test = runTest(["test.dart", "--shard-index=2", "--total-shards=3"]);
+    test.stdout.expect(containsInOrder([
+      "+0: test 8",
+      "+1: test 9",
+      "+2: test 10",
+      "+3: All tests passed!"
+    ]));
+    test.shouldExit(0);
+  });
+
+  test("shards each suite", () {
+    d.file("1_test.dart", """
+      import 'package:test/test.dart';
+
+      void main() {
+        test("test 1.1", () {});
+        test("test 1.2", () {});
+        test("test 1.3", () {});
+      }
+    """).create();
+
+    d.file("2_test.dart", """
+      import 'package:test/test.dart';
+
+      void main() {
+        test("test 2.1", () {});
+        test("test 2.2", () {});
+        test("test 2.3", () {});
+      }
+    """).create();
+
+    var test = runTest([".", "--shard-index=0", "--total-shards=3"]);
+    test.stdout.expect(inOrder([
+      either(containsInOrder([
+        "+0: ./1_test.dart: test 1.1",
+        "+1: ./2_test.dart: test 2.1"
+      ]), containsInOrder([
+        "+0: ./2_test.dart: test 2.1",
+        "+1: ./1_test.dart: test 1.1"
+      ])),
+      contains("+2: All tests passed!")
+    ]));
+    test.shouldExit(0);
+
+
+    test = runTest([".", "--shard-index=1", "--total-shards=3"]);
+    test.stdout.expect(inOrder([
+      either(containsInOrder([
+        "+0: ./1_test.dart: test 1.2",
+        "+1: ./2_test.dart: test 2.2"
+      ]), containsInOrder([
+        "+0: ./2_test.dart: test 2.2",
+        "+1: ./1_test.dart: test 1.2"
+      ])),
+      contains("+2: All tests passed!")
+    ]));
+    test.shouldExit(0);
+
+    test = runTest([".", "--shard-index=2", "--total-shards=3"]);
+    test.stdout.expect(inOrder([
+      either(containsInOrder([
+        "+0: ./1_test.dart: test 1.3",
+        "+1: ./2_test.dart: test 2.3"
+      ]), containsInOrder([
+        "+0: ./2_test.dart: test 2.3",
+        "+1: ./1_test.dart: test 1.3"
+      ])),
+      contains("+2: All tests passed!")
+    ]));
+    test.shouldExit(0);
+  });
+
+  test("an empty shard reports success", () {
+    d.file("test.dart", """
+      import 'package:test/test.dart';
+
+      void main() {
+        test("test 1", () {});
+        test("test 2", () {});
+      }
+    """).create();
+
+    var test = runTest(["test.dart", "--shard-index=1", "--total-shards=3"]);
+    test.stdout.expect(consumeThrough("No tests ran."));
+    test.shouldExit(0);
+  });
+
+  group("reports an error if", () {
+    test("--shard-index is provided alone", () {
+      var test = runTest(["--shard-index=1"]);
+      test.stderr.expect(
+          "--shard-index and --total-shards may only be passed together.");
+      test.shouldExit(exit_codes.usage);
+    });
+
+    test("--total-shards is provided alone", () {
+      var test = runTest(["--total-shards=5"]);
+      test.stderr.expect(
+          "--shard-index and --total-shards may only be passed together.");
+      test.shouldExit(exit_codes.usage);
+    });
+
+    test("--shard-index is negative", () {
+      var test = runTest(["--shard-index=-1", "--total-shards=5"]);
+      test.stderr.expect("--shard-index may not be negative.");
+      test.shouldExit(exit_codes.usage);
+    });
+
+    test("--shard-index is equal to --total-shards", () {
+      var test = runTest(["--shard-index=5", "--total-shards=5"]);
+      test.stderr.expect("--shard-index must be less than --total-shards.");
+      test.shouldExit(exit_codes.usage);
+    });
+  });
+}