diff --git a/lib/src/io.dart b/lib/src/io.dart index 0eab87a970e42ce138d17e442e2c7bf475ab76cb..65f0507f15a2168264ea18859ba1ae2d7e4125b3 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 0000000000000000000000000000000000000000..123a3086c2ed0b3a8c5d546e6a12521cf3e897d2 --- /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 13e6b8d68db8d6ca2119339b00abd2c61cb32fc1..fd431171cf43c81a655654df49832b273d26b315 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 a848c545574db233111e1e1e698822e96915c8c1..de027385ffdf75a6b7afc640cbc63482ecfb91a4 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 0000000000000000000000000000000000000000..cef0a760b07a95788a5b70111968d06c285b47bb --- /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 0000000000000000000000000000000000000000..fc2cf4df8250b05fa37d7766e761625e429ac306 --- /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 0000000000000000000000000000000000000000..324a37b3c532a8e64897b7dd4263017dec99e88c --- /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 0000000000000000000000000000000000000000..56218367f98f7066ad1a68378d01817cb35693c6 --- /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 0000000000000000000000000000000000000000..56fe28a061b98c8c1e7aaec8fa79911662db9380 --- /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 b84a7aa658dacf9f9ff1d0abefdcbdea2e738fb7..eb895750bfd872f4aa7723e70b9394e19c83af9e 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 b11d90f690fcb1e997a7985a3f0d5d721b5aa74d..b64f1311dc68fadd64cf3bc8e67be0d2b5637707 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"'); }); }); });