Give the benchmark some love.

- Get rid of a bunch of duplicate code.
- Fix crash in relative(from:) test.
- Add some normal looking paths to try to get more real-world results.
- Other tweaks.

R=nweiz@google.com

Review URL: https://codereview.chromium.org//1507143002 .
diff --git a/benchmark/benchmark.dart b/benchmark/benchmark.dart
index 4285e65..183b921 100644
--- a/benchmark/benchmark.dart
+++ b/benchmark/benchmark.dart
@@ -2,64 +2,59 @@
 // 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 '../lib/path.dart' as path;
+import 'package:path/path.dart' as p;
 
-void runBenchmark(String name, Function func, List files) {
-  // Warmup.
-  for (int i = 0; i < 10000; i++) {
-    for (var p in files) {
-      func(p);
-    }
-  }
-  var count = 100000;
-  var sw = new Stopwatch()..start();
-  for (int i = 0; i < count; i++) {
-    for (var p in files) {
-      func(p);
-    }
-  }
-  print("$name: ${count / sw.elapsedMicroseconds} iter/us (${sw.elapsed})");
-}
+/// Some hopefully real-world representative platform-independent paths.
+const genericPaths = const [
+  '.',
+  '..',
+  'out/ReleaseIA32/packages',
+  'lib',
+  'lib/src/',
+  'lib/src/style/url.dart',
+  'test/./not/.././normalized',
+  'benchmark/really/long/path/with/many/components.dart',
+];
 
-void runBenchmarkTwoArgs(String name, Function func, List files) {
-  // Warmup.
-  for (int i = 0; i < 1000; i++) {
-    for (var file1 in files) {
-      for (var file2 in files) {
-        func(file1, file2);
-      }
-    }
-  }
+/// Some platform-specific paths.
+final platformPaths = {
+  p.Style.posix: [
+    '/',
+    '/home/user/dart/sdk/lib/indexed_db/dart2js/indexed_db_dart2js.dart',
+  ],
+  p.Style.url: ['https://example.server.org/443643002/path?top=yes#fragment',],
+  p.Style.windows: [
+    r'C:\User\me\',
+    r'\\server\share\my\folders\some\file.data',
+  ],
+};
 
-  var count = 10000;
-  var sw = new Stopwatch()..start();
-  for (int i = 0; i < count; i++) {
-    for (var file1 in files) {
-      for (var file2 in files) {
-        func(file1, file2);
-      }
-    }
-  }
-  print("$name: ${count / sw.elapsedMicroseconds} iter/us (${sw.elapsed})");
-}
+/// The command line arguments passed to this script.
+List<String> arguments;
 
-main(args) {
-  for (var style in [path.Style.posix, path.Style.url, path.Style.windows]) {
-    var context = new path.Context(style: style);
-    var files = COMMON_PATHS.toList()..addAll(STYLE_PATHS[style]);
+void main(List<String> args) {
+  arguments = args;
 
-    benchmark(name, func) {
-      name = style.name + '-' + name;
-      if (args.isEmpty || args.any((arg) => name.contains(arg))) {
-        runBenchmark(name, func, files);
-      }
+  for (var style in [p.Style.posix, p.Style.url, p.Style.windows]) {
+    var context = new p.Context(style: style);
+    var files = genericPaths.toList()..addAll(platformPaths[style]);
+
+    benchmark(name, function) {
+      runBenchmark("${style.name}-$name", 100000, () {
+        for (var file in files) {
+          function(file);
+        }
+      });
     }
 
-    benchmarkTwoArgs(name, func) {
-      name = style.name + '-' + name + '-two';
-      if (args.isEmpty || args.any((arg) => name.contains(arg))) {
-        runBenchmarkTwoArgs(name, func, files);
-      }
+    benchmarkPairs(name, function) {
+      runBenchmark("${style.name}-$name", 1000, () {
+        for (var file1 in files) {
+          for (var file2 in files) {
+            function(file1, file2);
+          }
+        }
+      });
     }
 
     benchmark('absolute', context.absolute);
@@ -73,28 +68,37 @@
     benchmark('isRootRelative', context.isRootRelative);
     benchmark('normalize', context.normalize);
     benchmark('relative', context.relative);
-    benchmarkTwoArgs('relative', context.relative);
+    benchmarkPairs('relative from', (file, from) {
+      try {
+        return context.relative(file, from: from);
+      } on p.PathException {
+        // Do nothing.
+      }
+    });
     benchmark('toUri', context.toUri);
     benchmark('prettyUri', context.prettyUri);
-    benchmarkTwoArgs('isWithin', context.isWithin);
+    benchmarkPairs('isWithin', context.isWithin);
   }
 
-  if (args.isEmpty || args.any((arg) => arg == 'current')) {
-    runBenchmark('current', (_) => path.current, [null]);
-  }
+  runBenchmark('current', 100000, () => p.current);
 }
 
-const COMMON_PATHS = const ['.', '..', 'out/ReleaseIA32/packages'];
+void runBenchmark(String name, int count, Function function) {
+  // If names are passed on the command-line, they select which benchmarks are
+  // run.
+  if (arguments.isNotEmpty && !arguments.contains(name)) return;
 
-final STYLE_PATHS = {
-  path.Style.posix: [
-    '/home/user/dart/sdk/lib/indexed_db/dart2js/indexed_db_dart2js.dart',
-  ],
-  path.Style.url: [
-    'https://example.server.org/443643002/path?top=yes#fragment',
-  ],
-  path.Style.windows: [
-    r'C:\User\me\',
-    r'\\server\share\my\folders\some\file.data',
-  ],
-};
+  // Warmup.
+  for (var i = 0; i < 10000; i++) {
+    function();
+  }
+
+  var stopwatch = new Stopwatch()..start();
+  for (var i = 0; i < count; i++) {
+    function();
+  }
+
+  var rate =
+      (count / stopwatch.elapsedMicroseconds).toStringAsFixed(5).padLeft(9);
+  print("${name.padLeft(32)}: $rate iter/us (${stopwatch.elapsed})");
+}