From d970d05f0609acdc449779c5256f04834db7bb4a Mon Sep 17 00:00:00 2001 From: Eric Huss Date: Fri, 24 Aug 2018 15:09:38 -0700 Subject: [PATCH] Add documentation for creating test dependencies. Using `Package` has always been a little unclear to me. I think I understand it now. Maybe this will help. --- src/doc/src/reference/source-replacement.md | 2 +- tests/testsuite/support/git.rs | 52 +++++++++ tests/testsuite/support/mod.rs | 33 ++++++ tests/testsuite/support/registry.rs | 119 ++++++++++++++++++++ 4 files changed, 205 insertions(+), 1 deletion(-) diff --git a/src/doc/src/reference/source-replacement.md b/src/doc/src/reference/source-replacement.md index a70affc7f..6df49dbc7 100644 --- a/src/doc/src/reference/source-replacement.md +++ b/src/doc/src/reference/source-replacement.md @@ -106,7 +106,7 @@ source, but available on the local filesystem (aka vendoring). Local registries are downloaded ahead of time, typically sync'd with a `Cargo.lock`, and are made up of a set of `*.crate` files and an index like the normal registry is. -The primary way to manage and crate local registry sources is through the +The primary way to manage and create local registry sources is through the [`cargo-local-registry`][cargo-local-registry] subcommand, available on crates.io and can be installed with `cargo install cargo-local-registry`. diff --git a/tests/testsuite/support/git.rs b/tests/testsuite/support/git.rs index 3f759f2a8..09bd8bf81 100644 --- a/tests/testsuite/support/git.rs +++ b/tests/testsuite/support/git.rs @@ -1,3 +1,43 @@ +/* +# Git Testing Support + +## Creating a git dependency +`git::new()` is an easy way to create a new git repository containing a +project that you can then use as a dependency. It will automatically add all +the files you specify in the project and commit them to the repository. +Example: + +``` +let git_project = git::new("dep1", |project| { + project + .file("Cargo.toml", &basic_manifest("dep1")) + .file("src/lib.rs", r#"pub fn f() { println!("hi!"); } "#) +}).unwrap(); + +// Use the `url()` method to get the file url to the new repository. +let p = project() + .file("Cargo.toml", &format!(r#" + [package] + name = "a" + version = "1.0.0" + + [dependencies] + dep1 = {{ git = '{}' }} + "#, git_project.url())) + .file("src/lib.rs", "extern crate dep1;") + .build(); +``` + +## Manually creating repositories +`git::repo()` can be used to create a `RepoBuilder` which provides a way of +adding files to a blank repository and committing them. + +If you want to then manipulate the repository (such as adding new files or +tags), you can use `git2::Repository::open()` to open the repository and then +use some of the helper functions in this file to interact with the repository. + +*/ + use std::fs::{self, File}; use std::io::prelude::*; use std::path::{Path, PathBuf}; @@ -16,6 +56,9 @@ pub struct RepoBuilder { pub struct Repository(git2::Repository); +/// Create a `RepoBuilder` to build a new git repository. +/// +/// Call `build()` to finalize and create the repository. pub fn repo(p: &Path) -> RepoBuilder { RepoBuilder::init(p) } @@ -35,12 +78,15 @@ impl RepoBuilder { } } + /// Add a file to the repository. pub fn file(self, path: &str, contents: &str) -> RepoBuilder { let mut me = self.nocommit_file(path, contents); me.files.push(PathBuf::from(path)); me } + /// Add a file that will be left in the working directory, but not added + /// to the repository. pub fn nocommit_file(self, path: &str, contents: &str) -> RepoBuilder { let dst = self.repo.workdir().unwrap().join(path); t!(fs::create_dir_all(dst.parent().unwrap())); @@ -48,6 +94,7 @@ impl RepoBuilder { self } + /// Create the repository and commit the new files. pub fn build(self) -> Repository { { let mut index = t!(self.repo.index()); @@ -80,6 +127,7 @@ impl Repository { } } +/// Create a new git repository with a project. pub fn new(name: &str, callback: F) -> Result where F: FnOnce(ProjectBuilder) -> ProjectBuilder, @@ -98,6 +146,7 @@ where Ok(git_project) } +/// Add all files in the working directory to the git index. pub fn add(repo: &git2::Repository) { // FIXME(libgit2/libgit2#2514): apparently add_all will add all submodules // as well, and then fail b/c they're a directory. As a stopgap, we just @@ -121,6 +170,7 @@ pub fn add(repo: &git2::Repository) { t!(index.write()); } +/// Add a git submodule to the repository. pub fn add_submodule<'a>( repo: &'a git2::Repository, url: &str, @@ -137,6 +187,7 @@ pub fn add_submodule<'a>( s } +/// Commit changes to the git repository. pub fn commit(repo: &git2::Repository) -> git2::Oid { let tree_id = t!(t!(repo.index()).write_tree()); let sig = t!(repo.signature()); @@ -155,6 +206,7 @@ pub fn commit(repo: &git2::Repository) -> git2::Oid { )) } +/// Create a new tag in the git repository. pub fn tag(repo: &git2::Repository, name: &str) { let head = repo.head().unwrap().target().unwrap(); t!(repo.tag( diff --git a/tests/testsuite/support/mod.rs b/tests/testsuite/support/mod.rs index 75579cd2d..fa649c692 100644 --- a/tests/testsuite/support/mod.rs +++ b/tests/testsuite/support/mod.rs @@ -15,6 +15,9 @@ let p = project() .build(); ``` +If you do not specify a `Cargo.toml` manifest using `file()`, one is +automatically created with a project name of `foo` using `basic_manifest()`. + To run cargo, call the `cargo` method and use the `hamcrest` matchers to check the output. @@ -78,6 +81,34 @@ or overwrite a binary immediately after running it. Under some conditions Windows will fail with errors like "directory not empty" or "failed to remove" or "access is denied". +## Specifying Dependencies + +You should not write any tests that use the network such as contacting +crates.io. Typically, simple path dependencies are the easiest way to add a +dependency. Example: + +``` +let p = project() + .file("Cargo.toml", r#" + [package] + name = "foo" + version = "1.0.0" + + [dependencies] + bar = {path = "bar"} + "#) + .file("src/lib.rs", "extern crate bar;") + .file("bar/Cargo.toml", &basic_manifest("bar", "1.0.0")) + .file("bar/src/lib.rs", "") + .build(); +``` + +If you need to test with registry dependencies, see +`support::registry::Package` for creating packages you can depend on. + +If you need to test git dependencies, see `support::git` to create a git +dependency. + */ use std::env; @@ -530,6 +561,8 @@ impl Execs { } /// Verify the exit code from the process. + /// + /// This is not necessary if the expected exit code is `0`. pub fn with_status(mut self, expected: i32) -> Execs { self.expect_exit_code = Some(expected); self diff --git a/tests/testsuite/support/registry.rs b/tests/testsuite/support/registry.rs index cdea8836c..62abb9c00 100644 --- a/tests/testsuite/support/registry.rs +++ b/tests/testsuite/support/registry.rs @@ -49,6 +49,70 @@ pub fn alt_api_url() -> Url { Url::from_file_path(&*alt_api_path()).ok().unwrap() } +/// A builder for creating a new package in a registry. +/// +/// This uses "source replacement" using an automatically generated +/// `.cargo/config` file to ensure that dependencies will use these packages +/// instead of contacting crates.io. See `source-replacement.md` for more +/// details on how source replacement works. +/// +/// Call `publish` to finalize and create the package. +/// +/// If no files are specified, an empty `lib.rs` file is automatically created. +/// +/// The `Cargo.toml` file is automatically generated based on the methods +/// called on `Package` (for example, calling `dep()` will add to the +/// `[dependencies]` automatically). You may also specify a `Cargo.toml` file +/// to override the generated one. +/// +/// This supports different registry types: +/// - Regular source replacement that replaces `crates.io` (the default). +/// - A "local registry" which is a subset for vendoring (see +/// `Package::local`). +/// - An "alternative registry" which requires specifying the registry name +/// (see `Package::alternative`). +/// +/// This does not support "directory sources". See `directory.rs` for +/// `VendorPackage` which implements directory sources. +/// +/// # Example +/// ``` +/// // Publish package "a" depending on "b". +/// Package::new("a", "1.0.0") +/// .dep("b", "1.0.0") +/// .file("src/lib.rs", r#" +/// extern crate b; +/// pub fn f() -> i32 { b::f() * 2 } +/// "#) +/// .publish(); +/// +/// // Publish package "b". +/// Package::new("b", "1.0.0") +/// .file("src/lib.rs", r#" +/// pub fn f() -> i32 { 12 } +/// "#) +/// .publish(); +/// +/// // Create a project that uses package "a". +/// let p = project() +/// .file("Cargo.toml", r#" +/// [package] +/// name = "foo" +/// version = "0.0.1" +/// +/// [dependencies] +/// a = "1.0" +/// "#) +/// .file("src/main.rs", r#" +/// extern crate a; +/// fn main() { println!("{}", a::f()); } +/// "#) +/// .build(); +/// +/// assert_that( +/// p.cargo("run"), +/// execs().with_stdout("24")); +/// ``` pub struct Package { name: String, vers: String, @@ -128,6 +192,8 @@ pub fn init() { } impl Package { + /// Create a new package builder. + /// Call `publish()` to finalize and build the package. pub fn new(name: &str, vers: &str) -> Package { init(); Package { @@ -143,47 +209,92 @@ impl Package { } } + /// Call with `true` to publish in a "local registry". + /// + /// See `source-replacement.html#local-registry-sources` for more details + /// on local registries. See `local_registry.rs` for the tests that use + /// this. pub fn local(&mut self, local: bool) -> &mut Package { self.local = local; self } + /// Call with `true` to publish in an "alternative registry". + /// + /// The name of the alternative registry is called "alternative". + /// + /// See `unstable.html#alternate-registries` for more details on + /// alternative registries. See `alt_registry.rs` for the tests that use + /// this. pub fn alternative(&mut self, alternative: bool) -> &mut Package { self.alternative = alternative; self } + /// Add a file to the package. pub fn file(&mut self, name: &str, contents: &str) -> &mut Package { self.files.push((name.to_string(), contents.to_string())); self } + /// Add an "extra" file that is not rooted within the package. + /// + /// Normal files are automatically placed within a directory named + /// `$PACKAGE-$VERSION`. This allows you to override that behavior, + /// typically for testing invalid behavior. pub fn extra_file(&mut self, name: &str, contents: &str) -> &mut Package { self.extra_files .push((name.to_string(), contents.to_string())); self } + /// Add a normal dependency. Example: + /// ``` + /// [dependencies] + /// foo = {version = "1.0"} + /// ``` pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package { self.full_dep(name, vers, None, "normal", &[], None) } + /// Add a dependency with the given feature. Example: + /// ``` + /// [dependencies] + /// foo = {version = "1.0", "features": ["feat1", "feat2"]} + /// ``` pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package { self.full_dep(name, vers, None, "normal", features, None) } + /// Add a platform-specific dependency. Example: + /// ``` + /// [target.'cfg(windows)'.dependencies] + /// foo = {version = "1.0"} + /// ``` pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package { self.full_dep(name, vers, Some(target), "normal", &[], None) } + /// Add a dependency to an alternative registry. + /// The given registry should be a URI to the alternative registry. pub fn registry_dep(&mut self, name: &str, vers: &str, registry: &str) -> &mut Package { self.full_dep(name, vers, None, "normal", &[], Some(registry)) } + /// Add a dev-dependency. Example: + /// ``` + /// [dev-dependencies] + /// foo = {version = "1.0"} + /// ``` pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package { self.full_dep(name, vers, None, "dev", &[], None) } + /// Add a build-dependency. Example: + /// ``` + /// [build-dependencies] + /// foo = {version = "1.0"} + /// ``` pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package { self.full_dep(name, vers, None, "build", &[], None) } @@ -208,11 +319,18 @@ impl Package { self } + /// Specify whether or not the package is "yanked". pub fn yanked(&mut self, yanked: bool) -> &mut Package { self.yanked = yanked; self } + /// Create the package and place it in the registry. + /// + /// This does not actually use Cargo's publishing system, but instead + /// manually creates the entry in the registry on the filesystem. + /// + /// Returns the checksum for the package. pub fn publish(&self) -> String { self.make_archive(); @@ -358,6 +476,7 @@ impl Package { t!(ar.append(&header, contents.as_bytes())); } + /// Returns the path to the compressed package file. pub fn archive_dst(&self) -> PathBuf { if self.local { registry_path().join(format!("{}-{}.crate", self.name, self.vers))