diff --git a/lib/src/command/deps.dart b/lib/src/command/deps.dart index 25bfa910fd2c897d4b2698c2dda3b7184b084cc3..32cb0cdd447f5e265171bebc4e9c7c83e9f8aa7d 100644 --- a/lib/src/command/deps.dart +++ b/lib/src/command/deps.dart @@ -74,7 +74,7 @@ class DepsCommand extends PubCommand { _buffer.writeln(); _buffer.writeln("$section:"); for (var name in ordered(names)) { - var package = entrypoint.packageGraph.packages[name]; + var package = _getPackage(name); _buffer.write("- ${_labelPackage(package)}"); if (package.dependencies.isEmpty) { @@ -115,7 +115,7 @@ class DepsCommand extends PubCommand { _buffer.writeln("$name:"); for (var name in deps) { - var package = entrypoint.packageGraph.packages[name]; + var package = _getPackage(name); _buffer.writeln("- ${_labelPackage(package)}"); for (var dep in package.dependencies) { @@ -141,8 +141,7 @@ class DepsCommand extends PubCommand { // Start with the root dependencies. var packageTree = {}; for (var dep in entrypoint.root.immediateDependencies) { - toWalk.add( - new Pair(entrypoint.packageGraph.packages[dep.name], packageTree)); + toWalk.add(new Pair(_getPackage(dep.name), packageTree)); } // Do a breadth-first walk to the dependency graph. @@ -163,8 +162,7 @@ class DepsCommand extends PubCommand { map[_labelPackage(package)] = childMap; for (var dep in package.dependencies) { - toWalk.add( - new Pair(entrypoint.packageGraph.packages[dep.name], childMap)); + toWalk.add(new Pair(_getPackage(dep.name), childMap)); } } @@ -184,4 +182,17 @@ class DepsCommand extends PubCommand { transitive.removeAll(root.dependencyOverrides.map((dep) => dep.name)); return transitive; } + + /// Get the package named [name], or throw a [DataError] if it's not + /// available. + /// + /// It's very unlikely that the lockfile won't be up-to-date with the pubspec, + /// but it's possible, since [Entrypoint.assertUpToDate]'s modification time + /// check can return a false negative. This fails gracefully if that happens. + Package _getPackage(String name) { + var package = entrypoint.packageGraph.packages[name]; + if (package != null) return package; + dataError('The pubspec.yaml file has changed since the pubspec.lock file ' + 'was generated, please run "pub get" again.'); + } } diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 3c2fa25809eff4062f859eab320c927b99f9cacf..fd88b8da80cf42d7ec64f00d21f465121658f418 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -7,7 +7,8 @@ library pub.entrypoint; import 'dart:async'; import 'dart:io'; -import 'package:path/path.dart' as path; +import 'package:package_config/packages_file.dart' as packages_file; +import 'package:path/path.dart' as p; import 'package:barback/barback.dart'; import 'barback/asset_environment.dart'; @@ -201,13 +202,13 @@ class Entrypoint { // Just precompile the debug version of a package. We're mostly interested // in improving speed for development iteration loops, which usually use // debug mode. - var depsDir = path.join('.pub', 'deps', 'debug'); + var depsDir = p.join('.pub', 'deps', 'debug'); var dependenciesToPrecompile = packageGraph.packages.values .where((package) { if (package.pubspec.transformers.isEmpty) return false; if (packageGraph.isPackageMutable(package.name)) return false; - if (!dirExists(path.join(depsDir, package.name))) return true; + if (!dirExists(p.join(depsDir, package.name))) return true; if (changed == null) return true; /// Only recompile [package] if any of its transitive dependencies have @@ -222,12 +223,12 @@ class Entrypoint { if (dirExists(depsDir)) { // Delete any cached dependencies that are going to be recached. for (var package in dependenciesToPrecompile) { - deleteEntry(path.join(depsDir, package)); + deleteEntry(p.join(depsDir, package)); } // Also delete any cached dependencies that should no longer be cached. for (var subdir in listDir(depsDir)) { - var package = packageGraph.packages[path.basename(subdir)]; + var package = packageGraph.packages[p.basename(subdir)]; if (package == null || package.pubspec.transformers.isEmpty || packageGraph.isPackageMutable(package.name)) { deleteEntry(subdir); @@ -257,9 +258,9 @@ class Entrypoint { await waitAndPrintErrors(assets.map((asset) async { if (!dependenciesToPrecompile.contains(asset.id.package)) return; - var destPath = path.join( - depsDir, asset.id.package, path.fromUri(asset.id.path)); - ensureDir(path.dirname(destPath)); + var destPath = p.join( + depsDir, asset.id.package, p.fromUri(asset.id.path)); + ensureDir(p.dirname(destPath)); await createFileFromStream(asset.read(), destPath); })); @@ -271,7 +272,7 @@ class Entrypoint { // assets (issue 19491), catch and handle compilation errors on a // per-package basis. for (var package in dependenciesToPrecompile) { - deleteEntry(path.join(depsDir, package)); + deleteEntry(p.join(depsDir, package)); } rethrow; } @@ -282,8 +283,8 @@ class Entrypoint { Future precompileExecutables({Iterable<String> changed}) async { if (changed != null) changed = changed.toSet(); - var binDir = path.join('.pub', 'bin'); - var sdkVersionPath = path.join(binDir, 'sdk-version'); + var binDir = p.join('.pub', 'bin'); + var sdkVersionPath = p.join(binDir, 'sdk-version'); // If the existing executable was compiled with a different SDK, we need to // recompile regardless of what changed. @@ -297,7 +298,7 @@ class Entrypoint { for (var entry in listDir(binDir)) { if (!dirExists(entry)) continue; - var package = path.basename(entry); + var package = p.basename(entry); if (!packageGraph.packages.containsKey(package) || packageGraph.isPackageMutable(package)) { deleteEntry(entry); @@ -337,7 +338,7 @@ class Entrypoint { }); await waitAndPrintErrors(executables.keys.map((package) async { - var dir = path.join(binDir, package); + var dir = p.join(binDir, package); cleanDir(dir); await environment.precompileExecutables(package, dir, executableIds: executables[package]); @@ -373,8 +374,8 @@ class Entrypoint { // changed. Since we delete the bin directory before recompiling, we need to // recompile all executables. var executablesExist = executables.every((executable) => - fileExists(path.join('.pub', 'bin', packageName, - "${path.url.basename(executable.path)}.snapshot"))); + fileExists(p.join('.pub', 'bin', packageName, + "${p.url.basename(executable.path)}.snapshot"))); if (!executablesExist) return executables; // Otherwise, we don't need to recompile. @@ -396,7 +397,7 @@ class Entrypoint { return source.downloadToSystemCache(id); } - var packageDir = path.join(packagesDir, id.name); + var packageDir = p.join(packagesDir, id.name); if (entryExists(packageDir)) deleteEntry(packageDir); return source.get(id, packageDir); }).then((_) => source.resolveId(id)); @@ -415,20 +416,127 @@ class Entrypoint { dataError('No .packages file found, please run "pub get" first.'); } - var packagesModified = new File(packagesFile).lastModifiedSync(); var pubspecModified = new File(pubspecPath).lastModifiedSync(); - if (packagesModified.isBefore(pubspecModified)) { - dataError('The pubspec.yaml file has changed since the .packages file ' - 'was generated, please run "pub get" again.'); + var lockFileModified = new File(lockFilePath).lastModifiedSync(); + + var touchedLockFile = false; + if (lockFileModified.isBefore(pubspecModified)) { + if (_isLockFileUpToDate() && _arePackagesAvailable()) { + touchedLockFile = true; + touch(lockFilePath); + } else { + dataError('The pubspec.yaml file has changed since the pubspec.lock ' + 'file was generated, please run "pub get" again.'); + } } - var lockFileModified = new File(lockFilePath).lastModifiedSync(); + var packagesModified = new File(packagesFile).lastModifiedSync(); if (packagesModified.isBefore(lockFileModified)) { - dataError('The pubspec.lock file has changed since the .packages file ' - 'was generated, please run "pub get" again.'); + if (_isPackagesFileUpToDate()) { + touch(packagesFile); + } else { + dataError('The pubspec.lock file has changed since the .packages file ' + 'was generated, please run "pub get" again.'); + } + } else if (touchedLockFile) { + touch(packagesFile); } } + /// Determines whether or not the lockfile is out of date with respect to the + /// pubspec. + /// + /// This will be `false` if the pubspec contains dependencies that are not in + /// the lockfile or that don't match what's in there. + bool _isLockFileUpToDate() { + return root.immediateDependencies.every((package) { + var locked = lockFile.packages[package.name]; + if (locked == null) return false; + + if (package.source != locked.source) return false; + + if (!package.constraint.allows(locked.version)) return false; + + var source = cache.sources[package.source]; + if (source == null) return false; + + return source.descriptionsEqual(package.description, locked.description); + }); + } + + /// Determines whether all of the packages in the lockfile are already + /// installed and available. + /// + /// Note: this assumes [_isLockFileUpToDate] has already been called and + /// returned `true`. + bool _arePackagesAvailable() { + return lockFile.packages.values.every((package) { + var source = cache.sources[package.source]; + + // This should only be called after [_isLockFileUpToDate] has returned + // `true`, which ensures all of the sources in the lock file are valid. + assert(source != null); + + // We only care about cached sources. Uncached sources aren't "installed". + // If one of those is missing, we want to show the user the file not + // found error later since installing won't accomplish anything. + if (source is! CachedSource) return true; + + // Get the directory. + var dir = source.getDirectory(package); + // See if the directory is there and looks like a package. + return dirExists(dir) && fileExists(p.join(dir, "pubspec.yaml")); + }); + } + + /// Determines whether or not the `.packages` file is out of date with respect + /// to the lockfile. + /// + /// This will be `false` if the packages file contains dependencies that are + /// not in the lockfile or that don't match what's in there. + bool _isPackagesFileUpToDate() { + var packages = packages_file.parse( + new File(packagesFile).readAsBytesSync(), + p.toUri(packagesFile)); + + return lockFile.packages.values.every((lockFileId) { + var source = cache.sources[lockFileId.source]; + + // It's very unlikely that the lockfile is invalid here, but it's not + // impossible—for example, the user may have a very old application + // package with a checked-in lockfile that's newer than the pubspec, but + // that contains sdk dependencies. + if (source == null) return false; + + var packagesFileUri = packages[lockFileId.name]; + if (packagesFileUri == null) return false; + + // Pub only generates "file:" and relative URIs. + if (packagesFileUri.scheme != 'file' && + packagesFileUri.scheme.isNotEmpty) { + return false; + } + + // Get the dirname of the .packages path, since it's pointing to lib/. + var packagesFilePath = p.dirname( + p.join(root.dir, p.fromUri(packagesFileUri))); + var lockFilePath = p.join(root.dir, source.getDirectory(lockFileId)); + + // For cached sources, make sure the directory exists and looks like a + // package. This is also done by [_arePackagesAvailable] but that may not + // be run if the lockfile is newer than the pubspec. + if (source is CachedSource && + !dirExists(packagesFilePath) || + !fileExists(p.join(packagesFilePath, "pubspec.yaml"))) { + return false; + } + + // Make sure that the packages file agrees with the lock file about the + // path to the package. + return p.normalize(packagesFilePath) == p.normalize(lockFilePath); + }); + } + /// Saves a list of concrete package versions to the `pubspec.lock` file. void _saveLockFile(List<PackageId> packageIds) { _lockFile = new LockFile(packageIds, cache.sources); @@ -439,7 +547,7 @@ class Entrypoint { /// Creates a self-referential symlink in the `packages` directory that allows /// a package to import its own files using `package:`. void _linkSelf() { - var linkPath = path.join(packagesDir, root.name); + var linkPath = p.join(packagesDir, root.name); // Create the symlink if it doesn't exist. if (entryExists(linkPath)) return; ensureDir(packagesDir); @@ -482,7 +590,7 @@ class Entrypoint { /// files and `package` files. List<String> _listDirWithoutPackages(dir) { return flatten(listDir(dir).map((file) { - if (path.basename(file) == 'packages') return []; + if (p.basename(file) == 'packages') return []; if (!dirExists(file)) return []; var fileAndSubfiles = [file]; fileAndSubfiles.addAll(_listDirWithoutPackages(file)); @@ -495,7 +603,7 @@ class Entrypoint { /// /// Otherwise, deletes a "packages" directories in [dir] if one exists. void _linkOrDeleteSecondaryPackageDir(String dir) { - var symlink = path.join(dir, 'packages'); + var symlink = p.join(dir, 'packages'); if (entryExists(symlink)) deleteEntry(symlink); if (_packageSymlinks) createSymlink(packagesDir, symlink, relative: true); } diff --git a/lib/src/executable.dart b/lib/src/executable.dart index 65b276ee4cdbf5b5c430aa4d527d54fd687ca7a9..bd21b4d8301e0d90739eb64b9598455fb9387ac7 100644 --- a/lib/src/executable.dart +++ b/lib/src/executable.dart @@ -82,6 +82,10 @@ Future<int> runExecutable(Entrypoint entrypoint, String package, // default mode for them to run. We can't run them in a different mode // using the snapshot. mode == BarbackMode.RELEASE) { + // Since we don't access the package graph, this doesn't happen + // automatically. + entrypoint.assertUpToDate(); + return _runCachedExecutable(entrypoint, localSnapshotPath, args, checked: checked); } diff --git a/lib/src/io.dart b/lib/src/io.dart index 8b7fc17cdf73621c2aa9c22438448f9faa3b030d..952751c451bf9adb716ac7fa76fb90549be54f77 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -831,6 +831,15 @@ _doProcess(Function fn, String executable, List<String> args, environment: environment); } +/// Updates [path]'s modification time. +void touch(String path) { + var file = new File(path).openSync(mode: FileMode.APPEND); + var originalLength = file.lengthSync(); + file.writeByteSync(0); + file.truncateSync(originalLength); + file.closeSync(); +} + /// Creates a temporary directory and passes its path to [fn]. /// /// Once the [Future] returned by [fn] completes, the temporary directory and diff --git a/test/must_pub_get_test.dart b/test/must_pub_get_test.dart index 5a10d82dcb87fe12a0553e0f943a2ffb1129f06a..981d9ff13a417373f5be50ef0f2445b17785c52a 100644 --- a/test/must_pub_get_test.dart +++ b/test/must_pub_get_test.dart @@ -4,39 +4,42 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:io'; import 'package:path/path.dart' as p; import 'package:pub/src/exit_codes.dart' as exit_codes; import 'package:pub/src/io.dart'; +import 'package:scheduled_test/scheduled_stream.dart'; import 'package:scheduled_test/scheduled_test.dart'; import 'descriptor.dart' as d; import 'test_pub.dart'; main() { - group("requires the user to run pub get first if", () { - setUp(() { - d.dir(appPath, [ - d.appPubspec(), - d.dir("web", []), - d.dir("bin", [ - d.file("script.dart", "main() => print('hello!');") - ]) - ]).create(); + setUp(() { + servePackages((builder) { + builder.serve("foo", "1.0.0"); + builder.serve("foo", "2.0.0"); + }); - pubGet(); + d.dir(appPath, [ + d.appPubspec(), + d.dir("web", []), + d.dir("bin", [ + d.file("script.dart", "main() => print('hello!');") + ]) + ]).create(); - // Delay a bit to make sure the modification times are noticeably - // different. 1s seems to be the finest granularity that dart:io reports. - schedule(() => new Future.delayed(new Duration(seconds: 1))); - }); + pubGet(); + }); + group("requires the user to run pub get first if", () { group("there's no lockfile", () { setUp(() { schedule(() => deleteEntry(p.join(sandboxDir, "myapp/pubspec.lock"))); }); - _forEveryCommand( + _requiresPubGet( 'No pubspec.lock file found, please run "pub get" first.'); }); @@ -45,55 +48,335 @@ main() { schedule(() => deleteEntry(p.join(sandboxDir, "myapp/.packages"))); }); - _forEveryCommand('No .packages file found, please run "pub get" first.'); + _requiresPubGet('No .packages file found, please run "pub get" first.'); + }); + + group("the pubspec has a new dependency", () { + setUp(() { + d.dir("foo", [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({"foo": {"path": "../foo"}}) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); + }); + + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); + }); + + group("the lockfile has a dependency from the wrong source", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + createLockFile(appPath, sandbox: ["foo"]); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); + }); + + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); }); - group("the pubspec is newer than the package spec", () { + group("the lockfile has a dependency from an unknown source", () { setUp(() { - schedule(() => _touch("pubspec.yaml")); + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + d.dir(appPath, [ + d.file("pubspec.lock", yaml({ + "packages": { + "foo": { + "description": "foo", + "version": "1.0.0", + "source": "sdk" + } + } + })) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); }); - _forEveryCommand('The pubspec.yaml file has changed since the .packages ' + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); + }); + + group("the lockfile has a dependency with the wrong description", () { + setUp(() { + d.dir("bar", [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({"foo": {"path": "../bar"}}) + ]).create(); + + pubGet(); + + createLockFile(appPath, sandbox: ["foo"]); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); + }); + + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); + }); + + group("the pubspec has an incompatible version of a dependency", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + d.dir(appPath, [ + d.appPubspec({"foo": "2.0.0"}) + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); + }); + + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); + }); + + group("the lockfile is pointing to an unavailable package with a newer " + "pubspec", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + schedule(() => deleteEntry(p.join(sandboxDir, cachePath))); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.yaml"); + }); + + _requiresPubGet('The pubspec.yaml file has changed since the ' + 'pubspec.lock file was generated, please run "pub get" again.'); + }); + + group("the lockfile is pointing to an unavailable package with an older " + ".packages", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + schedule(() => deleteEntry(p.join(sandboxDir, cachePath))); + + // Ensure that the lockfile looks newer than the .packages file. + _touch("pubspec.lock"); + }); + + _requiresPubGet('The pubspec.lock file has changed since the .packages ' 'file was generated, please run "pub get" again.'); }); - group("the lockfile is newer than the package spec", () { + group("the lockfile has a package that the .packages file doesn't", () { setUp(() { - schedule(() => _touch("pubspec.lock")); + d.dir("foo", [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({"foo": {"path": "../foo"}}) + ]).create(); + + pubGet(); + + createPackagesFile(appPath); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.lock"); }); - _forEveryCommand('The pubspec.lock file has changed since the .packages ' + _requiresPubGet('The pubspec.lock file has changed since the .packages ' 'file was generated, please run "pub get" again.'); }); + + group("the .packages file has a package with a non-file URI", () { + setUp(() { + d.dir("foo", [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({"foo": {"path": "../foo"}}) + ]).create(); + + pubGet(); + + d.dir(appPath, [ + d.file(".packages", """ +myapp:lib +foo:http://example.com/ +""") + ]).create(); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.lock"); + }); + + _requiresPubGet('The pubspec.lock file has changed since the .packages ' + 'file was generated, please run "pub get" again.'); + }); + + group("the .packages file points to the wrong place", () { + setUp(() { + d.dir("bar", [ + d.libPubspec("foo", "1.0.0") + ]).create(); + + d.dir(appPath, [ + d.appPubspec({"foo": {"path": "../bar"}}) + ]).create(); + + pubGet(); + + createPackagesFile(appPath, sandbox: ["foo"]); + + // Ensure that the pubspec looks newer than the lockfile. + _touch("pubspec.lock"); + }); + + _requiresPubGet('The pubspec.lock file has changed since the .packages ' + 'file was generated, please run "pub get" again.'); + }); + }); + + group("doesn't require the user to run pub get first if", () { + group("the pubspec is older than the lockfile which is older than the " + "packages file, even if the contents are wrong", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + _touch("pubspec.lock"); + _touch(".packages"); + }); + + _runsSuccessfully(runDeps: false); + }); + + group("the pubspec is newer than the lockfile, but they're up-to-date", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + _touch("pubspec.yaml"); + }); + + _runsSuccessfully(); + }); + + group("the lockfile is newer than .packages, but they're up-to-date", () { + setUp(() { + d.dir(appPath, [ + d.appPubspec({"foo": "1.0.0"}) + ]).create(); + + pubGet(); + + _touch("pubspec.lock"); + }); + + _runsSuccessfully(); + }); }); } /// Runs every command that care about the world being up-to-date, and asserts /// that it prints [message] as part of its error. -void _forEveryCommand(String message) { +void _requiresPubGet(String message) { for (var command in ["build", "serve", "run", "deps"]) { integration("for pub $command", () { var args = [command]; if (command == "run") args.add("script"); - var output; - var error; - if (command == "list-package-dirs") { - output = contains(JSON.encode(message)); - } else { - error = contains(message); - } - schedulePub( args: args, - output: output, - error: error, + error: contains(message), exitCode: exit_codes.DATA); }); } } +/// Ensures that pub doesn't require "pub get" for the current package. +/// +/// If [runDeps] is false, `pub deps` isn't included in the test. This is +/// sometimes not desirable, since it uses slightly stronger checks for pubspec +/// and lockfile consistency. +void _runsSuccessfully({bool runDeps: true}) { + var commands = ["build", "serve", "run"]; + if (runDeps) commands.add("deps"); + + for (var command in commands) { + integration("for pub $command", () { + var args = [command]; + if (command == "run") args.add("bin/script.dart"); + if (command == "serve") ; + + if (command != "serve") { + schedulePub(args: args); + } else { + var pub = startPub(args: ["serve", "--port=0"]); + pub.stdout.expect(consumeThrough(startsWith("Serving myapp web"))); + pub.kill(); + } + + schedule(() { + // If pub determines that everything is up-to-date, it should set the + // mtimes to indicate that. + var pubspecModified = new File(p.join(sandboxDir, "myapp/pubspec.yaml")) + .lastModifiedSync(); + var lockFileModified = + new File(p.join(sandboxDir, "myapp/pubspec.lock")) + .lastModifiedSync(); + var packagesModified = new File(p.join(sandboxDir, "myapp/.packages")) + .lastModifiedSync(); + + expect(!pubspecModified.isAfter(lockFileModified), isTrue); + expect(!lockFileModified.isAfter(packagesModified), isTrue); + }, "testing last-modified times"); + }); + } +} + +/// Schedules a non-semantic modification to [path]. void _touch(String path) { - path = p.join(sandboxDir, "myapp", path); - writeTextFile(path, readTextFile(path) + " "); + schedule(() async { + // Delay a bit to make sure the modification times are noticeably different. + // 1s seems to be the finest granularity that dart:io reports. + await new Future.delayed(new Duration(seconds: 1)); + + path = p.join(sandboxDir, "myapp", path); + touch(path); + }, "touching $path"); } diff --git a/test/test_pub.dart b/test/test_pub.dart index 2c7af92411fa42e0a4b25171fb556c686c662b28..5427625d392a0e93cd87f3733f85bc27b840680b 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart @@ -714,16 +714,35 @@ void makeGlobalPackage(String package, String version, /// hosted packages. void createLockFile(String package, {Iterable<String> sandbox, Iterable<String> pkg, Map<String, String> hosted}) { - var cache = new SystemCache.withSources( - rootDir: p.join(sandboxDir, cachePath)); + schedule(() async { + var cache = new SystemCache.withSources( + rootDir: p.join(sandboxDir, cachePath)); - var lockFile = _createLockFile(cache.sources, - sandbox: sandbox, pkg: pkg, hosted: hosted); + var lockFile = _createLockFile(cache.sources, + sandbox: sandbox, pkg: pkg, hosted: hosted); - d.dir(package, [ - d.file('pubspec.lock', lockFile.serialize(null)), - d.file('.packages', lockFile.packagesFile(package)) - ]).create(); + await d.dir(package, [ + d.file('pubspec.lock', lockFile.serialize(null)), + d.file('.packages', lockFile.packagesFile(package)) + ]).create(); + }, "creating lockfile for $package"); +} + +/// Like [createLockFile], but creates only a `.packages` file without a +/// lockfile. +void createPackagesFile(String package, {Iterable<String> sandbox, + Iterable<String> pkg, Map<String, String> hosted}) { + schedule(() async { + var cache = new SystemCache.withSources( + rootDir: p.join(sandboxDir, cachePath)); + + var lockFile = _createLockFile(cache.sources, + sandbox: sandbox, pkg: pkg, hosted: hosted); + + await d.dir(package, [ + d.file('.packages', lockFile.packagesFile(package)) + ]).create(); + }, "creating .packages for $package"); } /// Creates a lock file for [package] without running `pub get`.