extern crate cargotest;
extern crate hamcrest;

use std::fs;
use std::env;

use cargotest::{is_nightly, rustc_host};
use cargotest::support::{project, execs};
use hamcrest::assert_that;

#[test]
fn plugin_to_the_max() {
    if !is_nightly() { return }

    let foo = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [lib]
            name = "foo_lib"

            [dependencies.bar]
            path = "../bar"
        "#)
        .file("src/main.rs", r#"
            #![feature(plugin)]
            #![plugin(bar)]
            extern crate foo_lib;

            fn main() { foo_lib::foo(); }
        "#)
        .file("src/foo_lib.rs", r#"
            #![feature(plugin)]
            #![plugin(bar)]

            pub fn foo() {}
        "#)
        .build();
    let _bar = project("bar")
        .file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [lib]
            name = "bar"
            plugin = true

            [dependencies.baz]
            path = "../baz"
        "#)
        .file("src/lib.rs", r#"
            #![feature(plugin_registrar, rustc_private)]

            extern crate rustc_plugin;
            extern crate baz;

            use rustc_plugin::Registry;

            #[plugin_registrar]
            pub fn foo(_reg: &mut Registry) {
                println!("{}", baz::baz());
            }
        "#)
        .build();
    let _baz = project("baz")
        .file("Cargo.toml", r#"
            [package]
            name = "baz"
            version = "0.0.1"
            authors = []

            [lib]
            name = "baz"
            crate_type = ["dylib"]
        "#)
        .file("src/lib.rs", "pub fn baz() -> i32 { 1 }")
        .build();

    assert_that(foo.cargo("build"),
                execs().with_status(0));
    assert_that(foo.cargo("doc"),
                execs().with_status(0));
}

#[test]
fn plugin_with_dynamic_native_dependency() {
    if !is_nightly() { return }

    let workspace = project("ws")
        .file("Cargo.toml", r#"
            [workspace]
            members = ["builder", "foo"]
        "#)
        .build();

    let build = project("ws/builder")
        .file("Cargo.toml", r#"
            [package]
            name = "builder"
            version = "0.0.1"
            authors = []

            [lib]
            name = "builder"
            crate-type = ["dylib"]
        "#)
        .file("src/lib.rs", r#"
            #[no_mangle]
            pub extern fn foo() {}
        "#)
        .build();

    let foo = project("ws/foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [dependencies.bar]
            path = "bar"
        "#)
        .file("src/main.rs", r#"
            #![feature(plugin)]
            #![plugin(bar)]

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

            [lib]
            name = "bar"
            plugin = true
        "#)
        .file("bar/build.rs", r#"
            use std::path::PathBuf;
            use std::env;

            fn main() {
                let src = PathBuf::from(env::var("SRC").unwrap());
                println!("cargo:rustc-flags=-L {}/deps", src.parent().unwrap().display());
            }
        "#)
        .file("bar/src/lib.rs", r#"
            #![feature(plugin_registrar, rustc_private)]
            extern crate rustc_plugin;

            use rustc_plugin::Registry;

            #[cfg_attr(not(target_env = "msvc"), link(name = "builder"))]
            #[cfg_attr(target_env = "msvc", link(name = "builder.dll"))]
            extern { fn foo(); }

            #[plugin_registrar]
            pub fn bar(_reg: &mut Registry) {
                unsafe { foo() }
            }
        "#)
        .build();

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

    let src = workspace.root().join("target/debug");
    let lib = fs::read_dir(&src).unwrap().map(|s| s.unwrap().path()).find(|lib| {
        let lib = lib.file_name().unwrap().to_str().unwrap();
        lib.starts_with(env::consts::DLL_PREFIX) &&
            lib.ends_with(env::consts::DLL_SUFFIX)
    }).unwrap();

    assert_that(foo.cargo("build").env("SRC", &lib).arg("-v"),
                execs().with_status(0));
}

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

            [lib]
            name = "foo"
            plugin = true
            doctest = false
        "#)
        .file("build.rs", "fn main() {}")
        .file("src/lib.rs", "")
        .file("tests/it_works.rs", "")
        .build();

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

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

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

            [lib]
            name = "bar"
            plugin = true
        "#)
        .file("bar/src/lib.rs", r#"
            pub fn bar() {}
        "#)
        .build();

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

// See #1515
#[test]
fn native_plugin_dependency_with_custom_ar_linker() {
    let target = rustc_host();

    let _foo = project("foo")
        .file("Cargo.toml", r#"
            [package]
            name = "foo"
            version = "0.0.1"
            authors = []

            [lib]
            plugin = true
        "#)
        .file("src/lib.rs", "")
        .build();

    let bar = project("bar")
        .file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [dependencies.foo]
            path = "../foo"
        "#)
        .file("src/lib.rs", "")
        .file(".cargo/config", &format!(r#"
            [target.{}]
            ar = "nonexistent-ar"
            linker = "nonexistent-linker"
        "#, target))
        .build();

    assert_that(bar.cargo("build").arg("--verbose"),
                execs().with_stderr_contains("\
[COMPILING] foo v0.0.1 ([..])
[RUNNING] `rustc [..] -C ar=nonexistent-ar -C linker=nonexistent-linker [..]`
[ERROR] [..]linker[..]
"));
}

#[test]
fn panic_abort_plugins() {
    if !is_nightly() {
        return
    }

    let p = project("bar")
        .file("Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []

            [profile.dev]
            panic = 'abort'

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

            [lib]
            plugin = true
        "#)
        .file("foo/src/lib.rs", r#"
            #![feature(rustc_private)]
            extern crate syntax;
        "#)
        .build();

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

#[test]
fn shared_panic_abort_plugins() {
    if !is_nightly() {
        return
    }

    let p = project("top")
        .file("Cargo.toml", r#"
            [package]
            name = "top"
            version = "0.0.1"
            authors = []

            [profile.dev]
            panic = 'abort'

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

            [lib]
            plugin = true

            [dependencies]
            bar = { path = "../bar" }
        "#)
        .file("foo/src/lib.rs", r#"
            #![feature(rustc_private)]
            extern crate syntax;
            extern crate bar;
        "#)
        .file("bar/Cargo.toml", r#"
            [package]
            name = "bar"
            version = "0.0.1"
            authors = []
        "#)
        .file("bar/src/lib.rs", "")
        .build();

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