From b88ebdc48a018a0212ca6919bce1e3a5048f9b68 Mon Sep 17 00:00:00 2001
From: "rnystrom@google.com" <rnystrom@google.com>
Date: Wed, 13 Feb 2013 18:47:19 +0000
Subject: [PATCH] Take Sam Elkhateeb's path for "path" dependencies and clean
 it up some.

BUG=http://code.google.com/p/dart/issues/detail?id=3732

Review URL: https://codereview.chromium.org//12208138

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge@18465 260f80e4-7a28-3924-810f-c04153c831b5
---
 lib/src/io.dart                             |   2 +-
 lib/src/path_source.dart                    |  78 +++++++
 lib/src/system_cache.dart                   |   4 +-
 lib/src/validator/dependency.dart           |   9 +-
 test/install/path/absolute_path_test.dart   |  46 +++++
 test/install/path/no_pubspec_test.dart      |  32 +++
 test/install/path/nonexistent_dir_test.dart |  31 +++
 test/install/path/path_is_file_test.dart    |  35 ++++
 test/install/path/relative_path_test.dart   |  25 +++
 test/test_pub.dart                          |  40 +++-
 test/validator_test.dart                    | 213 ++++++++++----------
 11 files changed, 394 insertions(+), 121 deletions(-)
 create mode 100644 lib/src/path_source.dart
 create mode 100644 test/install/path/absolute_path_test.dart
 create mode 100644 test/install/path/no_pubspec_test.dart
 create mode 100644 test/install/path/nonexistent_dir_test.dart
 create mode 100644 test/install/path/path_is_file_test.dart
 create mode 100644 test/install/path/relative_path_test.dart

diff --git a/lib/src/io.dart b/lib/src/io.dart
index 0eab87a9..65f0507f 100644
--- a/lib/src/io.dart
+++ b/lib/src/io.dart
@@ -315,7 +315,7 @@ Future<File> createSymlink(from, to) {
   from = _getPath(from);
   to = _getPath(to);
 
-  log.fine("Create symlink $from -> $to.");
+  log.fine("Creating symlink ($to is a symlink to $from)");
 
   var command = 'ln';
   var args = ['-s', from, to];
diff --git a/lib/src/path_source.dart b/lib/src/path_source.dart
new file mode 100644
index 00000000..123a3086
--- /dev/null
+++ b/lib/src/path_source.dart
@@ -0,0 +1,78 @@
+// Copyright (c) 2013, 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 path_source;
+
+import 'dart:async';
+import 'dart:io';
+
+import '../../pkg/path/lib/path.dart' as path;
+
+import 'io.dart';
+import 'package.dart';
+import 'pubspec.dart';
+import 'version.dart';
+import 'source.dart';
+import 'utils.dart';
+
+// TODO(rnystrom): Support relative paths. (See comment in _validatePath().)
+/// A package [Source] that installs packages from a given local file path.
+class PathSource extends Source {
+  final name = 'path';
+  final shouldCache = false;
+
+  Future<Pubspec> describe(PackageId id) {
+    return defer(() {
+      _validatePath(id.name, id.description);
+      return new Pubspec.load(id.name, id.description, systemCache.sources);
+    });
+  }
+
+  Future<bool> install(PackageId id, String path) {
+    return defer(() {
+      try {
+        _validatePath(id.name, id.description);
+      } on FormatException catch(err) {
+        return false;
+      }
+      return createPackageSymlink(id.name, id.description, path);
+    }).then((_) => true);
+  }
+
+  void validateDescription(description, {bool fromLockFile: false}) {
+    if (description is! String) {
+      throw new FormatException("The description must be a path string.");
+    }
+  }
+
+  /// Ensures that [dir] is a valid path. It must be an absolute path that
+  /// points to an existing directory. Throws a [FormatException] if the path
+  /// is invalid.
+  void _validatePath(String name, String dir) {
+    // Relative paths are not (currently) allowed because the user would expect
+    // them to be relative to the pubspec where the dependency appears. That in
+    // turn means that two pubspecs in different locations with the same
+    // relative path dependency could refer to two different packages. That
+    // violates pub's rule that a description should uniquely identify a
+    // package.
+    //
+    // At some point, we may want to loosen this, but it will mean tracking
+    // where a given PackageId appeared.
+    if (!path.isAbsolute(dir)) {
+      throw new FormatException(
+          "Path dependency for package '$name' must be an absolute path. "
+          "Was '$dir'.");
+    }
+
+    if (fileExists(dir)) {
+      throw new FormatException(
+          "Path dependency for package '$name' must refer to a "
+          "directory, not a file. Was '$dir'.");
+    }
+
+    if (!dirExists(dir)) {
+      throw new FormatException("Could not find package '$name' at '$dir'.");
+    }
+  }
+}
\ No newline at end of file
diff --git a/lib/src/system_cache.dart b/lib/src/system_cache.dart
index 13e6b8d6..fd431171 100644
--- a/lib/src/system_cache.dart
+++ b/lib/src/system_cache.dart
@@ -13,6 +13,7 @@ import 'io.dart';
 import 'io.dart' as io show createTempDir;
 import 'log.dart' as log;
 import 'package.dart';
+import 'path_source.dart';
 import 'pubspec.dart';
 import 'sdk_source.dart';
 import 'source.dart';
@@ -46,9 +47,10 @@ class SystemCache {
   /// Creates a system cache and registers the standard set of sources.
   factory SystemCache.withSources(String rootDir) {
     var cache = new SystemCache(rootDir);
-    cache.register(new SdkSource());
     cache.register(new GitSource());
     cache.register(new HostedSource());
+    cache.register(new PathSource());
+    cache.register(new SdkSource());
     cache.sources.setDefault('hosted');
     return cache;
   }
diff --git a/lib/src/validator/dependency.dart b/lib/src/validator/dependency.dart
index a848c545..de027385 100644
--- a/lib/src/validator/dependency.dart
+++ b/lib/src/validator/dependency.dart
@@ -10,6 +10,7 @@ import '../entrypoint.dart';
 import '../hosted_source.dart';
 import '../http.dart';
 import '../package.dart';
+import '../path_source.dart';
 import '../utils.dart';
 import '../validator.dart';
 import '../version.dart';
@@ -62,7 +63,13 @@ class DependencyValidator extends Validator {
         }
       }
 
-      warnings.add('Don\'t depend on "${ref.name}" from the ${ref.source.name} '
+      // Path sources are errors. Other sources are just warnings.
+      var messages = warnings;
+      if (ref.source is PathSource) {
+        messages = errors;
+      }
+
+      messages.add('Don\'t depend on "${ref.name}" from the ${ref.source.name} '
               'source. Use the hosted source instead. For example:\n'
           '\n'
           'dependencies:\n'
diff --git a/test/install/path/absolute_path_test.dart b/test/install/path/absolute_path_test.dart
new file mode 100644
index 00000000..cef0a760
--- /dev/null
+++ b/test/install/path/absolute_path_test.dart
@@ -0,0 +1,46 @@
+// Copyright (c) 2013, 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 '../../../../../pkg/path/lib/path.dart' as path;
+
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('path dependency with absolute path', () {
+    dir('foo', [
+      libDir('foo'),
+      libPubspec('foo', '0.0.1')
+    ]).scheduleCreate();
+
+    dir(appPath, [
+      pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": path.join(sandboxDir, "foo")}
+        }
+      })
+    ]).scheduleCreate();
+
+    schedulePub(args: ["install"],
+        output: new RegExp(r"Dependencies installed!$"));
+
+    dir(packagesPath, [
+      dir("foo", [
+        file("foo.dart", 'main() => "foo";')
+      ])
+    ]).scheduleValidate();
+
+    // Move the packages directory and ensure the symlink still works. That
+    // will validate that we actually created an absolute symlink.
+    dir("moved").scheduleCreate();
+    scheduleRename(packagesPath, "moved/packages");
+
+    dir("moved/packages", [
+      dir("foo", [
+        file("foo.dart", 'main() => "foo";')
+      ])
+    ]).scheduleValidate();
+  });
+}
\ No newline at end of file
diff --git a/test/install/path/no_pubspec_test.dart b/test/install/path/no_pubspec_test.dart
new file mode 100644
index 00000000..fc2cf4df
--- /dev/null
+++ b/test/install/path/no_pubspec_test.dart
@@ -0,0 +1,32 @@
+// Copyright (c) 2013, 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 '../../../../../pkg/path/lib/path.dart' as path;
+
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('path dependency to non-package directory', () {
+    // Make an empty directory.
+    dir('foo').scheduleCreate();
+    var fooPath = path.join(sandboxDir, "foo");
+
+    dir(appPath, [
+      pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": fooPath}
+        }
+      })
+    ]).scheduleCreate();
+
+    schedulePub(args: ['install'],
+        error:
+            new RegExp('Package "foo" doesn\'t have a pubspec.yaml file.'),
+        exitCode: 1);
+  });
+}
\ No newline at end of file
diff --git a/test/install/path/nonexistent_dir_test.dart b/test/install/path/nonexistent_dir_test.dart
new file mode 100644
index 00000000..324a37b3
--- /dev/null
+++ b/test/install/path/nonexistent_dir_test.dart
@@ -0,0 +1,31 @@
+// Copyright (c) 2013, 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 '../../../../../pkg/path/lib/path.dart' as path;
+
+import '../../../../pub/exit_codes.dart' as exit_codes;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('path dependency to non-existent directory', () {
+    var badPath = path.join(sandboxDir, "bad_path");
+
+    dir(appPath, [
+      pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": badPath}
+        }
+      })
+    ]).scheduleCreate();
+
+    schedulePub(args: ['install'],
+        error:
+            new RegExp("Could not find package 'foo' at '$badPath'."),
+        exitCode: exit_codes.DATA);
+  });
+}
\ No newline at end of file
diff --git a/test/install/path/path_is_file_test.dart b/test/install/path/path_is_file_test.dart
new file mode 100644
index 00000000..56218367
--- /dev/null
+++ b/test/install/path/path_is_file_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2013, 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 '../../../../../pkg/path/lib/path.dart' as path;
+
+import '../../../../pub/exit_codes.dart' as exit_codes;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('path dependency when path is a file', () {
+    dir('foo', [
+      libDir('foo'),
+      libPubspec('foo', '0.0.1')
+    ]).scheduleCreate();
+
+    file('dummy.txt', '').scheduleCreate();
+    var dummyPath = path.join(sandboxDir, 'dummy.txt');
+
+    dir(appPath, [
+      pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": dummyPath}
+        }
+      })
+    ]).scheduleCreate();
+
+    schedulePub(args: ['install'],
+        error: new RegExp("Path dependency for package 'foo' must refer to a "
+                          "directory, not a file. Was '$dummyPath'."),
+        exitCode: exit_codes.DATA);
+  });
+}
\ No newline at end of file
diff --git a/test/install/path/relative_path_test.dart b/test/install/path/relative_path_test.dart
new file mode 100644
index 00000000..56fe28a0
--- /dev/null
+++ b/test/install/path/relative_path_test.dart
@@ -0,0 +1,25 @@
+// Copyright (c) 2013, 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 '../../../../pub/exit_codes.dart' as exit_codes;
+import '../../test_pub.dart';
+
+main() {
+  initConfig();
+  integration('path dependencies cannot use relative paths', () {
+    dir(appPath, [
+      pubspec({
+        "name": "myapp",
+        "dependencies": {
+          "foo": {"path": "../foo"}
+        }
+      })
+    ]).scheduleCreate();
+
+    schedulePub(args: ['install'],
+        error: new RegExp("Path dependency for package 'foo' must be an "
+                          "absolute path. Was '../foo'."),
+        exitCode: exit_codes.DATA);
+  });
+}
\ No newline at end of file
diff --git a/test/test_pub.dart b/test/test_pub.dart
index b84a7aa6..eb895750 100644
--- a/test/test_pub.dart
+++ b/test/test_pub.dart
@@ -31,6 +31,7 @@ import '../../pub/git_source.dart';
 import '../../pub/hosted_source.dart';
 import '../../pub/http.dart';
 import '../../pub/io.dart';
+import '../../pub/path_source.dart';
 import '../../pub/sdk_source.dart';
 import '../../pub/system_cache.dart';
 import '../../pub/utils.dart';
@@ -404,6 +405,9 @@ Future<Map> _dependencyListToMap(List<Map> dependencies) {
       case "hosted":
         source = new HostedSource();
         break;
+      case "path":
+        source = new PathSource();
+        break;
       case "sdk":
         source = new SdkSource();
         break;
@@ -427,6 +431,8 @@ String _packageName(String sourceName, description) {
   case "hosted":
     if (description is String) return description;
     return description['name'];
+  case "path":
+    return basename(description);
   case "sdk":
     return description;
   default:
@@ -434,6 +440,10 @@ String _packageName(String sourceName, description) {
   }
 }
 
+/// The full path to the created sandbox directory for an integration test.
+String get sandboxDir => _sandboxDir.path;
+Directory _sandboxDir;
+
 /// The path of the package cache directory used for tests. Relative to the
 /// sandbox directory.
 final String cachePath = "cache";
@@ -488,27 +498,32 @@ void _integration(String description, void body(), [Function testFn]) {
       file('version', '0.1.2.3')
     ]).scheduleCreate();
 
+    _sandboxDir = createTempDir();
+
     // Schedule the test.
     body();
 
     // Run all of the scheduled tasks. If an error occurs, it will propagate
     // through the futures back up to here where we can hand it off to unittest.
     var asyncDone = expectAsync0(() {});
-    var sandboxDir = createTempDir();
-    return timeout(_runScheduled(sandboxDir, _scheduled),
+    return timeout(_runScheduled(_scheduled),
           _TIMEOUT, 'waiting for a test to complete').catchError((e) {
-      return _runScheduled(sandboxDir, _scheduledOnException).then((_) {
+      return _runScheduled(_scheduledOnException).then((_) {
         // Rethrow the original error so it keeps propagating.
         throw e;
       });
     }).whenComplete(() {
       // Clean up after ourselves. Do this first before reporting back to
       // unittest because it will advance to the next test immediately.
-      return _runScheduled(sandboxDir, _scheduledCleanup).then((_) {
+      return _runScheduled(_scheduledCleanup).then((_) {
         _scheduled = null;
         _scheduledCleanup = null;
         _scheduledOnException = null;
-        if (sandboxDir != null) return deleteDir(sandboxDir);
+        if (_sandboxDir != null) {
+          var dir = _sandboxDir;
+          _sandboxDir = null;
+          return deleteDir(dir);
+        }
       });
     }).then((_) {
       // If we got here, the test completed successfully so tell unittest so.
@@ -532,6 +547,14 @@ String get testDirectory {
   return getFullPath(dir);
 }
 
+/// Schedules renaming (moving) the directory at [from] to [to], both of which
+/// are assumed to be relative to [sandboxDir].
+void scheduleRename(String from, String to) {
+  _schedule((sandboxDir) {
+    return renameDir(join(sandboxDir, from), join(sandboxDir, to));
+  });
+}
+
 /// Schedules a call to the Pub command-line utility. Runs Pub with [args] and
 /// validates that its results match [output], [error], and [exitCode].
 void schedulePub({List args, Pattern output, Pattern error,
@@ -602,7 +625,6 @@ void confirmPublish(ScheduledProcess pub) {
   pub.writeLine("y");
 }
 
-
 /// Calls [fn] with appropriately modified arguments to run a pub process. [fn]
 /// should have the same signature as [startProcess], except that the returned
 /// [Future] may have a type other than [Process].
@@ -677,7 +699,7 @@ void useMockClient(MockClient client) {
   });
 }
 
-Future _runScheduled(Directory parentDir, List<_ScheduledEvent> scheduled) {
+Future _runScheduled(List<_ScheduledEvent> scheduled) {
   if (scheduled == null) return new Future.immediate(null);
   var iterator = scheduled.iterator;
 
@@ -688,7 +710,7 @@ Future _runScheduled(Directory parentDir, List<_ScheduledEvent> scheduled) {
       return new Future.immediate(null);
     }
 
-    var future = iterator.current(parentDir);
+    var future = iterator.current(_sandboxDir);
     if (future != null) {
       return future.then(runNextEvent);
     } else {
@@ -1169,7 +1191,7 @@ class NothingDescriptor extends Descriptor {
 typedef Validator ValidatorCreator(Entrypoint entrypoint);
 
 /// Schedules a single [Validator] to run on the [appPath]. Returns a scheduled
-/// Future that contains the erros and warnings produced by that validator.
+/// Future that contains the errors and warnings produced by that validator.
 Future<Pair<List<String>, List<String>>> schedulePackageValidation(
     ValidatorCreator fn) {
   return _scheduleValue((sandboxDir) {
diff --git a/test/validator_test.dart b/test/validator_test.dart
index b11d90f6..b64f1311 100644
--- a/test/validator_test.dart
+++ b/test/validator_test.dart
@@ -10,9 +10,10 @@ import 'dart:json' as json;
 import 'dart:math' as math;
 
 import 'test_pub.dart';
-import '../../../pkg/unittest/lib/unittest.dart';
 import '../../../pkg/http/lib/http.dart' as http;
 import '../../../pkg/http/lib/testing.dart';
+import '../../../pkg/path/lib/path.dart' as path;
+import '../../../pkg/unittest/lib/unittest.dart';
 import '../../pub/entrypoint.dart';
 import '../../pub/io.dart';
 import '../../pub/validator.dart';
@@ -38,6 +39,16 @@ void expectValidationWarning(ValidatorCreator fn) {
   expectLater(schedulePackageValidation(fn), pairOf(isEmpty, isNot(isEmpty)));
 }
 
+expectDependencyValidationError(String error) {
+  expectLater(schedulePackageValidation(dependency),
+      pairOf(someElement(contains(error)), isEmpty));
+}
+
+expectDependencyValidationWarning(String warning) {
+  expectLater(schedulePackageValidation(dependency),
+      pairOf(isEmpty, someElement(contains(warning))));
+}
+
 Validator compiledDartdoc(Entrypoint entrypoint) =>
   new CompiledDartdocValidator(entrypoint);
 
@@ -66,7 +77,31 @@ Validator utf8Readme(Entrypoint entrypoint) =>
 
 void scheduleNormalPackage() => normalPackage.scheduleCreate();
 
+/// Sets up a test package with dependency [dep] and mocks a server with
+/// [hostedVersions] of the package available.
+setUpDependency(Map dep, {List<String> hostedVersions}) {
+  useMockClient(new MockClient((request) {
+    expect(request.method, equals("GET"));
+    expect(request.url.path, equals("/packages/foo.json"));
+
+    if (hostedVersions == null) {
+      return new Future.immediate(new http.Response("not found", 404));
+    } else {
+      return new Future.immediate(new http.Response(json.stringify({
+        "name": "foo",
+        "uploaders": ["nweiz@google.com"],
+        "versions": hostedVersions
+      }), 200));
+    }
+  }));
+
+  dir(appPath, [
+    libPubspec("test_pkg", "1.0.0", deps: [dep])
+  ]).scheduleCreate();
+}
+
 main() {
+  initConfig();
   group('should consider a package valid if it', () {
     setUp(scheduleNormalPackage);
 
@@ -326,124 +361,88 @@ main() {
       expectValidationError(lib);
     });
 
-    group('has a dependency with a non-hosted source', () {
-      group('where a hosted version of that dependency exists', () {
-        integration("and should suggest the hosted package's primary "
-            "version", () {
-          useMockClient(new MockClient((request) {
-            expect(request.method, equals("GET"));
-            expect(request.url.path, equals("/packages/foo.json"));
-
-            return new Future.immediate(new http.Response(json.stringify({
-              "name": "foo",
-              "uploaders": ["nweiz@google.com"],
-              "versions": ["3.0.0-pre", "2.0.0", "1.0.0"]
-            }), 200));
-          }));
-
-          dir(appPath, [
-            libPubspec("test_pkg", "1.0.0", deps: [
-              {'git': 'git://github.com/dart-lang/foo'}
-            ])
-          ]).scheduleCreate();
-
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=2.0.0 <3.0.0"'))));
+    group('has a git dependency', () {
+      group('where a hosted version exists', () {
+        integration("and should suggest the hosted primary version", () {
+          setUpDependency({'git': 'git://github.com/dart-lang/foo'},
+              hostedVersions: ["3.0.0-pre", "2.0.0", "1.0.0"]);
+          expectDependencyValidationWarning('  foo: ">=2.0.0 <3.0.0"');
         });
 
-        integration("and should suggest the hosted package's prerelease "
-            "version if it's the only version available", () {
-          useMockClient(new MockClient((request) {
-            expect(request.method, equals("GET"));
-            expect(request.url.path, equals("/packages/foo.json"));
-
-            return new Future.immediate(new http.Response(json.stringify({
-              "name": "foo",
-              "uploaders": ["nweiz@google.com"],
-              "versions": ["3.0.0-pre", "2.0.0-pre"]
-            }), 200));
-          }));
+        integration("and should suggest the hosted prerelease version if "
+                    "it's the only version available", () {
+          setUpDependency({'git': 'git://github.com/dart-lang/foo'},
+              hostedVersions: ["3.0.0-pre", "2.0.0-pre"]);
+          expectDependencyValidationWarning('  foo: ">=3.0.0-pre <4.0.0"');
+        });
 
-          dir(appPath, [
-            libPubspec("test_pkg", "1.0.0", deps: [
-              {'git': 'git://github.com/dart-lang/foo'}
-            ])
-          ]).scheduleCreate();
+        integration("and should suggest a tighter constraint if primary is "
+                    "pre-1.0.0", () {
+          setUpDependency({'git': 'git://github.com/dart-lang/foo'},
+              hostedVersions: ["0.0.1", "0.0.2"]);
+          expectDependencyValidationWarning('  foo: ">=0.0.2 <0.0.3"');
+        });
+      });
 
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=3.0.0-pre <4.0.0"'))));
+      group('where no hosted version exists', () {
+        integration("and should use the other source's version", () {
+          setUpDependency({
+            'git': 'git://github.com/dart-lang/foo',
+            'version': '>=1.0.0 <2.0.0'
+          });
+          expectDependencyValidationWarning('  foo: ">=1.0.0 <2.0.0"');
         });
 
-        integration("and should suggest a tighter constraint if the primary "
-            "version is pre-1.0.0", () {
-          useMockClient(new MockClient((request) {
-            expect(request.method, equals("GET"));
-            expect(request.url.path, equals("/packages/foo.json"));
+        integration("and should use the other source's unquoted version if "
+                    "concrete", () {
+          setUpDependency({
+            'git': 'git://github.com/dart-lang/foo',
+            'version': '0.2.3'
+          });
+          expectDependencyValidationWarning('  foo: 0.2.3');
+        });
+      });
+    });
 
-            return new Future.immediate(new http.Response(json.stringify({
-              "name": "foo",
-              "uploaders": ["nweiz@google.com"],
-              "versions": ["0.0.1", "0.0.2"]
-            }), 200));
-          }));
+    group('has a path dependency', () {
+      group('where a hosted version exists', () {
+        integration("and should suggest the hosted primary version", () {
+          setUpDependency({'path': path.join(sandboxDir, 'foo')},
+              hostedVersions: ["3.0.0-pre", "2.0.0", "1.0.0"]);
+          expectDependencyValidationError('  foo: ">=2.0.0 <3.0.0"');
+        });
 
-          dir(appPath, [
-            libPubspec("test_pkg", "1.0.0", deps: [
-              {'git': 'git://github.com/dart-lang/foo'}
-            ])
-          ]).scheduleCreate();
+        integration("and should suggest the hosted prerelease version if "
+                    "it's the only version available", () {
+          setUpDependency({'path': path.join(sandboxDir, 'foo')},
+              hostedVersions: ["3.0.0-pre", "2.0.0-pre"]);
+          expectDependencyValidationError('  foo: ">=3.0.0-pre <4.0.0"');
+        });
 
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=0.0.2 <0.0.3"'))));
+        integration("and should suggest a tighter constraint if primary is "
+                    "pre-1.0.0", () {
+          setUpDependency({'path': path.join(sandboxDir, 'foo')},
+              hostedVersions: ["0.0.1", "0.0.2"]);
+          expectDependencyValidationError('  foo: ">=0.0.2 <0.0.3"');
         });
       });
 
-      group('where no hosted version of that dependency exists', () {
+      group('where no hosted version exists', () {
         integration("and should use the other source's version", () {
-          useMockClient(new MockClient((request) {
-            expect(request.method, equals("GET"));
-            expect(request.url.path, equals("/packages/foo.json"));
-
-            return new Future.immediate(new http.Response("not found", 404));
-          }));
-
-          dir(appPath, [
-            libPubspec("test_pkg", "1.0.0", deps: [
-              {
-                'git': {'url': 'git://github.com/dart-lang/foo'},
-                'version': '>=1.0.0 <2.0.0'
-              }
-            ])
-          ]).scheduleCreate();
-
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=1.0.0 <2.0.0"'))));
+          setUpDependency({
+            'path': path.join(sandboxDir, 'foo'),
+            'version': '>=1.0.0 <2.0.0'
+          });
+          expectDependencyValidationError('  foo: ">=1.0.0 <2.0.0"');
         });
 
         integration("and should use the other source's unquoted version if "
-            "it's concrete", () {
-          useMockClient(new MockClient((request) {
-            expect(request.method, equals("GET"));
-            expect(request.url.path, equals("/packages/foo.json"));
-
-            return new Future.immediate(new http.Response("not found", 404));
-          }));
-
-          dir(appPath, [
-            libPubspec("test_pkg", "1.0.0", deps: [
-              {
-                'git': {'url': 'git://github.com/dart-lang/foo'},
-                'version': '0.2.3'
-              }
-            ])
-          ]).scheduleCreate();
-
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains('  foo: 0.2.3'))));
+                    "concrete", () {
+          setUpDependency({
+            'path': path.join(sandboxDir, 'foo'),
+            'version': '0.2.3'
+          });
+          expectDependencyValidationError('  foo: 0.2.3');
         });
       });
     });
@@ -507,9 +506,7 @@ main() {
             }))
           ]).scheduleCreate();
 
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=1.2.3 <2.0.0"'))));
+          expectDependencyValidationWarning('  foo: ">=1.2.3 <2.0.0"');
         });
 
         integration('and it should suggest a concrete constraint if the locked '
@@ -532,9 +529,7 @@ main() {
             }))
           ]).scheduleCreate();
 
-          expectLater(schedulePackageValidation(dependency),
-              pairOf(isEmpty, someElement(contains(
-                  '  foo: ">=0.1.2 <0.1.3"'))));
+          expectDependencyValidationWarning('  foo: ">=0.1.2 <0.1.3"');
         });
       });
     });
-- 
GitLab