diff --git a/lib/src/command/cache_repair.dart b/lib/src/command/cache_repair.dart index dfeab5eaa55f085668d51f093b5ed140db28f3ef..d5b3212c9ffcb6b467a805b229363ec77be0a599 100644 --- a/lib/src/command/cache_repair.dart +++ b/lib/src/command/cache_repair.dart @@ -42,6 +42,17 @@ class CacheRepairCommand extends PubCommand { log.message("Failed to reinstall ${log.red(failures)} $packages."); } + var results = await globals.repairActivatedPackages(); + if (results.first > 0) { + var packages = pluralize("package", results.first); + log.message("Reactivated ${log.green(results.first)} $packages."); + } + + if (results.last > 0) { + var packages = pluralize("package", results.last); + log.message("Failed to reactivate ${log.red(results.last)} $packages."); + } + if (successes == 0 && failures == 0) { log.message("No packages in cache, so nothing to repair."); } diff --git a/lib/src/entrypoint.dart b/lib/src/entrypoint.dart index 940efab97d5c5a9a67b311dca22ec2048c0a278f..5c2c8454d28963c6504371957d724f6a340add0b 100644 --- a/lib/src/entrypoint.dart +++ b/lib/src/entrypoint.dart @@ -348,6 +348,10 @@ class Entrypoint { /// contains dependencies that are not in the lockfile or that don't match /// what's in there. bool _isLockFileUpToDate(LockFile lockFile) { + /// If this is an entrypoint for an in-memory package, trust the in-memory + /// lockfile provided for it. + if (root.dir == null) return true; + return root.immediateDependencies.every((package) { var locked = lockFile.packages[package.name]; if (locked == null) return false; diff --git a/lib/src/global_packages.dart b/lib/src/global_packages.dart index 73a0c0abffb0038257ee13b6c04a02627d663c6a..d1f13cbc077f1461b4be79006237788002e348ab 100644 --- a/lib/src/global_packages.dart +++ b/lib/src/global_packages.dart @@ -13,6 +13,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'barback/asset_environment.dart'; import 'entrypoint.dart'; +import 'exceptions.dart'; import 'executable.dart' as exe; import 'io.dart'; import 'lock_file.dart'; @@ -27,10 +28,6 @@ import 'source/path.dart'; import 'system_cache.dart'; import 'utils.dart'; -/// Matches the package name that a binstub was created for inside the contents -/// of the shell script. -final _binStubPackagePattern = new RegExp(r"Package: ([a-zA-Z0-9_-]+)"); - /// Maintains the set of packages that have been globally activated. /// /// These have been hand-chosen by the user to make their executables in bin/ @@ -377,29 +374,35 @@ class GlobalPackages { String _getLockFilePath(String name) => p.join(_directory, name, "pubspec.lock"); - /// Shows to the user formatted list of globally activated packages. + /// Shows the user a formatted list of globally activated packages. void listActivePackages() { if (!dirExists(_directory)) return; - // Loads lock [file] and returns [PackageId] of the activated package. - loadPackageId(file, name) { - var lockFile = new LockFile.load(p.join(_directory, file), cache.sources); - return lockFile.packages[name]; - } - - var packages = listDir(_directory).map((entry) { - if (fileExists(entry)) { - return loadPackageId(entry, p.basenameWithoutExtension(entry)); - } else { - return loadPackageId(p.join(entry, 'pubspec.lock'), p.basename(entry)); - } - }).toList(); - - packages + return listDir(_directory).map(_loadPackageId).toList() ..sort((id1, id2) => id1.name.compareTo(id2.name)) ..forEach((id) => log.message(_formatPackage(id))); } + /// Returns the [PackageId] for the globally-activated package at [path]. + /// + /// [path] should be a path within [_directory]. It can either be an old-style + /// path to a single lockfile or a new-style path to a directory containing a + /// lockfile. + PackageId _loadPackageId(String path) { + var name = p.basenameWithoutExtension(path); + if (!fileExists(path)) path = p.join(path, 'pubspec.lock'); + + var id = new LockFile.load(p.join(_directory, path), cache.sources) + .packages[name]; + + if (id == null) { + throw new FormatException("Pubspec for activated package $name didn't " + "contain an entry for itself."); + } + + return id; + } + /// Returns formatted string representing the package [id]. String _formatPackage(PackageId id) { if (id.source == 'git') { @@ -413,6 +416,92 @@ class GlobalPackages { } } + /// Repairs any corrupted globally-activated packages and their binstubs. + /// + /// Returns a pair of two [int]s. The first indicates how many packages were + /// successfully re-activated; the second indicates how many failed. + Future<Pair<int, int>> repairActivatedPackages() async { + var executables = {}; + if (dirExists(_binStubDir)) { + for (var entry in listDir(_binStubDir)) { + try { + var binstub = readTextFile(entry); + var package = _binStubProperty(binstub, "Package"); + if (package == null) { + throw new ApplicationException("No 'Package' property."); + } + + var executable = _binStubProperty(binstub, "Executable"); + if (executable == null) { + throw new ApplicationException("No 'Executable' property."); + } + + executables.putIfAbsent(package, () => []).add(executable); + } catch (error, stackTrace) { + log.error( + "Error reading binstub for " + "\"${p.basenameWithoutExtension(entry)}\"", + error, stackTrace); + + tryDeleteEntry(entry); + } + } + } + + var successes = 0; + var failures = 0; + if (dirExists(_directory)) { + for (var entry in listDir(_directory)) { + var id; + try { + id = _loadPackageId(entry); + log.message("Reactivating ${log.bold(id.name)} ${id.version}..."); + + var entrypoint = await find(id.name); + + var graph = await entrypoint.loadPackageGraph(); + var snapshots = await _precompileExecutables(entrypoint, id.name); + var packageExecutables = executables.remove(id.name); + if (packageExecutables == null) packageExecutables = []; + _updateBinStubs(graph.packages[id.name], packageExecutables, + overwriteBinStubs: true, snapshots: snapshots, + suggestIfNotOnPath: false); + successes++; + } catch (error, stackTrace) { + var message = "Failed to reactivate " + "${log.bold(p.basenameWithoutExtension(entry))}"; + if (id != null) { + message += " ${id.version}"; + if (id.source != "hosted") message += " from ${id.source}"; + } + + log.error(message, error, stackTrace); + failures++; + + tryDeleteEntry(entry); + } + } + } + + if (executables.isNotEmpty) { + var packages = pluralize("package", executables.length); + var message = new StringBuffer("Binstubs exist for non-activated " + "packages:\n"); + executables.forEach((package, executableNames) { + // TODO(nweiz): Use a normal for loop here when + // https://github.com/dart-lang/async_await/issues/68 is fixed. + executableNames.forEach((executable) => + deleteEntry(p.join(_binStubDir, executable))); + + message.writeln(" From ${log.bold(package)}: " + "${toSentence(executableNames)}"); + }); + log.error(message); + } + + return new Pair(successes, failures); + } + /// Updates the binstubs for [package]. /// /// A binstub is a little shell script in `PUB_CACHE/bin` that runs an @@ -430,10 +519,14 @@ class GlobalPackages { /// Otherwise, the previous ones will be preserved. /// /// If [snapshots] is given, it is a map of the names of executables whose - /// snapshots that were precompiled to their paths. Binstubs for those will - /// run the snapshot directly and skip pub entirely. + /// snapshots were precompiled to the paths of those snapshots. Binstubs for + /// those will run the snapshot directly and skip pub entirely. + /// + /// If [suggestIfNotOnPath] is `true` (the default), this will warn the user if + /// the bin directory isn't on their path. void _updateBinStubs(Package package, List<String> executables, - {bool overwriteBinStubs, Map<String, String> snapshots}) { + {bool overwriteBinStubs, Map<String, String> snapshots, + bool suggestIfNotOnPath: true}) { if (snapshots == null) snapshots = const {}; // Remove any previously activated binstubs for this package, in case the @@ -513,7 +606,9 @@ class GlobalPackages { } } - if (installed.isNotEmpty) _suggestIfNotOnPath(installed); + if (suggestIfNotOnPath && installed.isNotEmpty) { + _suggestIfNotOnPath(installed.first); + } } /// Creates a binstub named [executable] that runs [script] from [package]. @@ -537,12 +632,11 @@ class GlobalPackages { var previousPackage; if (fileExists(binStubPath)) { var contents = readTextFile(binStubPath); - var match = _binStubPackagePattern.firstMatch(contents); - if (match != null) { - previousPackage = match[1]; - if (!overwrite) return previousPackage; - } else { + previousPackage = _binStubProperty(contents, "Package"); + if (previousPackage == null) { log.fine("Could not parse binstub $binStubPath:\n$contents"); + } else if (!overwrite) { + return previousPackage; } } @@ -568,6 +662,20 @@ rem Executable: ${executable} rem Script: ${script} $invocation %* """; + + if (snapshot != null) { + batch += """ + +rem The VM exits with code 255 if the snapshot version is out-of-date. +rem If it is, we need to delete it and run "pub global" manually. +if not errorlevel 255 ( + exit /b %errorlevel% +) + +pub global run ${package.name}:$script %* +"""; + } + writeTextFile(binStubPath, batch); } else { var bash = """ @@ -579,6 +687,21 @@ $invocation %* # Script: ${script} $invocation "\$@" """; + + if (snapshot != null) { + bash += """ + +# The VM exits with code 255 if the snapshot version is out-of-date. +# If it is, we need to delete it and run "pub global" manually. +exit_code=\$? +if [[ \$exit_code != 255 ]]; then + exit \$exit_code +fi + +pub global run ${package.name}:$script "\$@" +"""; + } + writeTextFile(binStubPath, bash); // Make it executable. @@ -606,13 +729,13 @@ $invocation "\$@" for (var file in listDir(_binStubDir, includeDirs: false)) { var contents = readTextFile(file); - var match = _binStubPackagePattern.firstMatch(contents); - if (match == null) { + var binStubPackage = _binStubProperty(contents, "Package"); + if (binStubPackage == null) { log.fine("Could not parse binstub $file:\n$contents"); continue; } - if (match[1] == package) { + if (binStubPackage == package) { log.fine("Deleting old binstub $file"); deleteEntry(file); } @@ -621,11 +744,14 @@ $invocation "\$@" /// Checks to see if the binstubs are on the user's PATH and, if not, suggests /// that the user add the directory to their PATH. - void _suggestIfNotOnPath(List<String> installed) { + /// + /// [installed] should be the name of an installed executable that can be used + /// to test whether accessing it on the path works. + void _suggestIfNotOnPath(String installed) { if (Platform.operatingSystem == "windows") { // See if the shell can find one of the binstubs. // "\q" means return exit code 0 if found or 1 if not. - var result = runProcessSync("where", [r"\q", installed.first + ".bat"]); + var result = runProcessSync("where", [r"\q", installed + ".bat"]); if (result.exitCode == 0) return; log.warning( @@ -636,7 +762,7 @@ $invocation "\$@" 'A web search for "configure windows path" will show you how.'); } else { // See if the shell can find one of the binstubs. - var result = runProcessSync("which", [installed.first]); + var result = runProcessSync("which", [installed]); if (result.exitCode == 0) return; var binDir = _binStubDir; @@ -655,4 +781,12 @@ $invocation "\$@" "\n"); } } + + /// Returns the value of the property named [name] in the bin stub script + /// [source]. + String _binStubProperty(String source, String name) { + var pattern = new RegExp(quoteRegExp(name) + r": ([a-zA-Z0-9_-]+)"); + var match = pattern.firstMatch(source); + return match == null ? null : match[1]; + } } diff --git a/lib/src/io.dart b/lib/src/io.dart index c55c17b67e345308edbe6b4e2e02fed1787267e2..58a52ef00eaf0e412fb618ec23763f836da0273f 100644 --- a/lib/src/io.dart +++ b/lib/src/io.dart @@ -392,6 +392,17 @@ void deleteEntry(String path) { }); } +/// Attempts to delete whatever's at [path], but doesn't throw an exception if +/// the deletion fails. +void tryDeleteEntry(String path) { + try { + deleteEntry(path); + } catch (error, stackTrace) { + log.fine("Failed to delete $entry: $error\n" + "${new Chain.forTrace(stackTrace)}"); + } +} + /// "Cleans" [dir]. /// /// If that directory already exists, it is deleted. Then a new empty directory diff --git a/lib/src/log.dart b/lib/src/log.dart index edc7fe0d75f7b422d3cdd9cfd9dcef9f7338d291..14627122e29b4eb1d3faec6ed8d03cb045d6124b 100644 --- a/lib/src/log.dart +++ b/lib/src/log.dart @@ -182,16 +182,16 @@ class Entry { } /// Logs [message] at [Level.ERROR]. -void error(message, [error]) { +/// +/// If [error] is passed, it's appended to [message]. If [trace] is passed, it's +/// printed at log level fine. +void error(message, [error, StackTrace trace]) { if (error != null) { message = "$message: $error"; - var trace; - if (error is Error) trace = error.stackTrace; - if (trace != null) { - message = "$message\nStackTrace: $trace"; - } + if (error is Error && trace == null) trace = error.stackTrace; } write(Level.ERROR, message); + if (trace != null) write(Level.FINE, new Chain.forTrace(trace)); } /// Logs [message] at [Level.WARNING]. diff --git a/lib/src/source/git.dart b/lib/src/source/git.dart index 9d08f83fc1e7588ba35494409bf0c9608a786c4f..58ab066ad33bf4982bccdd41671c1cfcbc79bc25 100644 --- a/lib/src/source/git.dart +++ b/lib/src/source/git.dart @@ -143,8 +143,8 @@ class GitSource extends CachedSource { /// Resets all cached packages back to the pristine state of the Git /// repository at the revision they are pinned to. - Future<Pair<int, int>> repairCachedPackages() { - if (!dirExists(systemCacheRoot)) return new Future.value(new Pair(0, 0)); + Future<Pair<int, int>> repairCachedPackages() async { + if (!dirExists(systemCacheRoot)) return new Pair(0, 0); var successes = 0; var failures = 0; @@ -159,25 +159,30 @@ class GitSource extends CachedSource { // (pinned to different commits). The sort order of those is unspecified. packages.sort(Package.orderByNameAndVersion); - return Future.wait(packages.map((package) { + for (var package in packages) { log.message("Resetting Git repository for " "${log.bold(package.name)} ${package.version}..."); - // Remove all untracked files. - return git.run(["clean", "-d", "--force", "-x"], - workingDir: package.dir).then((_) { + try { + // Remove all untracked files. + await git.run(["clean", "-d", "--force", "-x"], + workingDir: package.dir); + // Discard all changes to tracked files. - return git.run(["reset", "--hard", "HEAD"], workingDir: package.dir); - }).then((_) { + await git.run(["reset", "--hard", "HEAD"], workingDir: package.dir); + successes++; - }).catchError((error, stackTrace) { - failures++; + } on git.GitException catch (error, stackTrace) { log.error("Failed to reset ${log.bold(package.name)} " "${package.version}. Error:\n$error"); log.fine(stackTrace); failures++; - }, test: (error) => error is git.GitException); - })).then((_) => new Pair(successes, failures)); + + tryDeleteEntry(package.dir); + } + } + + return new Pair(successes, failures); } /// Ensure that the canonical clone of the repository referred to by [id] (the diff --git a/lib/src/source/hosted.dart b/lib/src/source/hosted.dart index 3bb0ea14bfa1ab075996a807def53c3da6c6432a..825a87257384c62e6834b01a23c925754a16ff73 100644 --- a/lib/src/source/hosted.dart +++ b/lib/src/source/hosted.dart @@ -124,30 +124,34 @@ class HostedSource extends CachedSource { /// Re-downloads all packages that have been previously downloaded into the /// system cache from any server. - Future<Pair<int, int>> repairCachedPackages() { - if (!dirExists(systemCacheRoot)) return new Future.value(new Pair(0, 0)); + Future<Pair<int, int>> repairCachedPackages() async { + if (!dirExists(systemCacheRoot)) return new Pair(0, 0); var successes = 0; var failures = 0; - return Future.wait(listDir(systemCacheRoot).map((serverDir) { + for (var serverDir in listDir(systemCacheRoot)) { var url = _directoryToUrl(path.basename(serverDir)); var packages = _getCachedPackagesInDirectory(path.basename(serverDir)); packages.sort(Package.orderByNameAndVersion); - return Future.wait(packages.map((package) { - return _download(url, package.name, package.version, package.dir) - .then((_) { + for (var package in packages) { + try { + await _download(url, package.name, package.version, package.dir); successes++; - }).catchError((error, stackTrace) { + } catch (error, stackTrace) { failures++; var message = "Failed to repair ${log.bold(package.name)} " "${package.version}"; if (url != defaultUrl) message += " from $url"; log.error("$message. Error:\n$error"); log.fine(stackTrace); - }); - })); - })).then((_) => new Pair(successes, failures)); + + tryDeleteEntry(package.dir); + } + } + } + + return new Pair(successes, failures); } /// Gets all of the packages that have been downloaded into the system cache diff --git a/test/cache/repair/handles_corrupted_binstub_test.dart b/test/cache/repair/handles_corrupted_binstub_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..5af3b4703f3cca5ba4eaf1f3c8200a7a244e428a --- /dev/null +++ b/test/cache/repair/handles_corrupted_binstub_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2014, 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 pub_tests; + +import 'dart:io'; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('handles a corrupted binstub script', () { + servePackages((builder) { + builder.serve("foo", "1.0.0", contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('bin', [ + d.file(binStubName('script'), 'junk') + ]) + ]).create(); + + schedulePub(args: ["cache", "repair"], + error: contains('Error reading binstub for "script":')); + }); +} diff --git a/test/cache/repair/handles_corrupted_global_lockfile_test.dart b/test/cache/repair/handles_corrupted_global_lockfile_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..098f60270e862f26723f61bfd9adff8245f6d3a5 --- /dev/null +++ b/test/cache/repair/handles_corrupted_global_lockfile_test.dart @@ -0,0 +1,25 @@ +// Copyright (c) 2014, 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 pub_tests; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('handles a corrupted global lockfile', () { + d.dir(cachePath, [ + d.dir('global_packages/foo', [ + d.file('pubspec.lock', 'junk') + ]) + ]).create(); + + schedulePub(args: ["cache", "repair"], + error: contains('Failed to reactivate foo:'), + output: contains('Failed to reactivate 1 package.')); + }); +} diff --git a/test/cache/repair/handles_orphaned_binstub_test.dart b/test/cache/repair/handles_orphaned_binstub_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..af6fd33604321d2644e7588e3a2c4aedb1e8c275 --- /dev/null +++ b/test/cache/repair/handles_orphaned_binstub_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2014, 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 pub_tests; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +const _ORPHANED_BINSTUB = """ +#!/usr/bin/env sh +# This file was created by pub v0.1.2-3. +# Package: foo +# Version: 1.0.0 +# Executable: foo-script +# Script: script +dart "/path/to/.pub-cache/global_packages/foo/bin/script.dart.snapshot" "\$@" +"""; + +main() { + initConfig(); + integration('handles an orphaned binstub script', () { + d.dir(cachePath, [ + d.dir('bin', [ + d.file(binStubName('script'), _ORPHANED_BINSTUB) + ]) + ]).create(); + + schedulePub(args: ["cache", "repair"], error: allOf([ + contains('Binstubs exist for non-activated packages:'), + contains('From foo: foo-script') + ])); + }); +} diff --git a/test/cache/repair/recompiles_snapshots_test.dart b/test/cache/repair/recompiles_snapshots_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..feb9f39c66af8fb26e3e718ba71df2bda3a5917e --- /dev/null +++ b/test/cache/repair/recompiles_snapshots_test.dart @@ -0,0 +1,45 @@ +// Copyright (c) 2014, 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 pub_tests; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +main() { + initConfig(); + integration('recompiles activated executable snapshots', () { + servePackages((builder) { + builder.serve("foo", "1.0.0", contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('global_packages/foo/bin', [ + d.file('script.dart.snapshot', 'junk') + ]) + ]).create(); + + schedulePub(args: ["cache", "repair"], + output: ''' + Downloading foo 1.0.0... + Reinstalled 1 package. + Reactivating foo 1.0.0... + Precompiling executables... + Loading source assets... + Precompiled foo:script. + Reactivated 1 package.'''); + + var pub = pubRun(global: true, args: ["foo:script"]); + pub.stdout.expect("ok"); + pub.shouldExit(); + }); +} diff --git a/test/cache/repair/updates_binstubs_test.dart b/test/cache/repair/updates_binstubs_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..1d612a1b6162a8d6523aa8b8bc274dab54b246ea --- /dev/null +++ b/test/cache/repair/updates_binstubs_test.dart @@ -0,0 +1,65 @@ +// Copyright (c) 2014, 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 pub_tests; + +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +const _OUTDATED_BINSTUB = """ +#!/usr/bin/env sh +# This file was created by pub v0.1.2-3. +# Package: foo +# Version: 1.0.0 +# Executable: foo-script +# Script: script +dart "/path/to/.pub-cache/global_packages/foo/bin/script.dart.snapshot" "\$@" +"""; + +main() { + initConfig(); + integration('updates an outdated binstub script', () { + servePackages((builder) { + builder.serve("foo", "1.0.0", pubspec: { + "executables": { + "foo-script": "script" + } + }, contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok \$args');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('bin', [ + d.file(binStubName('foo-script'), _OUTDATED_BINSTUB) + ]) + ]).create(); + + // Repair them. + schedulePub(args: ["cache", "repair"], + output: ''' + Downloading foo 1.0.0... + Reinstalled 1 package. + Reactivating foo 1.0.0... + Precompiling executables... + Loading source assets... + Precompiled foo:script. + Installed executable foo-script. + Reactivated 1 package.'''); + + // The broken versions should have been replaced. + d.dir(cachePath, [ + d.dir('bin', [ + // 255 is the VM's exit code upon seeing an out-of-date snapshot. + d.matcherFile(binStubName('foo-script'), contains('255')) + ]) + ]).validate(); + }); +} diff --git a/test/global/activate/outdated_binstub_test.dart b/test/global/activate/outdated_binstub_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..51bcc837bab0a730a4f8c0f2c39c29d3ee454e19 --- /dev/null +++ b/test/global/activate/outdated_binstub_test.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2014, 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 'dart:async'; + +import 'package:path/path.dart' as p; +import 'package:scheduled_test/scheduled_process.dart'; +import 'package:scheduled_test/scheduled_stream.dart'; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; + +const _OUTDATED_BINSTUB = """ +#!/usr/bin/env sh +# This file was created by pub v0.1.2-3. +# Package: foo +# Version: 1.0.0 +# Executable: foo-script +# Script: script +dart "/path/to/.pub-cache/global_packages/foo/bin/script.dart.snapshot" "\$@" +"""; + +main() { + initConfig(); + integration("an outdated binstub is replaced", () { + servePackages((builder) { + builder.serve("foo", "1.0.0", pubspec: { + "executables": { + "foo-script": "script" + } + }, contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok \$args');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('bin', [ + d.file(binStubName('foo-script'), _OUTDATED_BINSTUB) + ]) + ]).create(); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('bin', [ + // 255 is the VM's exit code upon seeing an out-of-date snapshot. + d.matcherFile(binStubName('foo-script'), contains("255")) + ]) + ]).validate(); + }); +} diff --git a/test/global/binstubs/binstub_runs_executable_test.dart b/test/global/binstubs/binstub_runs_executable_test.dart index 30167b0e796234769d7ca6f6c59c3a4092a454dd..a3353e967e6841b0e0718fa33e10e9b282f699f6 100644 --- a/test/global/binstubs/binstub_runs_executable_test.dart +++ b/test/global/binstubs/binstub_runs_executable_test.dart @@ -62,16 +62,3 @@ main() { process.shouldExit(); }); } - -/// The buildbots do not have the Dart SDK (containing "dart" and "pub") on -/// their PATH, so we need to spawn the binstub process with a PATH that -/// explicitly includes it. -getEnvironment() { - var binDir = p.dirname(Platform.executable); - var separator = Platform.operatingSystem == "windows" ? ";" : ":"; - var path = "${Platform.environment["PATH"]}$separator$binDir"; - - var environment = getPubTestEnvironment(); - environment["PATH"] = path; - return environment; -} diff --git a/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart b/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart index e8f7d153cf0b1ab3422af4dfd51f5078edc75a6b..a5d02befb8cdb482c8a5203191cbcfa87aa5e0b8 100644 --- a/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart +++ b/test/global/binstubs/binstub_runs_global_run_if_no_snapshot_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart index 3d704e73edb02ecbecdc157043ff0121037fbe16..9e7538279b04057e9f94d0b46ec83215c7d16778 100644 --- a/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart +++ b/test/global/binstubs/binstub_runs_precompiled_snapshot_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/creates_executables_in_pubspec_test.dart b/test/global/binstubs/creates_executables_in_pubspec_test.dart index f631748e73f4fe363b54ef45e5fc4f9f1c3c18c0..ca0f42bb6f7ebab10db88dcf77444f2a8768fa1b 100644 --- a/test/global/binstubs/creates_executables_in_pubspec_test.dart +++ b/test/global/binstubs/creates_executables_in_pubspec_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/explicit_executables_test.dart b/test/global/binstubs/explicit_executables_test.dart index a5b6c7ae74c6e3c94076b649454997643f87a0b1..14ba50af43e6ad8efae583a9f9bce7a8c555af21 100644 --- a/test/global/binstubs/explicit_executables_test.dart +++ b/test/global/binstubs/explicit_executables_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/name_collision_test.dart b/test/global/binstubs/name_collision_test.dart index 57f6a8ecec9ae61f06bc4cf4fdc36fb236e8be8b..e07975a064ca7539abf4ddeaf4b454e623d4e17a 100644 --- a/test/global/binstubs/name_collision_test.dart +++ b/test/global/binstubs/name_collision_test.dart @@ -7,7 +7,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/name_collision_with_overwrite_test.dart b/test/global/binstubs/name_collision_with_overwrite_test.dart index 6afa39eb9948af0b5e382a8ad3cab909cbb75233..375252518b7e49a9c2ab9aa292466c6b52ff7f4a 100644 --- a/test/global/binstubs/name_collision_with_overwrite_test.dart +++ b/test/global/binstubs/name_collision_with_overwrite_test.dart @@ -7,7 +7,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/no_executables_flag_test.dart b/test/global/binstubs/no_executables_flag_test.dart index 1eb1bb81193bdbca31b232375a9768921f5cfc5b..4b1bf331cf54f0f90646a03c029763628fb5ce04 100644 --- a/test/global/binstubs/no_executables_flag_test.dart +++ b/test/global/binstubs/no_executables_flag_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..e2f276d1cea3329240aa81101b0c18edf46b1967 --- /dev/null +++ b/test/global/binstubs/outdated_binstub_runs_pub_global_test.dart @@ -0,0 +1,49 @@ +// Copyright (c) 2014, 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 'package:path/path.dart' as p; +import 'package:scheduled_test/scheduled_process.dart'; +import 'package:scheduled_test/scheduled_stream.dart'; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; +import 'utils.dart'; + +main() { + initConfig(); + integration("an outdated binstub runs 'pub global run'", () { + servePackages((builder) { + builder.serve("foo", "1.0.0", pubspec: { + "executables": { + "foo-script": "script" + } + }, contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.dir('bin', [d.outOfDateSnapshot('script.dart.snapshot')]) + ]) + ]) + ]).create(); + + var process = new ScheduledProcess.start( + p.join(sandboxDir, cachePath, "bin", binStubName("foo-script")), + ["arg1", "arg2"], + environment: getEnvironment()); + + process.stdout.expect(consumeThrough("ok")); + process.shouldExit(); + }); +} diff --git a/test/global/binstubs/outdated_snapshot_test.dart b/test/global/binstubs/outdated_snapshot_test.dart new file mode 100644 index 0000000000000000000000000000000000000000..9e6acc9a2a6acf940a4246530287eec6f1523cbc --- /dev/null +++ b/test/global/binstubs/outdated_snapshot_test.dart @@ -0,0 +1,58 @@ +// Copyright (c) 2014, 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 'package:path/path.dart' as p; +import 'package:scheduled_test/scheduled_process.dart'; +import 'package:scheduled_test/scheduled_stream.dart'; +import 'package:scheduled_test/scheduled_test.dart'; + +import '../../../lib/src/io.dart'; +import '../../descriptor.dart' as d; +import '../../test_pub.dart'; +import 'utils.dart'; + +main() { + initConfig(); + integration("a binstub runs 'pub global run' for an outdated snapshot", () { + servePackages((builder) { + builder.serve("foo", "1.0.0", pubspec: { + "executables": { + "foo-script": "script" + } + }, contents: [ + d.dir("bin", [ + d.file("script.dart", "main(args) => print('ok \$args');") + ]) + ]); + }); + + schedulePub(args: ["global", "activate", "foo"]); + + d.dir(cachePath, [ + d.dir('global_packages', [ + d.dir('foo', [ + d.dir('bin', [d.outOfDateSnapshot('script.dart.snapshot')]) + ]) + ]) + ]).create(); + + var process = new ScheduledProcess.start( + p.join(sandboxDir, cachePath, "bin", binStubName("foo-script")), + ["arg1", "arg2"], + environment: getEnvironment()); + + process.stderr.expect(startsWith("Wrong script snapshot version")); + process.stdout.expect(consumeThrough("ok [arg1, arg2]")); + process.shouldExit(); + + d.dir(cachePath, [ + d.dir('global_packages/foo/bin', [ + d.binaryMatcherFile('script.dart.snapshot', isNot(equals( + readBinaryFile(testAssetPath('out-of-date.snapshot'))))) + ]) + ]).validate(); + }); +} diff --git a/test/global/binstubs/path_package_test.dart b/test/global/binstubs/path_package_test.dart index 1141131a993885ef722b6cc1868ce12e13495c4d..901c0f88d998101e1c59583f0a62eb0558454422 100644 --- a/test/global/binstubs/path_package_test.dart +++ b/test/global/binstubs/path_package_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/reactivate_removes_old_executables_test.dart b/test/global/binstubs/reactivate_removes_old_executables_test.dart index 748ce04a7965a2ab97115793f658574ee5aaa718..bf1365c3fccad330cc784eb1b08d5a2a94095152 100644 --- a/test/global/binstubs/reactivate_removes_old_executables_test.dart +++ b/test/global/binstubs/reactivate_removes_old_executables_test.dart @@ -6,7 +6,6 @@ import 'package:scheduled_test/scheduled_test.dart'; import '../../descriptor.dart' as d; import '../../test_pub.dart'; -import 'utils.dart'; main() { initConfig(); diff --git a/test/global/binstubs/utils.dart b/test/global/binstubs/utils.dart index e52a1ebdfcd0798cb3595d6317ae0aba7930ccaf..ca70df8146bbc111fe445455f20025d69b395e60 100644 --- a/test/global/binstubs/utils.dart +++ b/test/global/binstubs/utils.dart @@ -2,12 +2,22 @@ // 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:async'; import 'dart:io'; -/// Returns the name of the shell script for a binstub named [name]. -/// -/// Adds a ".bat" extension on Windows. -binStubName(String name) { - if (Platform.operatingSystem == "windows") return "$name.bat"; - return name; +import 'package:path/path.dart' as p; + +import '../../test_pub.dart'; + +/// The buildbots do not have the Dart SDK (containing "dart" and "pub") on +/// their PATH, so we need to spawn the binstub process with a PATH that +/// explicitly includes it. +Future<Map> getEnvironment() async { + var binDir = p.dirname(Platform.executable); + var separator = Platform.operatingSystem == "windows" ? ";" : ":"; + var path = "${Platform.environment["PATH"]}$separator$binDir"; + + var environment = await getPubTestEnvironment(); + environment["PATH"] = path; + return environment; } diff --git a/test/test_pub.dart b/test/test_pub.dart index 6a6fec12e1520cd4da2a21641bd2816c0303d528..51eb18a0fcb4290a1d83dfa1e8df1616a31a3208 100644 --- a/test/test_pub.dart +++ b/test/test_pub.dart @@ -486,7 +486,7 @@ String _pathInSandbox(String relPath) { } /// Gets the environment variables used to run pub in a test context. -Map getPubTestEnvironment([String tokenEndpoint]) { +Future<Map> getPubTestEnvironment([String tokenEndpoint]) async { var environment = {}; environment['_PUB_TESTING'] = 'true'; environment['PUB_CACHE'] = _pathInSandbox(cachePath); @@ -498,6 +498,13 @@ Map getPubTestEnvironment([String tokenEndpoint]) { environment['_PUB_TEST_TOKEN_ENDPOINT'] = tokenEndpoint.toString(); } + if (_hasServer) { + return port.then((p) { + environment['PUB_HOSTED_URL'] = "http://localhost:$p"; + return environment; + }); + } + return environment; } @@ -534,20 +541,9 @@ ScheduledProcess startPub({List args, Future<String> tokenEndpoint, dartArgs.addAll(args); if (tokenEndpoint == null) tokenEndpoint = new Future.value(); - var environmentFuture = tokenEndpoint.then((tokenEndpoint) { - var pubEnvironment = getPubTestEnvironment(tokenEndpoint); - - // If there is a server running, tell pub what its URL is so hosted - // dependencies will look there. - if (_hasServer) { - return port.then((p) { - pubEnvironment['PUB_HOSTED_URL'] = "http://localhost:$p"; - return pubEnvironment; - }); - } - - return pubEnvironment; - }).then((pubEnvironment) { + var environmentFuture = tokenEndpoint + .then((tokenEndpoint) => getPubTestEnvironment(tokenEndpoint)) + .then((pubEnvironment) { if (environment != null) pubEnvironment.addAll(environment); return pubEnvironment; }); @@ -854,6 +850,11 @@ Map packageVersionApiMap(Map pubspec, {bool full: false}) { return map; } +/// Returns the name of the shell script for a binstub named [name]. +/// +/// Adds a ".bat" extension on Windows. +String binStubName(String name) => Platform.isWindows ? '$name.bat' : name; + /// Compares the [actual] output from running pub with [expected]. /// /// If [expected] is a [String], ignores leading and trailing whitespace