Include osx and windows testing on Travis (#2299)

This fixes a number of tests that failed on windows, and also two real bug fixes:

- `pub global install` now prints
  precompiling package:binary instead of precompiling package:bin\binary on windows
- #2122
 This is done by always using / for relative paths in pubspec lock path-dependencies
diff --git a/.travis.yml b/.travis.yml
index 655a174..6778d50 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,12 +4,28 @@
 dart:
   - dev
 
+os:
+  - linux
+  - osx
+  - windows
+
+addons:
+  homebrew:
+    packages:
+    - coreutils
+
+env:
+  global:
+    - SPLIT=`which gsplit || which split`
+
 dart_task:
-  - test: --preset travis --total-shards 5 --shard-index 0
-  - test: --preset travis --total-shards 5 --shard-index 1
-  - test: --preset travis --total-shards 5 --shard-index 2
-  - test: --preset travis --total-shards 5 --shard-index 3
-  - test: --preset travis --total-shards 5 --shard-index 4
+  - test: --preset travis `$SPLIT -n l/1/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/2/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/3/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/4/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/5/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/6/7 .dart_tool/test_files`
+  - test: --preset travis `$SPLIT -n l/7/7 .dart_tool/test_files`
   - dartfmt
   - dartanalyzer: --fatal-infos --fatal-warnings .
 
@@ -17,6 +33,8 @@
 # snapshot if it's available.
 before_script:
   - dart --snapshot=bin/pub.dart.snapshot.dart2 bin/pub.dart
+  - find test -name "*_test\\.dart" | sort > .dart_tool/test_files
+  -
 
 # Only building these branches means that we don't run two builds for each pull
 # request.
diff --git a/lib/src/git.dart b/lib/src/git.dart
index 96f36b8..6fa0256 100644
--- a/lib/src/git.dart
+++ b/lib/src/git.dart
@@ -21,10 +21,20 @@
   /// The standard error emitted by git.
   final String stderr;
 
-  @override
-  String get message => 'Git error. Command: git ${args.join(" ")}\n$stderr';
+  /// The standard out emitted by git.
+  final String stdout;
 
-  GitException(Iterable<String> args, this.stderr) : args = args.toList();
+  /// The error code
+  final int exitCode;
+
+  @override
+  String get message => 'Git error. Command: `git ${args.join(' ')}`\n'
+      'stdout: $stdout\n'
+      'stderr: $stderr\n'
+      'exit code: $exitCode';
+
+  GitException(Iterable<String> args, this.stdout, this.stderr, this.exitCode)
+      : args = args.toList();
 
   @override
   String toString() => message;
@@ -48,7 +58,10 @@
   try {
     var result = await runProcess(command, args,
         workingDir: workingDir, environment: environment);
-    if (!result.success) throw GitException(args, result.stderr.join('\n'));
+    if (!result.success) {
+      throw GitException(args, result.stdout.join('\n'),
+          result.stderr.join('\n'), result.exitCode);
+    }
     return result.stdout;
   } finally {
     log.unmuteProgress();
@@ -65,7 +78,11 @@
 
   var result = runProcessSync(command, args,
       workingDir: workingDir, environment: environment);
-  if (!result.success) throw GitException(args, result.stderr.join('\n'));
+  if (!result.success) {
+    throw GitException(args, result.stdout.join('\n'), result.stderr.join('\n'),
+        result.exitCode);
+  }
+
   return result.stdout;
 }
 
diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart
index c71e81c..71053d6 100644
--- a/lib/src/global_packages.dart
+++ b/lib/src/global_packages.dart
@@ -263,7 +263,7 @@
         var snapshotPath = p.join(binDir, '$basename.snapshot.dart2');
         await dart.snapshot(url, snapshotPath,
             packagesFile: p.toUri(_getPackagesFilePath(package.name)),
-            name: '${package.name}:${p.url.basenameWithoutExtension(path)}');
+            name: '${package.name}:${p.basenameWithoutExtension(path)}');
         precompiled[p.withoutExtension(basename)] = snapshotPath;
       }));
       return precompiled;
diff --git a/lib/src/source/path.dart b/lib/src/source/path.dart
index ab9742c..7ee64bc 100644
--- a/lib/src/source/path.dart
+++ b/lib/src/source/path.dart
@@ -128,13 +128,21 @@
   dynamic serializeDescription(String containingPath, description) {
     if (description['relative']) {
       return {
-        'path': p.relative(description['path'], from: containingPath),
+        'path': relativePathWithPosixSeperators(
+            p.relative(description['path'], from: containingPath)),
         'relative': true
       };
     }
     return description;
   }
 
+  /// On both Windows and linux we prefer `/` in the pubspec.lock for relative
+  /// paths.
+  static String relativePathWithPosixSeperators(String path) {
+    assert(p.isRelative(path));
+    return p.posix.joinAll(p.split(path));
+  }
+
   /// Converts a parsed relative path to its original relative form.
   @override
   String formatDescription(description) {
diff --git a/test/cache/repair/git_test.dart b/test/cache/repair/git_test.dart
index 1f54e3f..c6c2db2 100644
--- a/test/cache/repair/git_test.dart
+++ b/test/cache/repair/git_test.dart
@@ -185,7 +185,7 @@
             contains('Failed to load package:'),
             contains('Could not find a file named "pubspec.yaml" in '),
             contains('foo-'),
-            contains('/subdir'),
+            contains('${path.separator}subdir'),
           ]),
           output: allOf([
             startsWith('Failed to reinstall 2 packages:'),
diff --git a/test/descriptor/git.dart b/test/descriptor/git.dart
index b3fe8d6..53101f9 100644
--- a/test/descriptor/git.dart
+++ b/test/descriptor/git.dart
@@ -4,6 +4,7 @@
 
 import 'dart:async';
 
+import 'dart:io';
 import 'package:path/path.dart' as path;
 import 'package:pub/src/git.dart' as git;
 import 'package:test_descriptor/test_descriptor.dart';
@@ -19,7 +20,11 @@
     await super.create(parent);
     await _runGitCommands(parent, [
       ['init'],
-      ['config', 'core.excludesfile', ''],
+      [
+        'config', 'core.excludesfile',
+        // TODO(sigurdm): This works around https://github.com/dart-lang/sdk/issues/40060
+        Platform.isWindows ? '""' : ''
+      ],
       ['add', '.'],
       ['commit', '-m', 'initial commit', '--allow-empty']
     ]);
diff --git a/test/descriptor/packages.dart b/test/descriptor/packages.dart
index 8ba6157..54ed6a3 100644
--- a/test/descriptor/packages.dart
+++ b/test/descriptor/packages.dart
@@ -44,7 +44,7 @@
         } else {
           // Otherwise it's a path relative to the pubspec file,
           // which is also relative to the .packages file.
-          packagePath = p.fromUri(version);
+          packagePath = version;
         }
         mapping[package] = p.toUri(p.join(packagePath, 'lib', ''));
       });
@@ -78,7 +78,7 @@
               'Expected $description, found location: ${map[package]}.');
         }
       } else {
-        var expected = p.normalize(p.join(p.fromUri(description), 'lib'));
+        var expected = p.normalize(p.join(description, 'lib'));
         var actual = p.normalize(p.fromUri(
             p.url.relative(map[package].toString(), from: p.dirname(_base))));
 
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index d9a75b3..44cec94 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:io';
 
 import 'package:path/path.dart' as p;
 import 'package:test/test.dart';
@@ -422,8 +423,8 @@
       ]).create();
 
       await expectResolves(error: equalsIgnoringWhitespace('''
-        Because myapp depends on both foo from path foo and foo from path
-          ../foo, version solving failed.
+      Because myapp depends on both foo from path foo and foo from path
+          ..${Platform.pathSeparator}foo, version solving failed.
       '''));
     });
   });