add offline mode (-Z offline) with tests

This commit is contained in:
Pelepeichenko Alexander 2017-12-25 01:32:29 +02:00
parent c4003c4fbb
commit ec5f78f933
14 changed files with 438 additions and 8 deletions

View file

@ -232,6 +232,7 @@ impl Features {
pub struct CliUnstable {
pub print_im_a_teapot: bool,
pub unstable_options: bool,
pub offline: bool,
}
impl CliUnstable {
@ -262,6 +263,7 @@ impl CliUnstable {
match k {
"print-im-a-teapot" => self.print_im_a_teapot = parse_bool(v)?,
"unstable-options" => self.unstable_options = true,
"offline" => self.offline = true,
_ => bail!("unknown `-Z` flag specified: {}", k),
}

View file

@ -722,7 +722,7 @@ fn activate_deps_loop<'a>(mut cx: Context<'a>,
None => return Err(activation_error(&cx, registry, &parent,
&dep,
cx.prev_active(&dep),
&candidates)),
&candidates, config)),
Some(candidate) => candidate,
}
}
@ -788,7 +788,8 @@ fn activation_error(cx: &Context,
parent: &Summary,
dep: &Dependency,
prev_active: &[Summary],
candidates: &[Candidate]) -> CargoError {
candidates: &[Candidate],
config: Option<&Config>) -> CargoError {
if !candidates.is_empty() {
let mut msg = format!("failed to select a version for `{}` \
(required by `{}`):\n\
@ -843,7 +844,7 @@ fn activation_error(cx: &Context,
b.version().cmp(a.version())
});
let msg = if !candidates.is_empty() {
let mut msg = if !candidates.is_empty() {
let versions = {
let mut versions = candidates.iter().take(3).map(|cand| {
cand.version().to_string()
@ -886,6 +887,13 @@ fn activation_error(cx: &Context,
dep.version_req())
};
if let Some(config) = config {
if config.cli_unstable().offline {
msg.push_str("\nperhaps an error occurred because you are using \
the offline mode");
}
}
format_err!("{}", msg)
}

View file

@ -37,6 +37,10 @@ pub fn update_lockfile(ws: &Workspace, opts: &UpdateOptions)
bail!("you can't generate a lockfile for an empty workspace.")
}
if opts.config.cli_unstable().offline {
bail!("you can't update in the offline mode");
}
let previous_resolve = match ops::load_pkg_lockfile(ws)? {
Some(resolve) => resolve,
None => return generate_lockfile(ws),

View file

@ -76,6 +76,10 @@ pub fn write_pkg_lockfile(ws: &Workspace, resolve: &Resolve) -> CargoResult<()>
}
if !ws.config().lock_update_allowed() {
if ws.config().cli_unstable().offline {
bail!("can't update in the offline mode");
}
let flag = if ws.config().network_allowed() {"--locked"} else {"--frozen"};
bail!("the lock file needs to be updated but {} was passed to \
prevent this", flag);

View file

@ -263,10 +263,13 @@ pub fn registry(config: &Config,
/// Create a new HTTP handle with appropriate global configuration for cargo.
pub fn http_handle(config: &Config) -> CargoResult<Easy> {
if !config.network_allowed() {
if config.frozen() {
bail!("attempting to make an HTTP request, but --frozen was \
specified")
}
if !config.network_allowed() {
bail!("can't make HTTP request in the offline mode")
}
// The timeout option for libcurl by default times out the entire transfer,
// but we probably don't want this. Instead we only set timeouts for the

View file

@ -151,6 +151,10 @@ impl<'cfg> Source for GitSource<'cfg> {
let db_path = lock.parent().join("db").join(&self.ident);
if self.config.cli_unstable().offline && !db_path.exists() {
bail!("can't checkout from '{}': you are in the offline mode", self.remote.url());
}
// Resolve our reference to an actual revision, and check if the
// database already has that revision. If it does, we just load a
// database pinned at that revision, and if we don't we issue an update
@ -159,7 +163,7 @@ impl<'cfg> Source for GitSource<'cfg> {
let should_update = actual_rev.is_err() ||
self.source_id.precise().is_none();
let (db, actual_rev) = if should_update {
let (db, actual_rev) = if should_update && !self.config.cli_unstable().offline {
self.config.shell().status("Updating",
format!("git repository `{}`", self.remote.url()))?;

View file

@ -615,10 +615,13 @@ pub fn fetch(repo: &mut git2::Repository,
url: &Url,
refspec: &str,
config: &Config) -> CargoResult<()> {
if !config.network_allowed() {
if config.frozen() {
bail!("attempting to update a git repository, but --frozen \
was specified")
}
if !config.network_allowed() {
bail!("can't update a git repository in the offline mode")
}
// If we're fetching from github, attempt github's special fast path for
// testing if we've already got an up-to-date copy of the repository

View file

@ -110,13 +110,20 @@ impl<'cfg> RegistryIndex<'cfg> {
.map(|s| s.trim())
.filter(|l| !l.is_empty());
let online = !self.config.cli_unstable().offline;
// Attempt forwards-compatibility on the index by ignoring
// everything that we ourselves don't understand, that should
// allow future cargo implementations to break the
// interpretation of each line here and older cargo will simply
// ignore the new lines.
ret.extend(lines.filter_map(|line| {
self.parse_registry_package(line).ok()
self.parse_registry_package(line).ok().and_then(|v|{
if online || load.is_crate_downloaded(v.0.package_id()) {
Some(v)
} else {
None
}
})
}));
Ok(())

View file

@ -249,6 +249,8 @@ pub trait RegistryData {
fn download(&mut self,
pkg: &PackageId,
checksum: &str) -> CargoResult<FileLock>;
fn is_crate_downloaded(&self, _pkg: &PackageId) -> bool { true }
}
mod index;

View file

@ -153,6 +153,10 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
}
fn update_index(&mut self) -> CargoResult<()> {
if self.config.cli_unstable().offline {
return Ok(());
}
// Ensure that we'll actually be able to acquire an HTTP handle later on
// once we start trying to download crates. This will weed out any
// problems with `.cargo/config` configuration related to HTTP.
@ -258,6 +262,20 @@ impl<'cfg> RegistryData for RemoteRegistry<'cfg> {
dst.seek(SeekFrom::Start(0))?;
Ok(dst)
}
fn is_crate_downloaded(&self, pkg: &PackageId) -> bool {
let filename = format!("{}-{}.crate", pkg.name(), pkg.version());
let path = Path::new(&filename);
if let Ok(dst) = self.cache_path.open_ro(path, self.config, &filename) {
if let Ok(meta) = dst.file().metadata(){
return meta.len() > 0;
}
}
false
}
}
impl<'cfg> Drop for RemoteRegistry<'cfg> {

View file

@ -504,7 +504,11 @@ impl Config {
}
pub fn network_allowed(&self) -> bool {
!self.frozen
!self.frozen() && !self.cli_unstable().offline
}
pub fn frozen(&self) -> bool {
self.frozen
}
pub fn lock_update_allowed(&self) -> bool {

View file

@ -15,6 +15,7 @@ use cargotest::support::paths::{CargoPathExt,root};
use cargotest::support::{ProjectBuilder};
use cargotest::support::{project, execs, main_file, basic_bin_manifest};
use cargotest::support::registry::Package;
use cargotest::ChannelChanger;
use hamcrest::{assert_that, existing_file, existing_dir, is_not};
use tempdir::TempDir;
@ -829,6 +830,218 @@ Did you mean `a`?"));
Did you mean `a`?"));
}
#[test]
fn cargo_compile_path_with_offline() {
let p = project("foo")
.file("Cargo.toml", r#"
[package]
name = "foo"
version = "0.0.1"
authors = []
[dependencies.bar]
path = "bar"
"#)
.file("src/lib.rs", "")
.file("bar/Cargo.toml", r#"
[package]
name = "bar"
version = "0.0.1"
authors = []
"#)
.file("bar/src/lib.rs", "")
.build();
assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(0));
}
#[test]
fn cargo_compile_with_downloaded_dependency_with_offline() {
Package::new("present_dep", "1.2.3")
.file("Cargo.toml", r#"
[project]
name = "present_dep"
version = "1.2.3"
"#)
.file("src/lib.rs", "")
.publish();
{
// make package downloaded
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.1.0"
[dependencies]
present_dep = "1.2.3"
"#)
.file("src/lib.rs", "")
.build();
assert_that(p.cargo("build"),execs().with_status(0));
}
let p2 = project("bar")
.file("Cargo.toml", r#"
[project]
name = "bar"
version = "0.1.0"
[dependencies]
present_dep = "1.2.3"
"#)
.file("src/lib.rs", "")
.build();
assert_that(p2.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(0)
.with_stderr_does_not_contain("Updating registry")
.with_stderr_does_not_contain("Downloading")
.with_stderr(format!("\
[COMPILING] present_dep v1.2.3
[COMPILING] bar v0.1.0 ({url})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
url = p2.url())));
}
#[test]
fn cargo_compile_offline_not_try_update() {
let p = project("bar")
.file("Cargo.toml", r#"
[project]
name = "bar"
version = "0.1.0"
[dependencies]
not_cached_dep = "1.2.5"
"#)
.file("src/lib.rs", "")
.build();
assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(101)
.with_stderr_does_not_contain("Updating registry")
.with_stderr_does_not_contain("Downloading")
.with_stderr("\
error: no matching package named `not_cached_dep` found (required by `bar`)
location searched: registry `[..]`
version required: ^1.2.5
perhaps an error occurred because you are using the offline mode"));
}
#[test]
fn compile_offline_without_maxvers_cached(){
Package::new("present_dep", "1.2.1").publish();
Package::new("present_dep", "1.2.2").publish();
Package::new("present_dep", "1.2.3")
.file("Cargo.toml", r#"
[project]
name = "present_dep"
version = "1.2.3"
"#)
.file("src/lib.rs", r#"pub fn get_version()->&'static str {"1.2.3"}"#)
.publish();
Package::new("present_dep", "1.2.5")
.file("Cargo.toml", r#"
[project]
name = "present_dep"
version = "1.2.5"
"#)
.file("src/lib.rs", r#"pub fn get_version(){"1.2.5"}"#)
.publish();
{
// make package cached
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.1.0"
[dependencies]
present_dep = "=1.2.3"
"#)
.file("src/lib.rs", "")
.build();
assert_that(p.cargo("build"),execs().with_status(0));
}
let p2 = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.1.0"
[dependencies]
present_dep = "1.2"
"#)
.file("src/main.rs", "\
extern crate present_dep;
fn main(){
println!(\"{}\", present_dep::get_version());
}")
.build();
assert_that(p2.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(0)
.with_stderr(format!("\
[COMPILING] present_dep v1.2.3
[COMPILING] foo v0.1.0 ({url})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
url = p2.url())));
assert_that(process(&p2.bin("foo")),
execs().with_status(0).with_stdout("1.2.3"));
}
#[test]
fn compile_offline_while_transitive_dep_not_cached() {
let bar = Package::new("bar", "1.0.0");
let bar_path = bar.archive_dst();
bar.publish();
let mut content = Vec::new();
let mut file = File::open(bar_path.clone()).ok().unwrap();
let _ok = file.read_to_end(&mut content).ok().unwrap();
drop(file);
drop(File::create(bar_path.clone()).ok().unwrap() );
Package::new("foo", "0.1.0").dep("bar", "1.0.0").publish();
let p = project("transitive_load_test")
.file("Cargo.toml", r#"
[project]
name = "transitive_load_test"
version = "0.0.1"
[dependencies]
foo = "0.1.0"
"#)
.file("src/main.rs", "fn main(){}")
.build();
// simulate download foo, but fail to download bar
let _out = p.cargo("build").exec_with_output();
drop( File::create(bar_path).ok().unwrap().write_all(&content) );
assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(101)
.with_stderr_does_not_contain("Updating registry")
.with_stderr_does_not_contain("Downloading")
.with_stderr("\
error: no matching package named `bar` found (required by `foo`)
location searched: registry `[..]`
version required: = 1.0.0
perhaps an error occurred because you are using the offline mode"));
}
#[test]
fn compile_path_dep_then_change_version() {
let p = project("foo")

View file

@ -15,7 +15,9 @@ use cargo::util::process;
use cargotest::sleep_ms;
use cargotest::support::paths::{self, CargoPathExt};
use cargotest::support::{git, project, execs, main_file, path2url};
use cargotest::ChannelChanger;
use hamcrest::{assert_that,existing_file};
use hamcrest::matchers::regex::matches_regex;
#[test]
fn cargo_compile_simple_git_dep() {
@ -75,6 +77,142 @@ fn cargo_compile_simple_git_dep() {
execs().with_stdout("hello world\n"));
}
#[test]
fn cargo_compile_forbird_git_httpsrepo_offline() {
let p = project("need_remote_repo")
.file("Cargo.toml", r#"
[project]
name = "need_remote_repo"
version = "0.5.0"
authors = ["chabapok@example.com"]
[dependencies.dep1]
git = 'https://github.com/some_user/dep1.git'
"#)
.file("src/main.rs", "")
.build();
assert_that(p.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(101).
with_stderr_does_not_contain("[UPDATING] git repository [..]").
with_stderr("\
error: failed to load source for a dependency on `dep1`
Caused by:
Unable to update https://github.com/some_user/dep1.git
Caused by:
can't checkout from 'https://github.com/some_user/dep1.git': you are in the offline mode"));
}
#[test]
fn cargo_compile_offline_with_cached_git_dep() {
let git_project = git::new("dep1", |project| {
project
.file("Cargo.toml", r#"
[project]
name = "dep1"
version = "0.5.0"
authors = ["chabapok@example.com"]
[lib]
name = "dep1""#)
.file("src/lib.rs", r#"
pub static COOL_STR:&str = "cached git repo rev1";
"#)
}).unwrap();
let repo = git2::Repository::open(&git_project.root()).unwrap();
let rev1 = repo.revparse_single("HEAD").unwrap().id();
// Commit the changes and make sure we trigger a recompile
File::create(&git_project.root().join("src/lib.rs")).unwrap().write_all(br#"
pub static COOL_STR:&str = "cached git repo rev2";
"#).unwrap();
git::add(&repo);
let rev2 = git::commit(&repo);
{
// cache to regisrty rev1 and rev2
let prj = project("cache_git_dep")
.file("Cargo.toml", &format!(r#"
[project]
name = "cache_git_dep"
version = "0.5.0"
[dependencies.dep1]
git = '{}'
rev = "{}"
"#, git_project.url(), rev1.clone()))
.file("src/main.rs", "fn main(){}")
.build();
assert_that(prj.cargo("build"), execs().with_status(0));
File::create(&prj.root().join("Cargo.toml")).unwrap().write_all(
&format!(r#"
[project]
name = "cache_git_dep"
version = "0.5.0"
[dependencies.dep1]
git = '{}'
rev = "{}"
"#, git_project.url(), rev2.clone()).as_bytes()
).unwrap();
assert_that(prj.cargo("build"), execs().with_status(0));
}
let project = project("foo")
.file("Cargo.toml", &format!(r#"
[project]
name = "foo"
version = "0.5.0"
[dependencies.dep1]
git = '{}'
"#, git_project.url()))
.file("src/main.rs", &main_file(r#""hello from {}", dep1::COOL_STR"#, &["dep1"]))
.build();
let root = project.root();
let git_root = git_project.root();
assert_that(project.cargo("build").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_stderr(format!("\
[COMPILING] dep1 v0.5.0 ({}#[..])
[COMPILING] foo v0.5.0 ({})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]",
path2url(git_root),
path2url(root)
)));
assert_that(&project.bin("foo"), existing_file());
assert_that(process(&project.bin("foo")),
execs().with_stdout("hello from cached git repo rev2\n"));
drop( File::create(&project.root().join("Cargo.toml")).unwrap()
.write_all(&format!(r#"
[project]
name = "foo"
version = "0.5.0"
[dependencies.dep1]
git = '{}'
rev = "{}"
"#, git_project.url(), rev1).as_bytes()).unwrap() );
let _out = project.cargo("build").masquerade_as_nightly_cargo()
.arg("-Zoffline").exec_with_output();
assert_that(process(&project.bin("foo")),
execs().with_stdout("hello from cached git repo rev1\n"));
}
#[test]
fn cargo_compile_git_dep_branch() {
let project = project("foo");

View file

@ -552,6 +552,26 @@ fn update_lockfile() {
"));
}
#[test]
fn update_offline(){
use cargotest::ChannelChanger;
let p = project("foo")
.file("Cargo.toml", r#"
[project]
name = "foo"
version = "0.0.1"
authors = []
[dependencies]
bar = "*"
"#)
.file("src/main.rs", "fn main() {}")
.build();
assert_that(p.cargo("update").masquerade_as_nightly_cargo().arg("-Zoffline"),
execs().with_status(101).
with_stderr("error: you can't update in the offline mode[..]"));
}
#[test]
fn dev_dependency_not_used() {
let p = project("foo")