//! Tests for rustc plugins.

use cargo_test_support::rustc_host;
use cargo_test_support::{basic_manifest, project};

#[cargo_test(nightly, reason = "plugins are unstable")]
fn plugin_to_the_max() {
    let foo = project()
        .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()
        .at("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(rustc_private)]

                extern crate baz;
                extern crate rustc_driver;

                use rustc_driver::plugin::Registry;

                #[no_mangle]
                pub fn __rustc_plugin_registrar(_reg: &mut Registry) {
                    println!("{}", baz::baz());
                }
            "#,
        )
        .build();
    let _baz = project()
        .at("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();

    foo.cargo("build").run();
    foo.cargo("doc").run();
}

#[cargo_test(nightly, reason = "plugins are unstable")]
fn plugin_with_dynamic_native_dependency() {
    let build = project()
        .at("builder")
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "builder"
                version = "0.0.1"
                authors = []

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

    let foo = project()
        .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::env;
                use std::fs;
                use std::path::PathBuf;

                fn main() {
                    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
                    let root = PathBuf::from(env::var("BUILDER_ROOT").unwrap());
                    let file = format!("{}builder{}",
                        env::consts::DLL_PREFIX,
                        env::consts::DLL_SUFFIX);
                    let src = root.join(&file);
                    let dst = out_dir.join(&file);
                    fs::copy(src, dst).unwrap();
                    if cfg!(target_env = "msvc") {
                        fs::copy(root.join("builder.dll.lib"),
                                 out_dir.join("builder.dll.lib")).unwrap();
                    }
                    println!("cargo:rustc-flags=-L {}", out_dir.display());
                }
            "#,
        )
        .file(
            "bar/src/lib.rs",
            r#"
                #![feature(rustc_private)]

                extern crate rustc_driver;
                use rustc_driver::plugin::Registry;

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

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

    build.cargo("build").run();

    let root = build.root().join("target").join("debug");
    foo.cargo("build -v").env("BUILDER_ROOT", root).run();
}

#[cargo_test]
fn plugin_integration() {
    let p = project()
        .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();

    p.cargo("test -v").run();
}

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

                [dependencies]
                bar = { path = "bar" }
            "#,
        )
        .file("src/lib.rs", "#[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", "pub fn bar() {}")
        .build();

    p.cargo("test -v").run();
}

// See #1515
#[cargo_test]
fn native_plugin_dependency_with_custom_linker() {
    let target = rustc_host();

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

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

    let bar = project()
        .at("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.{}]
                    linker = "nonexistent-linker"
                "#,
                target
            ),
        )
        .build();

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

#[cargo_test(nightly, reason = "requires rustc_private")]
fn panic_abort_plugins() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [profile.dev]
                panic = 'abort'

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

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

    p.cargo("build").run();
}

#[cargo_test(nightly, reason = "requires rustc_private")]
fn shared_panic_abort_plugins() {
    let p = project()
        .file(
            "Cargo.toml",
            r#"
                [package]
                name = "foo"
                version = "0.0.1"
                authors = []

                [profile.dev]
                panic = 'abort'

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

                [lib]
                plugin = true

                [dependencies]
                baz = { path = "../baz" }
            "#,
        )
        .file(
            "bar/src/lib.rs",
            r#"
                #![feature(rustc_private)]
                extern crate rustc_ast;
                extern crate baz;
            "#,
        )
        .file("baz/Cargo.toml", &basic_manifest("baz", "0.0.1"))
        .file("baz/src/lib.rs", "")
        .build();

    p.cargo("build -v").run();
}
