#[macro_use]
extern crate cargotest;
extern crate hamcrest;

use std::fs::File;
use std::io::prelude::*;

use cargotest::support::paths::CargoPathExt;
use cargotest::support::{project, execs};
use hamcrest::assert_that;

#[test]
fn invalid1() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            bar = ["baz"]
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Feature `bar` includes `baz` which is neither a dependency nor another feature
"));
}

#[test]
fn invalid2() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            bar = ["baz"]

            [dependencies.bar]
            path = "foo"
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Features and dependencies cannot have the same name: `bar`
"));
}

#[test]
fn invalid3() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            bar = ["baz"]

            [dependencies.baz]
            path = "foo"
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Feature `bar` depends on `baz` which is not an optional dependency.
Consider adding `optional = true` to the dependency
"));
}

#[test]
fn invalid4() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            features = ["bar"]
        "#)
        .file("src/main.rs", "")
        .file("bar/Cargo.toml", r#"
            [project]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] Package `bar v0.0.1 ([..])` does not have these features: `bar`
"));

    p.change_file("Cargo.toml", r#"
        [project]
        name = "foo"
        version = "0.0.1"
        authors = []
    "#);

    assert_that(p.cargo("build").arg("--features").arg("test"),
                execs().with_status(101).with_stderr("\
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `test`
"));
}

#[test]
fn invalid5() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dev-dependencies.bar]
            path = "bar"
            optional = true
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Dev-dependencies are not allowed to be optional: `bar`
"));
}

#[test]
fn invalid6() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            foo = ["bar/baz"]
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("foo"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Feature `foo` requires a feature of `bar` which is not a dependency
"));
}

#[test]
fn invalid7() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            foo = ["bar/baz"]
            bar = []
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("foo"),
                execs().with_status(101).with_stderr("\
[ERROR] failed to parse manifest at `[..]`

Caused by:
  Feature `foo` requires a feature of `bar` which is not a dependency
"));
}

#[test]
fn invalid8() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            features = ["foo/bar"]
        "#)
        .file("src/main.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").arg("--features").arg("foo"),
                execs().with_status(101).with_stderr("\
[ERROR] feature names may not contain slashes: `foo/bar`
"));
}

#[test]
fn invalid9() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
        "#)
        .file("src/main.rs", "fn main() {}")
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("bar"),
                execs().with_status(0).with_stderr("\
warning: Package `foo v0.0.1 ([..])` does not have feature `bar`. It has a required dependency with \
that name, but only optional dependencies can be used as features. [..]
   Compiling bar v0.0.1 ([..])
   Compiling foo v0.0.1 ([..])
    Finished dev [unoptimized + debuginfo] target(s) in [..] secs
"));
}

#[test]
fn invalid10() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            features = ["baz"]
        "#)
        .file("src/main.rs", "fn main() {}")
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [dependencies.baz]
            path = "baz"
        "#)
        .file("bar/src/lib.rs", "")
        .file("bar/baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/baz/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr("\
warning: Package `bar v0.0.1 ([..])` does not have feature `baz`. It has a required dependency with \
that name, but only optional dependencies can be used as features. [..]
   Compiling baz v0.0.1 ([..])
   Compiling bar v0.0.1 ([..])
   Compiling foo v0.0.1 ([..])
    Finished dev [unoptimized + debuginfo] target(s) in [..] secs
"));
}

#[test]
fn no_transitive_dep_feature_requirement() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.derived]
            path = "derived"

            [features]
            default = ["derived/bar/qux"]
        "#)
        .file("src/main.rs", r#"
            extern crate derived;
            fn main() { derived::test(); }
        "#)
        .file("derived/Cargo.toml", r#"
            [package]
            name = "derived"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"
        "#)
        .file("derived/src/lib.rs", r#"
            extern crate bar;
            pub use bar::test;
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [features]
            qux = []
        "#)
        .file("bar/src/lib.rs", r#"
            #[cfg(feature = "qux")]
            pub fn test() { print!("test"); }
        "#)
        .build();
    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] feature names may not contain slashes: `bar/qux`
"));
}

#[test]
fn no_feature_doesnt_build() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[cfg(feature = "bar")]
            extern crate bar;
            #[cfg(feature = "bar")]
            fn main() { bar::bar(); println!("bar") }
            #[cfg(not(feature = "bar"))]
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
    assert_that(p.process(&p.bin("foo")),
                execs().with_status(0).with_stdout(""));

    assert_that(p.cargo("build").arg("--features").arg("bar"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] bar v0.0.1 ({dir}/bar)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
    assert_that(p.process(&p.bin("foo")),
                execs().with_status(0).with_stdout("bar\n"));
}

#[test]
fn default_feature_pulled_in() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            default = ["bar"]

            [dependencies.bar]
            path = "bar"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[cfg(feature = "bar")]
            extern crate bar;
            #[cfg(feature = "bar")]
            fn main() { bar::bar(); println!("bar") }
            #[cfg(not(feature = "bar"))]
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] bar v0.0.1 ({dir}/bar)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
    assert_that(p.process(&p.bin("foo")),
                execs().with_status(0).with_stdout("bar\n"));

    assert_that(p.cargo("build").arg("--no-default-features"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
    assert_that(p.process(&p.bin("foo")),
                execs().with_status(0).with_stdout(""));
}

#[test]
fn cyclic_feature() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            default = ["default"]
        "#)
        .file("src/main.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(101).with_stderr("\
[ERROR] Cyclic feature dependency: feature `default` depends on itself
"));
}

#[test]
fn cyclic_feature2() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            foo = ["bar"]
            bar = ["foo"]
        "#)
        .file("src/main.rs", "fn main() {}")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stdout(""));
}

#[test]
fn groups_on_groups_on_groups() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            default = ["f1"]
            f1 = ["f2", "bar"]
            f2 = ["f3", "f4"]
            f3 = ["f5", "f6", "baz"]
            f4 = ["f5", "f7"]
            f5 = ["f6"]
            f6 = ["f7"]
            f7 = ["bar"]

            [dependencies.bar]
            path = "bar"
            optional = true

            [dependencies.baz]
            path = "baz"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[allow(unused_extern_crates)]
            extern crate bar;
            #[allow(unused_extern_crates)]
            extern crate baz;
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .file("baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("baz/src/lib.rs", "pub fn baz() {}")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
}

#[test]
fn many_cli_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            optional = true

            [dependencies.baz]
            path = "baz"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[allow(unused_extern_crates)]
            extern crate bar;
            #[allow(unused_extern_crates)]
            extern crate baz;
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .file("baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("baz/src/lib.rs", "pub fn baz() {}")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("bar baz"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
}

#[test]
fn union_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.d1]
            path = "d1"
            features = ["f1"]
            [dependencies.d2]
            path = "d2"
            features = ["f2"]
        "#)
        .file("src/main.rs", r#"
            #[allow(unused_extern_crates)]
            extern crate d1;
            extern crate d2;
            fn main() {
                d2::f1();
                d2::f2();
            }
        "#)
        .file("d1/Cargo.toml", r#"
            [package]
            name = "d1"
            version = "0.0.1"
            authors = []

            [features]
            f1 = ["d2"]

            [dependencies.d2]
            path = "../d2"
            features = ["f1"]
            optional = true
        "#)
        .file("d1/src/lib.rs", "")
        .file("d2/Cargo.toml", r#"
            [package]
            name = "d2"
            version = "0.0.1"
            authors = []

            [features]
            f1 = []
            f2 = []
        "#)
        .file("d2/src/lib.rs", r#"
            #[cfg(feature = "f1")] pub fn f1() {}
            #[cfg(feature = "f2")] pub fn f2() {}
        "#)
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] d2 v0.0.1 ({dir}/d2)
[COMPILING] d1 v0.0.1 ({dir}/d1)
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
}

#[test]
fn many_features_no_rebuilds() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name    = "b"
            version = "0.1.0"
            authors = []

            [dependencies.a]
            path = "a"
            features = ["fall"]
        "#)
        .file("src/main.rs", "fn main() {}")
        .file("a/Cargo.toml", r#"
            [package]
            name    = "a"
            version = "0.1.0"
            authors = []

            [features]
            ftest  = []
            ftest2 = []
            fall   = ["ftest", "ftest2"]
        "#)
        .file("a/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] a v0.1.0 ({dir}/a)
[COMPILING] b v0.1.0 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
    p.root().move_into_the_past();

    assert_that(p.cargo("build").arg("-v"),
                execs().with_status(0).with_stderr("\
[FRESH] a v0.1.0 ([..]/a)
[FRESH] b v0.1.0 ([..])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
"));
}

// Tests that all cmd lines work with `--features ""`
#[test]
fn empty_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []
        "#)
        .file("src/main.rs", "fn main() {}")
        .build();

    assert_that(p.cargo("build").arg("--features").arg(""),
                execs().with_status(0));
}

// Tests that all cmd lines work with `--features ""`
#[test]
fn transitive_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            foo = ["bar/baz"]

            [dependencies.bar]
            path = "bar"
        "#)
        .file("src/main.rs", "
            extern crate bar;
            fn main() { bar::baz(); }
        ")
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [features]
            baz = []
        "#)
        .file("bar/src/lib.rs", r#"
            #[cfg(feature = "baz")]
            pub fn baz() {}
        "#)
        .build();

    assert_that(p.cargo("build").arg("--features").arg("foo"),
                execs().with_status(0));
}

#[test]
fn everything_in_the_lockfile() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            f1 = ["d1/f1"]
            f2 = ["d2"]

            [dependencies.d1]
            path = "d1"
            [dependencies.d2]
            path = "d2"
            optional = true
            [dependencies.d3]
            path = "d3"
            optional = true
        "#)
        .file("src/main.rs", "fn main() {}")
        .file("d1/Cargo.toml", r#"
            [package]
            name = "d1"
            version = "0.0.1"
            authors = []

            [features]
            f1 = []
        "#)
        .file("d1/src/lib.rs", "")
        .file("d2/Cargo.toml", r#"
            [package]
            name = "d2"
            version = "0.0.2"
            authors = []
        "#)
        .file("d2/src/lib.rs", "")
        .file("d3/Cargo.toml", r#"
            [package]
            name = "d3"
            version = "0.0.3"
            authors = []

            [features]
            f3 = []
        "#)
        .file("d3/src/lib.rs", "")
        .build();

    assert_that(p.cargo("fetch"), execs().with_status(0));
    let loc = p.root().join("Cargo.lock");
    let mut lockfile = String::new();
    t!(t!(File::open(&loc)).read_to_string(&mut lockfile));
    assert!(lockfile.contains(r#"name = "d1""#), "d1 not found\n{}", lockfile);
    assert!(lockfile.contains(r#"name = "d2""#), "d2 not found\n{}", lockfile);
    assert!(lockfile.contains(r#"name = "d3""#), "d3 not found\n{}", lockfile);
}

#[test]
fn no_rebuild_when_frobbing_default_feature() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.1.0"
            authors = []

            [dependencies]
            a = { path = "a" }
            b = { path = "b" }
        "#)
        .file("src/lib.rs", "")
        .file("b/Cargo.toml", r#"
            [package]
            name = "b"
            version = "0.1.0"
            authors = []

            [dependencies]
            a = { path = "../a", features = ["f1"], default-features = false }
        "#)
        .file("b/src/lib.rs", "")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.1.0"
            authors = []

            [features]
            default = ["f1"]
            f1 = []
        "#)
        .file("a/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build"), execs().with_status(0));
    assert_that(p.cargo("build"), execs().with_status(0).with_stdout(""));
    assert_that(p.cargo("build"), execs().with_status(0).with_stdout(""));
}

#[test]
fn unions_work_with_no_default_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.1.0"
            authors = []

            [dependencies]
            a = { path = "a" }
            b = { path = "b" }
        "#)
        .file("src/lib.rs", r#"
            extern crate a;
            pub fn foo() { a::a(); }
        "#)
        .file("b/Cargo.toml", r#"
            [package]
            name = "b"
            version = "0.1.0"
            authors = []

            [dependencies]
            a = { path = "../a", features = [], default-features = false }
        "#)
        .file("b/src/lib.rs", "")
        .file("a/Cargo.toml", r#"
            [package]
            name = "a"
            version = "0.1.0"
            authors = []

            [features]
            default = ["f1"]
            f1 = []
        "#)
        .file("a/src/lib.rs", r#"
            #[cfg(feature = "f1")]
            pub fn a() {}
        "#)
        .build();

    assert_that(p.cargo("build"), execs().with_status(0));
    assert_that(p.cargo("build"), execs().with_status(0).with_stdout(""));
    assert_that(p.cargo("build"), execs().with_status(0).with_stdout(""));
}

#[test]
fn optional_and_dev_dep() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name    = "test"
            version = "0.1.0"
            authors = []

            [dependencies]
            foo = { path = "foo", optional = true }
            [dev-dependencies]
            foo = { path = "foo" }
        "#)
        .file("src/lib.rs", "")
        .file("foo/Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.1.0"
            authors = []
        "#)
        .file("foo/src/lib.rs", "")
        .build();

    assert_that(p.cargo("build"),
                execs().with_status(0).with_stderr("\
[COMPILING] test v0.1.0 ([..])
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
"));
}

#[test]
fn activating_feature_activates_dep() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name    = "test"
            version = "0.1.0"
            authors = []

            [dependencies]
            foo = { path = "foo", optional = true }

            [features]
            a = ["foo/a"]
        "#)
        .file("src/lib.rs", "
            extern crate foo;
            pub fn bar() {
                foo::bar();
            }
        ")
        .file("foo/Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.1.0"
            authors = []

            [features]
            a = []
        "#)
        .file("foo/src/lib.rs", r#"
            #[cfg(feature = "a")]
            pub fn bar() {}
        "#)
        .build();

    assert_that(p.cargo("build").arg("--features").arg("a").arg("-v"),
                execs().with_status(0));
}

#[test]
fn dep_feature_in_cmd_line() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.derived]
            path = "derived"
        "#)
        .file("src/main.rs", r#"
            extern crate derived;
            fn main() { derived::test(); }
        "#)
        .file("derived/Cargo.toml", r#"
            [package]
            name = "derived"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "../bar"

            [features]
            default = []
            derived-feat = ["bar/some-feat"]
        "#)
        .file("derived/src/lib.rs", r#"
            extern crate bar;
            pub use bar::test;
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [features]
            some-feat = []
        "#)
        .file("bar/src/lib.rs", r#"
            #[cfg(feature = "some-feat")]
            pub fn test() { print!("test"); }
        "#)
        .build();

    // The foo project requires that feature "some-feat" in "bar" is enabled.
    // Building without any features enabled should fail:
    assert_that(p.cargo("build"),
                execs().with_status(101));

    // We should be able to enable the feature "derived-feat", which enables "some-feat",
    // on the command line. The feature is enabled, thus building should be successful:
    assert_that(p.cargo("build").arg("--features").arg("derived/derived-feat"),
                execs().with_status(0));

    // Trying to enable features of transitive dependencies is an error
    assert_that(p.cargo("build").arg("--features").arg("bar/some-feat"),
                execs().with_status(101).with_stderr("\
[ERROR] Package `foo v0.0.1 ([..])` does not have these features: `bar`
"));

    // Hierarchical feature specification should still be disallowed
    assert_that(p.cargo("build").arg("--features").arg("derived/bar/some-feat"),
                execs().with_status(101).with_stderr("\
[ERROR] feature names may not contain slashes: `bar/some-feat`
"));
}

#[test]
fn all_features_flag_enables_all_features() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [features]
            foo = []
            bar = []

            [dependencies.baz]
            path = "baz"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[cfg(feature = "foo")]
            pub fn foo() {}

            #[cfg(feature = "bar")]
            pub fn bar() {
                extern crate baz;
                baz::baz();
            }

            fn main() {
                foo();
                bar();
            }
        "#)
        .file("baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("baz/src/lib.rs", "pub fn baz() {}")
        .build();

    assert_that(p.cargo("build").arg("--all-features"),
                execs().with_status(0));
}

#[test]
fn many_cli_features_comma_delimited() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            optional = true

            [dependencies.baz]
            path = "baz"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[allow(unused_extern_crates)]
            extern crate bar;
            #[allow(unused_extern_crates)]
            extern crate baz;
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .file("baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("baz/src/lib.rs", "pub fn baz() {}")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("bar,baz"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
}

#[test]
fn many_cli_features_comma_and_space_delimited() {
    let p = project("foo")
        .file("Cargo.toml", r#"
            [project]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
            optional = true

            [dependencies.baz]
            path = "baz"
            optional = true

            [dependencies.bam]
            path = "bam"
            optional = true

            [dependencies.bap]
            path = "bap"
            optional = true
        "#)
        .file("src/main.rs", r#"
            #[allow(unused_extern_crates)]
            extern crate bar;
            #[allow(unused_extern_crates)]
            extern crate baz;
            #[allow(unused_extern_crates)]
            extern crate bam;
            #[allow(unused_extern_crates)]
            extern crate bap;
            fn main() {}
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "pub fn bar() {}")
        .file("baz/Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []
        "#)
        .file("baz/src/lib.rs", "pub fn baz() {}")
        .file("bam/Cargo.toml", r#"
            [package]
            name = "bam"
            version = "0.0.1"
            authors = []
        "#)
        .file("bam/src/lib.rs", "pub fn bam() {}")
        .file("bap/Cargo.toml", r#"
            [package]
            name = "bap"
            version = "0.0.1"
            authors = []
        "#)
        .file("bap/src/lib.rs", "pub fn bap() {}")
        .build();

    assert_that(p.cargo("build").arg("--features").arg("bar,baz bam bap"),
                execs().with_status(0).with_stderr(format!("\
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] ba[..] v0.0.1 ({dir}/ba[..])
[COMPILING] foo v0.0.1 ({dir})
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
", dir = p.url())));
}
