]> git.proxmox.com Git - cargo.git/commitdiff
Implement a directory source
authorAlex Crichton <alex@alexcrichton.com>
Tue, 5 Jul 2016 17:28:51 +0000 (10:28 -0700)
committerAlex Crichton <alex@alexcrichton.com>
Mon, 1 Aug 2016 17:14:52 +0000 (10:14 -0700)
This flavor of source is intended to behave like a local registry except that
its contents are unpacked rather than zipped up in `.crate` form. Like with
local registries the only way to use this currently is via the
`.cargo/config`-based source replacement currently, and primarily only to
replace crates.io or other registries at the moment.

A directory source is simply a directory which has many `.crate` files unpacked
inside of it. The directory is not recursively traversed for changes, but rather
it is just required that all elements in the directory are themselves
directories of packages.

This format is more suitable for checking into source trees, and it still
provides guarantees around preventing modification of the original source from
the upstream copy. Each directory in the directory source is required to have a
`.cargo-checksum.json` file indicating the checksum it *would* have had if the
crate had come from the original source as well as all of the sha256 checksums
of all the files in the repo. It is intended that directory sources are
assembled from a separately shipped subcommand (e.g.  `cargo vendor` or `cargo
local-registry`), so these checksum files don't have to be managed manually.

Modification of a directory source is not the intended purpose, and if a
modification is detected then the user is nudged towards solutions like
`[replace]` which are intended for overriding other sources and processing local
modifications.

12 files changed:
src/cargo/core/source.rs
src/cargo/ops/cargo_rustc/fingerprint.rs
src/cargo/sources/config.rs
src/cargo/sources/directory.rs [new file with mode: 0644]
src/cargo/sources/mod.rs
src/cargo/sources/replaced.rs
tests/cargotest/support/registry.rs
tests/cfg.rs
tests/directory.rs [new file with mode: 0644]
tests/install.rs
tests/local-registry.rs
tests/publish.rs

index 93e223a29e554a90230646a13f543a173d9a6552..ed684e954685d721003f76b460950a39d9c03e9c 100644 (file)
@@ -14,6 +14,7 @@ use core::{Package, PackageId, Registry};
 use ops;
 use sources::git;
 use sources::{PathSource, GitSource, RegistrySource, CRATES_IO};
+use sources::DirectorySource;
 use util::{human, Config, CargoResult, ToUrl};
 
 /// A Source finds and downloads remote packages based on names and
@@ -38,6 +39,17 @@ pub trait Source: Registry {
     /// The `pkg` argument is the package which this fingerprint should only be
     /// interested in for when this source may contain multiple packages.
     fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
+
+    /// If this source supports it, verifies the source of the package
+    /// specified.
+    ///
+    /// Note that the source may also have performed other checksum-based
+    /// verification during the `download` step, but this is intended to be run
+    /// just before a crate is compiled so it may perform more expensive checks
+    /// which may not be cacheable.
+    fn verify(&self, _pkg: &PackageId) -> CargoResult<()> {
+        Ok(())
+    }
 }
 
 impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
@@ -52,6 +64,10 @@ impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
     fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
         (**self).fingerprint(pkg)
     }
+
+    fn verify(&self, pkg: &PackageId) -> CargoResult<()> {
+        (**self).verify(pkg)
+    }
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -64,6 +80,8 @@ enum Kind {
     Registry,
     /// represents a local filesystem-based registry
     LocalRegistry,
+    /// represents a directory-based registry
+    Directory,
 }
 
 #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
@@ -172,6 +190,9 @@ impl SourceId {
             SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
                 format!("local-registry+{}", url)
             }
+            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+                format!("directory+{}", url)
+            }
         }
     }
 
@@ -194,6 +215,11 @@ impl SourceId {
         Ok(SourceId::new(Kind::LocalRegistry, url))
     }
 
+    pub fn for_directory(path: &Path) -> CargoResult<SourceId> {
+        let url = try!(path.to_url());
+        Ok(SourceId::new(Kind::Directory, url))
+    }
+
     /// Returns the `SourceId` corresponding to the main repository.
     ///
     /// This is the main cargo registry by default, but it can be overridden in
@@ -253,6 +279,13 @@ impl SourceId {
                 };
                 Box::new(RegistrySource::local(self, &path, config))
             }
+            Kind::Directory => {
+                let path = match self.inner.url.to_file_path() {
+                    Ok(p) => p,
+                    Err(()) => panic!("path sources cannot be remote"),
+                };
+                Box::new(DirectorySource::new(&path, self, config))
+            }
         }
     }
 
@@ -342,6 +375,9 @@ impl fmt::Display for SourceId {
             SourceIdInner { kind: Kind::LocalRegistry, ref url, .. } => {
                 write!(f, "registry {}", url)
             }
+            SourceIdInner { kind: Kind::Directory, ref url, .. } => {
+                write!(f, "dir {}", url)
+            }
         }
     }
 }
index e8ad1880f8a430283f9229c1f1c66037717b608f..91a8b1abb30dd069bdd710400f9bc08007118c02 100644 (file)
@@ -57,6 +57,15 @@ pub fn prepare_target<'a, 'cfg>(cx: &mut Context<'a, 'cfg>,
     let compare = compare_old_fingerprint(&loc, &*fingerprint);
     log_compare(unit, &compare);
 
+    if compare.is_err() {
+        let source_id = unit.pkg.package_id().source_id();
+        let sources = cx.packages.sources();
+        let source = try!(sources.get(source_id).chain_error(|| {
+            internal("missing package source")
+        }));
+        try!(source.verify(unit.pkg.package_id()));
+    }
+
     let root = cx.out_dir(unit);
     let mut missing_outputs = false;
     if unit.profile.doc {
index 3be5156452c90badda2e3f12743696f1c030cca5..e40b6a7d546c3688ce64160325b3fe53ac60ccd3 100644 (file)
@@ -137,6 +137,15 @@ a lock file compatible with `{orig}` cannot be generated in this situation
             path.push(s);
             srcs.push(try!(SourceId::for_local_registry(&path)));
         }
+        if let Some(val) = table.get("directory") {
+            let (s, path) = try!(val.string(&format!("source.{}.directory",
+                                                     name)));
+            let mut path = path.to_path_buf();
+            path.pop();
+            path.pop();
+            path.push(s);
+            srcs.push(try!(SourceId::for_directory(&path)));
+        }
 
         let mut srcs = srcs.into_iter();
         let src = try!(srcs.next().chain_error(|| {
diff --git a/src/cargo/sources/directory.rs b/src/cargo/sources/directory.rs
new file mode 100644 (file)
index 0000000..84a9501
--- /dev/null
@@ -0,0 +1,153 @@
+use std::collections::HashMap;
+use std::fmt::{self, Debug, Formatter};
+use std::fs::File;
+use std::io::Read;
+use std::path::{Path, PathBuf};
+
+use rustc_serialize::hex::ToHex;
+use rustc_serialize::json;
+
+use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
+use sources::PathSource;
+use util::{CargoResult, human, ChainError, Config, Sha256};
+use util::paths;
+
+pub struct DirectorySource<'cfg> {
+    id: SourceId,
+    root: PathBuf,
+    packages: HashMap<PackageId, (Package, Checksum)>,
+    config: &'cfg Config,
+}
+
+#[derive(RustcDecodable)]
+struct Checksum {
+    package: String,
+    files: HashMap<String, String>,
+}
+
+impl<'cfg> DirectorySource<'cfg> {
+    pub fn new(path: &Path, id: &SourceId, config: &'cfg Config)
+               -> DirectorySource<'cfg> {
+        DirectorySource {
+            id: id.clone(),
+            root: path.to_path_buf(),
+            config: config,
+            packages: HashMap::new(),
+        }
+    }
+}
+
+impl<'cfg> Debug for DirectorySource<'cfg> {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        write!(f, "DirectorySource {{ root: {:?} }}", self.root)
+    }
+}
+
+impl<'cfg> Registry for DirectorySource<'cfg> {
+    fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
+        let packages = self.packages.values().map(|p| &p.0);
+        let matches = packages.filter(|pkg| dep.matches(pkg.summary()));
+        let summaries = matches.map(|pkg| pkg.summary().clone());
+        Ok(summaries.collect())
+    }
+
+    fn supports_checksums(&self) -> bool {
+        true
+    }
+}
+
+impl<'cfg> Source for DirectorySource<'cfg> {
+    fn update(&mut self) -> CargoResult<()> {
+        self.packages.clear();
+        let entries = try!(self.root.read_dir().chain_error(|| {
+            human(format!("failed to read root of directory source: {}",
+                          self.root.display()))
+        }));
+
+        for entry in entries {
+            let entry = try!(entry);
+            let path = entry.path();
+            let mut src = PathSource::new(&path,
+                                          &self.id,
+                                          self.config);
+            try!(src.update());
+            let pkg = try!(src.root_package());
+
+            let cksum_file = path.join(".cargo-checksum.json");
+            let cksum = try!(paths::read(&path.join(cksum_file)).chain_error(|| {
+                human(format!("failed to load checksum `.cargo-checksum.json` \
+                               of {} v{}",
+                              pkg.package_id().name(),
+                              pkg.package_id().version()))
+
+            }));
+            let cksum: Checksum = try!(json::decode(&cksum).chain_error(|| {
+                human(format!("failed to decode `.cargo-checksum.json` of \
+                               {} v{}",
+                              pkg.package_id().name(),
+                              pkg.package_id().version()))
+            }));
+
+            let mut manifest = pkg.manifest().clone();
+            let summary = manifest.summary().clone();
+            manifest.set_summary(summary.set_checksum(cksum.package.clone()));
+            let pkg = Package::new(manifest, pkg.manifest_path());
+            self.packages.insert(pkg.package_id().clone(), (pkg, cksum));
+        }
+
+        Ok(())
+    }
+
+    fn download(&mut self, id: &PackageId) -> CargoResult<Package> {
+        self.packages.get(id).map(|p| &p.0).cloned().chain_error(|| {
+            human(format!("failed to find package with id: {}", id))
+        })
+    }
+
+    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
+        Ok(pkg.package_id().version().to_string())
+    }
+
+    fn verify(&self, id: &PackageId) -> CargoResult<()> {
+        let (pkg, cksum) = match self.packages.get(id) {
+            Some(&(ref pkg, ref cksum)) => (pkg, cksum),
+            None => bail!("failed to find entry for `{}` in directory source",
+                          id),
+        };
+
+        let mut buf = [0; 16 * 1024];
+        for (file, cksum) in cksum.files.iter() {
+            let mut h = Sha256::new();
+            let file = pkg.root().join(file);
+
+            try!((|| -> CargoResult<()> {
+                let mut f = try!(File::open(&file));
+                loop {
+                    match try!(f.read(&mut buf)) {
+                        0 => return Ok(()),
+                        n => h.update(&buf[..n]),
+                    }
+                }
+            }).chain_error(|| {
+                human(format!("failed to calculate checksum of: {}",
+                              file.display()))
+            }));
+
+            let actual = h.finish().to_hex();
+            if &*actual != cksum {
+                bail!("\
+                    the listed checksum of `{}` has changed:\n\
+                    expected: {}\n\
+                    actual:   {}\n\
+                    \n\
+                    directory sources are not intended to be edited, if \
+                    modifications are required then it is recommended \
+                    that [replace] is used with a forked copy of the \
+                    source\
+                ", file.display(), cksum, actual);
+            }
+        }
+
+        Ok(())
+    }
+}
index 53c573aa38c1db7b743ae68d1a746ca58d78f1ce..ed784e95ab44b44ad8524bbfe5e35c29ab8ce95d 100644 (file)
@@ -1,11 +1,13 @@
-pub use self::path::PathSource;
+pub use self::config::SourceConfigMap;
+pub use self::directory::DirectorySource;
 pub use self::git::GitSource;
+pub use self::path::PathSource;
 pub use self::registry::{RegistrySource, CRATES_IO};
 pub use self::replaced::ReplacedSource;
-pub use self::config::SourceConfigMap;
 
-pub mod path;
+pub mod config;
+pub mod directory;
 pub mod git;
+pub mod path;
 pub mod registry;
-pub mod config;
 pub mod replaced;
index cd0ffd4b1ea050ba434ed204ee9fdcd1b09d7101..7fb95bdf6c87ee9aca2f20c980bcc58ac8ad4da6 100644 (file)
@@ -50,6 +50,11 @@ impl<'cfg> Source for ReplacedSource<'cfg> {
     }
 
     fn fingerprint(&self, id: &Package) -> CargoResult<String> {
-        self.inner.fingerprint(id)
+        self.inner.fingerprint(&id)
+    }
+
+    fn verify(&self, id: &PackageId) -> CargoResult<()> {
+        let id = id.with_source_id(&self.replace_with);
+        self.inner.verify(&id)
     }
 }
index da3e12bbff7ec1bdd3d58593d697b8834f5596cd..12e857211cc2d1e81ed3f4294a6c4f69a4d09bc0 100644 (file)
@@ -132,7 +132,7 @@ impl Package {
         self
     }
 
-    pub fn publish(&self) {
+    pub fn publish(&self) -> String {
         self.make_archive();
 
         // Figure out what we're going to write into the index
@@ -197,6 +197,8 @@ impl Package {
                            "Another commit", &tree,
                            &[&parent]));
         }
+
+        return cksum
     }
 
     fn make_archive(&self) {
@@ -255,7 +257,7 @@ impl Package {
     }
 }
 
-fn cksum(s: &[u8]) -> String {
+pub fn cksum(s: &[u8]) -> String {
     let mut sha = Sha256::new();
     sha.update(s);
     sha.finish().to_hex()
index adbdfc3e67a12dde8377df4d105751dbe4ffa6c4..e8de0101beb41a88bc6f47c51afbb87487b03dba 100644 (file)
@@ -221,8 +221,8 @@ fn works_through_the_registry() {
 [UPDATING] registry [..]
 [DOWNLOADING] [..]
 [DOWNLOADING] [..]
-[COMPILING] foo v0.1.0 ([..])
-[COMPILING] bar v0.1.0 ([..])
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0
 [COMPILING] a v0.0.1 ([..])
 [FINISHED] debug [unoptimized + debuginfo] target(s) in [..]
 "));
diff --git a/tests/directory.rs b/tests/directory.rs
new file mode 100644 (file)
index 0000000..aa68087
--- /dev/null
@@ -0,0 +1,335 @@
+#[macro_use]
+extern crate cargotest;
+extern crate hamcrest;
+extern crate rustc_serialize;
+
+use std::collections::HashMap;
+use std::fs::{self, File};
+use std::io::prelude::*;
+use std::str;
+
+use rustc_serialize::json;
+
+use cargotest::support::{project, execs, ProjectBuilder};
+use cargotest::support::paths;
+use cargotest::support::registry::{Package, cksum};
+use hamcrest::assert_that;
+
+fn setup() {
+    let root = paths::root();
+    t!(fs::create_dir(&root.join(".cargo")));
+    t!(t!(File::create(root.join(".cargo/config"))).write_all(br#"
+        [source.crates-io]
+        registry = 'https://wut'
+        replace-with = 'my-awesome-local-registry'
+
+        [source.my-awesome-local-registry]
+        directory = 'index'
+    "#));
+}
+
+struct VendorPackage {
+    p: Option<ProjectBuilder>,
+    cksum: Checksum,
+}
+
+#[derive(RustcEncodable)]
+struct Checksum {
+    package: String,
+    files: HashMap<String, String>,
+}
+
+impl VendorPackage {
+    fn new(name: &str) -> VendorPackage {
+        VendorPackage {
+            p: Some(project(&format!("index/{}", name))),
+            cksum: Checksum {
+                package: String::new(),
+                files: HashMap::new(),
+            },
+        }
+    }
+
+    fn file(&mut self, name: &str, contents: &str) -> &mut VendorPackage {
+        self.p = Some(self.p.take().unwrap().file(name, contents));
+        self.cksum.files.insert(name.to_string(), cksum(contents.as_bytes()));
+        self
+    }
+
+    fn build(&mut self) {
+        let p = self.p.take().unwrap();
+        let json = json::encode(&self.cksum).unwrap();
+        let p = p.file(".cargo-checksum.json", &json);
+        p.build();
+    }
+}
+
+#[test]
+fn simple() {
+    setup();
+
+    VendorPackage::new("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("src/lib.rs", "pub fn foo() {}")
+        .build();
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn not_there() {
+    setup();
+
+    project("index").build();
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(101).with_stderr("\
+error: no matching package named `foo` found (required by `bar`)
+location searched: [..]
+version required: ^0.1.0
+"));
+}
+
+#[test]
+fn multiple() {
+    setup();
+
+    VendorPackage::new("foo-0.1.0")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("src/lib.rs", "pub fn foo() {}")
+        .file(".cargo-checksum", "")
+        .build();
+
+    VendorPackage::new("foo-0.2.0")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.2.0"
+            authors = []
+        "#)
+        .file("src/lib.rs", "pub fn foo() {}")
+        .file(".cargo-checksum", "")
+        .build();
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn crates_io_then_directory() {
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", r#"
+            extern crate foo;
+
+            pub fn bar() {
+                foo::foo();
+            }
+        "#);
+    p.build();
+
+    let cksum = Package::new("foo", "0.1.0")
+                        .file("src/lib.rs", "pub fn foo() -> u32 { 0 }")
+                        .publish();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0).with_stderr("\
+[UPDATING] registry `[..]`
+[DOWNLOADING] foo v0.1.0 ([..])
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+
+    setup();
+
+    let mut v = VendorPackage::new("foo");
+    v.file("Cargo.toml", r#"
+        [package]
+        name = "foo"
+        version = "0.1.0"
+        authors = []
+    "#);
+    v.file("src/lib.rs", "pub fn foo() -> u32 { 1 }");
+    v.cksum.package = cksum;
+    v.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0).with_stderr("\
+[COMPILING] foo v0.1.0
+[COMPILING] bar v0.1.0 ([..]bar)
+[FINISHED] [..]
+"));
+}
+
+#[test]
+fn crates_io_then_bad_checksum() {
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", "");
+    p.build();
+
+    Package::new("foo", "0.1.0").publish();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(0));
+    setup();
+
+    VendorPackage::new("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(101).with_stderr("\
+error: checksum for `foo v0.1.0` changed between lock files
+
+this could be indicative of a few possible errors:
+
+    * the lock file is corrupt
+    * a replacement source in use (e.g. a mirror) returned a different checksum
+    * the source itself may be corrupt in one way or another
+
+unable to verify that `foo v0.1.0` was the same as before in any situation
+
+"));
+}
+
+#[test]
+fn bad_file_checksum() {
+    setup();
+
+    VendorPackage::new("foo")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "foo"
+            version = "0.1.0"
+            authors = []
+        "#)
+        .file("src/lib.rs", "")
+        .build();
+
+    let mut f = t!(File::create(paths::root().join("index/foo/src/lib.rs")));
+    t!(f.write_all(b"fn foo() -> u32 { 0 }"));
+
+    let p = project("bar")
+        .file("Cargo.toml", r#"
+            [package]
+            name = "bar"
+            version = "0.1.0"
+            authors = []
+
+            [dependencies]
+            foo = "0.1.0"
+        "#)
+        .file("src/lib.rs", "");
+    p.build();
+
+    assert_that(p.cargo("build"),
+                execs().with_status(101).with_stderr("\
+error: the listed checksum of `[..]lib.rs` has changed:
+expected: [..]
+actual:   [..]
+
+directory sources are not intended to be edited, if modifications are \
+required then it is recommended that [replace] is used with a forked copy of \
+the source
+"));
+}
index 52576d5bb237bd11bc5527a25b0929a7f1d99d59..af6efcfad7fd38f0e7b1219616b84c92c2c08266 100644 (file)
@@ -26,7 +26,7 @@ fn pkg(name: &str, vers: &str) {
             extern crate {};
             fn main() {{}}
         ", name))
-        .publish()
+        .publish();
 }
 
 #[test]
@@ -61,8 +61,6 @@ fn pick_max_version() {
     assert_that(cargo_process("install").arg("foo"),
                 execs().with_status(0).with_stderr(&format!("\
 [UPDATING] registry `[..]`
-[DOWNLOADING] foo v0.0.2 (registry file://[..])
-[COMPILING] foo v0.0.2 (registry file://[..])
 [DOWNLOADING] foo v0.0.2 (registry [..])
 [COMPILING] foo v0.0.2
 [FINISHED] release [optimized] target(s) in [..]
index 906e7cde255e6a11ad825428c124f00b919a8cd1..43bf1fa29e5cdc65d97c1395622faa80ecd4361d 100644 (file)
@@ -53,9 +53,12 @@ fn simple() {
 [UNPACKING] foo v0.0.1 ([..])
 [COMPILING] foo v0.0.1
 [COMPILING] bar v0.0.1 ({dir})
+[FINISHED] [..]
 ",
         dir = p.url())));
-    assert_that(p.cargo("build"), execs().with_status(0).with_stderr(""));
+    assert_that(p.cargo("build"), execs().with_status(0).with_stderr("\
+[FINISHED] [..]
+"));
     assert_that(p.cargo("test"), execs().with_status(0));
 }
 
@@ -90,6 +93,7 @@ fn multiple_versions() {
 [UNPACKING] foo v0.1.0 ([..])
 [COMPILING] foo v0.1.0
 [COMPILING] bar v0.0.1 ({dir})
+[FINISHED] [..]
 ",
         dir = p.url())));
 
@@ -143,6 +147,7 @@ fn multiple_names() {
 [COMPILING] [..]
 [COMPILING] [..]
 [COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
 ",
         dir = p.url())));
 }
@@ -187,6 +192,7 @@ fn interdependent() {
 [COMPILING] foo v0.0.1
 [COMPILING] bar v0.1.0
 [COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
 ",
         dir = p.url())));
 }
@@ -247,6 +253,7 @@ fn path_dep_rewritten() {
 [COMPILING] foo v0.0.1
 [COMPILING] bar v0.1.0
 [COMPILING] local v0.0.1 ({dir})
+[FINISHED] [..]
 ",
         dir = p.url())));
 }
index 6b7b19bb080cb38b5d66c24cab75d3e3ce74cc7e..38ccad219ff2830bdea4c98d20ce3f1496099400 100644 (file)
@@ -355,7 +355,7 @@ fn dry_run() {
     assert_that(p.cargo_process("publish").arg("--dry-run")
                  .arg("--host").arg(registry().to_string()),
                 execs().with_status(0).with_stderr(&format!("\
-[UPDATING] registry `{reg}`
+[UPDATING] registry `[..]`
 [WARNING] manifest has no documentation, [..]
 [PACKAGING] foo v0.0.1 ({dir})
 [VERIFYING] foo v0.0.1 ({dir})
@@ -364,8 +364,7 @@ fn dry_run() {
 [UPLOADING] foo v0.0.1 ({dir})
 [WARNING] aborting upload due to dry run
 ",
-        dir = p.url(),
-        reg = registry())));
+        dir = p.url())));
 
     // Ensure the API request wasn't actually made
     assert!(!upload_path().join("api/v1/crates/new").exists());