use mytest::*;
use rstest_test::*;
use std::{fs::File, io::Write, path::Path};
use unindent::Unindent;

const SRC_LIB_RS: &str = {
    const SEP: u8 = std::path::MAIN_SEPARATOR as u8;
    const SRC_LIB_RS: [u8; 11] = [
        SEP, b's', b'r', b'c', SEP, b'l', b'i', b'b', b'.', b'r', b's',
    ];
    unsafe { std::str::from_utf8_unchecked(&SRC_LIB_RS) }
};

pub fn resources(res: impl AsRef<Path>) -> std::path::PathBuf {
    let path = Path::new("rstest").join(res.as_ref());
    super::resources(path)
}

fn prj(res: impl AsRef<Path>) -> Project {
    crate::prj().set_code_file(resources(res))
}

fn run_test(res: impl AsRef<Path>) -> (std::process::Output, String) {
    let prj = prj(res);
    (
        prj.run_tests().unwrap(),
        prj.get_name().to_owned().to_string(),
    )
}

fn prepare_folders_tests(project_path: std::path::PathBuf) {
    let folders_path = project_path.join("folders");

    let folder_with_txt_extension_path = folders_path.join("folder_but.txt");
    let sub_folder_path = folders_path.join("sub");

    std::fs::create_dir(&folders_path).unwrap();
    std::fs::create_dir(&folder_with_txt_extension_path).unwrap();
    std::fs::create_dir(&sub_folder_path).unwrap();

    let text_file_path = sub_folder_path.join("file.txt");
    File::create(text_file_path).unwrap();
}

#[test]
fn files() {
    std::env::set_var("FILES_ENV_VAR", "files");

    let prj = prj("files.rs");
    let files_path = prj.path().join("files");
    let sub_folder = files_path.join("sub");
    let up_sub_folder = prj.path().join("../files_test_sub_folder");
    std::fs::create_dir(&files_path).unwrap();
    std::fs::create_dir(&sub_folder).unwrap();
    std::fs::create_dir(&up_sub_folder).unwrap();

    prepare_folders_tests(prj.path());

    for n in 0..4 {
        let name = format!("element_{}.txt", n);
        let path = files_path.join(&name);
        let mut out = File::create(path).unwrap();
        out.write_all(name.as_bytes()).unwrap();
        out.write_all(b"--\n").unwrap();
        out.write_all(b"something else\n").unwrap();
    }
    let dot = files_path.join(".ignore_me.txt");
    File::create(dot)
        .unwrap()
        .write_all(b".ignore_me.txt--\n")
        .unwrap();
    let exclude = files_path.join("exclude.txt");
    File::create(exclude)
        .unwrap()
        .write_all(b"excluded\n")
        .unwrap();
    let sub = sub_folder.join("sub_dir_file.txt");
    File::create(sub)
        .unwrap()
        .write_all(b"sub_dir_file.txt--\nmore")
        .unwrap();
    let down_from_parent_folder = up_sub_folder.join("from_parent_folder.txt");
    File::create(down_from_parent_folder)
        .unwrap()
        .write_all(b"from_parent_folder.txt--\nmore")
        .unwrap();
    let output = prj.run_tests().unwrap();

    TestResults::new()
        .ok("env_vars::path_1_files_element_0_txt")
        .ok("env_vars::path_2_files_element_1_txt")
        .ok("env_vars::path_3_files_element_2_txt")
        .ok("env_vars::path_4_files_element_3_txt")
        .ok("env_vars::path_5_files_sub_sub_dir_file_txt")
        .ok("env_vars_base_dir::path_1_element_0_txt")
        .ok("env_vars_base_dir::path_2_element_1_txt")
        .ok("env_vars_base_dir::path_3_element_2_txt")
        .ok("env_vars_base_dir::path_4_element_3_txt")
        .ok("env_vars_base_dir::path_5_sub_sub_dir_file_txt")
        .ok("env_vars_unknown::path_1_files_element_0_txt")
        .ok("env_vars_unknown::path_2_files_element_1_txt")
        .ok("env_vars_unknown::path_3_files_element_2_txt")
        .ok("env_vars_unknown::path_4_files_element_3_txt")
        .ok("env_vars_unknown::path_5_files_sub_sub_dir_file_txt")
        .ok("ignore_missing_env_vars::path_1_files_element_0_txt")
        .ok("ignore_missing_env_vars::path_2_files_element_1_txt")
        .ok("ignore_missing_env_vars::path_3_files_element_2_txt")
        .ok("ignore_missing_env_vars::path_4_files_element_3_txt")
        .ok("ignore_missing_env_vars::path_5_files_sub_sub_dir_file_txt")
        .ok("include_bytes::contents_1__UP_files_test_sub_folder_from_parent_folder_txt")
        .ok("include_bytes::contents_2_files_element_0_txt")
        .ok("include_bytes::contents_3_files_element_1_txt")
        .ok("include_bytes::contents_4_files_element_2_txt")
        .ok("include_bytes::contents_5_files_element_3_txt")
        .ok("include_bytes::contents_6_files_sub_sub_dir_file_txt")
        .ok("include_str::contents_1__UP_files_test_sub_folder_from_parent_folder_txt")
        .ok("include_str::contents_2_files_element_0_txt")
        .ok("include_str::contents_3_files_element_1_txt")
        .ok("include_str::contents_4_files_element_2_txt")
        .ok("include_str::contents_5_files_element_3_txt")
        .ok("include_str::contents_6_files_sub_sub_dir_file_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_1_files__ignore_me_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_2_files_element_0_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_3_files_element_1_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_4_files_element_2_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_5_files_element_3_txt")
        .ok("module::pathbuf_need_not_be_in_scope::path_6_files_sub_sub_dir_file_txt")
        .ok("start_with_name::path_1__UP_files_test_sub_folder_from_parent_folder_txt")
        .ok("start_with_name::path_2_files_element_0_txt")
        .ok("start_with_name::path_3_files_element_1_txt")
        .ok("start_with_name::path_4_files_element_2_txt")
        .ok("start_with_name::path_5_files_element_3_txt")
        .ok("start_with_name::path_6_files_sub_sub_dir_file_txt")
        .ok("start_with_name_file_mode::path_1__UP_files_test_sub_folder_from_parent_folder_txt")
        .ok("start_with_name_file_mode::path_2_files_element_0_txt")
        .ok("start_with_name_file_mode::path_3_files_element_1_txt")
        .ok("start_with_name_file_mode::path_4_files_element_2_txt")
        .ok("start_with_name_file_mode::path_5_files_element_3_txt")
        .ok("start_with_name_file_mode::path_6_files_sub_sub_dir_file_txt")
        .ok("start_with_name_with_include::path_1_files__ignore_me_txt")
        .ok("start_with_name_with_include::path_2_files_element_0_txt")
        .ok("start_with_name_with_include::path_3_files_element_1_txt")
        .ok("start_with_name_with_include::path_4_files_element_2_txt")
        .ok("start_with_name_with_include::path_5_files_element_3_txt")
        .ok("start_with_name_with_include::path_6_files_sub_sub_dir_file_txt")
        .ok("ignore_directories::path_1_folders_sub_file_txt")
        .ok("ignore_directories_wildcard::path_1_folders_sub_file_txt")
        .ok("include_directories::path_1_folders_folder_but_txt")
        .ok("include_directories::path_2_folders_sub_file_txt")
        .ok("include_directories_wildcard::path_1_folders_folder_but_txt")
        .ok("include_directories_wildcard::path_2_folders_sub")
        .ok("include_directories_wildcard::path_3_folders_sub_file_txt")
        .assert(output);
}

#[test]
fn mutable_input() {
    let (output, _) = run_test("mut.rs");

    TestResults::new()
        .ok("should_success")
        .fail("should_fail")
        .ok("add_test::case_1")
        .ok("add_test::case_2")
        .fail("add_test::case_3")
        .assert(output);
}

#[test]
fn test_with_return_type() {
    let (output, _) = run_test("return_result.rs");

    TestResults::new()
        .ok("should_success")
        .fail("should_fail")
        .ok("return_type::case_1_should_success")
        .fail("return_type::case_2_should_fail")
        .assert(output);
}

#[test]
fn should_panic() {
    let (output, _) = run_test("panic.rs");

    TestResults::new()
        .ok("should_success")
        .fail("should_fail")
        .ok("fail::case_1")
        .ok("fail::case_2")
        .fail("fail::case_3")
        .assert(output);
}

#[test]
fn should_not_show_a_warning_for_should_panic_attribute() {
    let (output, _) = run_test("panic.rs");

    assert!(!output.stderr.str().contains("unused attribute"));
}

#[test]
fn should_not_show_a_warning_for_values_test_names() {
    let (output, _) = run_test("values_tests_name.rs");

    assert_not_in!(output.stderr.str(), "warning:");
}

#[test]
fn should_map_fixture_by_remove_first_underscore_if_any() {
    let (output, _) = run_test("remove_underscore.rs");

    TestResults::new().ok("ignore_input").assert(output);
}

#[test]
fn generic_input() {
    let (output, _) = run_test("generic.rs");

    TestResults::new()
        .ok("simple")
        .ok("strlen_test::case_1")
        .ok("strlen_test::case_2")
        .assert(output);
}

#[test]
fn impl_input() {
    let (output, _) = run_test("impl_param.rs");

    TestResults::new()
        .ok("simple")
        .ok("strlen_test::case_1")
        .ok("strlen_test::case_2")
        .assert(output);
}

#[test]
fn use_mutable_fixture_in_parametric_arguments() {
    let (output, _) = run_test("use_mutable_fixture_in_parametric_arguments.rs");

    TestResults::new()
        .with_contains(true)
        .ok("use_mutate_fixture::case_1::b_1")
        .assert(output);
}

#[test]
fn should_not_remove_lifetimes() {
    let (output, _) = run_test("lifetimes.rs");

    TestResults::new()
        .with_contains(true)
        .ok("case")
        .ok("values")
        .ok("fixture")
        .assert(output);
}

#[test]
fn should_reject_no_item_function() {
    let (output, name) = run_test("reject_no_item_function.rs");

    assert_in!(
        output.stderr.str(),
        format!(
            "
        error: expected `fn`
         --> {name}{SRC_LIB_RS}:4:1
          |
        4 | struct Foo;
          | ^^^^^^
        "
        )
        .unindent()
    );

    assert_in!(
        output.stderr.str(),
        format!(
            "
        error: expected `fn`
         --> {name}{SRC_LIB_RS}:7:1
          |
        7 | impl Foo {{}}
          | ^^^^
        "
        )
        .unindent()
    );

    assert_in!(
        output.stderr.str(),
        format!(
            "
        error: expected `fn`
          --> {name}{SRC_LIB_RS}:10:1
           |
        10 | mod mod_baz {{}}
           | ^^^
        "
        )
        .unindent()
    );
}

mod dump_input_values {
    use super::*;

    #[rstest]
    #[case::compact_syntax("dump_debug_compact.rs")]
    #[case::attr_syntax("dump_debug.rs")]
    fn if_implements_debug(#[case] source: &str) {
        let (output, _) = run_test(source);
        let out = output.stdout.str().to_string();

        TestResults::new()
            .fail("single_fail")
            .fail("no_trace_single_fail")
            .fail("cases_fail::case_1")
            .fail("cases_fail::case_2")
            .fail("no_trace_cases_fail::case_1")
            .fail("no_trace_cases_fail::case_2")
            .fail_with("matrix_fail::u_1", false, 8)
            .fail_with("matrix_fail::u_2", false, 8)
            .assert(output);

        assert_in!(out, "fu32 = 42");
        assert_in!(out, r#"fstring = "A String""#);
        assert_in!(out, r#"ftuple = (A, "A String", -12"#);

        assert_in!(out, "u = 42");
        assert_in!(out, r#"s = "str""#);
        assert_in!(out, r#"t = ("ss", -12)"#);

        assert_in!(out, "u = 24");
        assert_in!(out, r#"s = "trs""#);
        assert_in!(out, r#"t = ("tt", -24)"#);

        assert_in!(out, "u = 1");
        assert_in!(out, r#"s = "rst""#);
        assert_in!(out, r#"t = ("SS", -12)"#);

        assert_in!(out, "u = 2");
        assert_in!(out, r#"s = "srt""#);
        assert_in!(out, r#"t = ("TT", -24)"#);

        let expected = 11;
        for marker in ["TEST START", "TEST ARGUMENTS"] {
            let n_found = out.lines().filter(|l| l.contains(marker)).count();
            assert_eq!(
                n_found, expected,
                "Should contain {expected} '{marker}' but found {n_found}. [Should not enclose output if no trace]"
            );
        }
    }

    #[rstest]
    #[case::compact_syntax("dump_not_debug_compact.rs")]
    #[case::attr_syntax("dump_not_debug.rs")]
    fn should_not_compile_if_not_implement_debug(#[case] source: &str) {
        let (output, name) = run_test(source);

        assert_all_in!(
            output.stderr.str(),
            format!("--> {name}{SRC_LIB_RS}:10:11"),
            "fn single(fixture: S) {}",
            "^^^^^^^ `S` cannot be formatted using `{:?}`"
        );

        assert_in!(
            output.stderr.str(),
            format!("--> {name}{SRC_LIB_RS}:15:10"),
            "fn cases(s: S) {}",
            "^ `S` cannot be formatted using `{:?}`"
        );

        assert_in!(
            output.stderr.str(),
            format!("--> {name}{SRC_LIB_RS}:20:11"),
            "fn matrix(s: S) {}",
            "^ `S` cannot be formatted using `{:?}`"
        );
    }

    #[rstest]
    #[case::compact_syntax("dump_exclude_some_inputs_compact.rs")]
    #[case::attr_syntax("dump_exclude_some_inputs.rs")]
    fn can_exclude_some_inputs(#[case] source: &str) {
        let (output, _) = run_test(source);
        let out = output.stdout.str().to_string();

        TestResults::new()
            .fail("simple")
            .fail("cases::case_1")
            .fail_in("matrix::a_1")
            .assert(output);

        assert_in!(out, "fu32 = 42");
        assert_in!(out, "d = D");
        assert_in!(out, "fd = D");
        assert_in!(out, "dd = D");
    }

    #[test]
    fn should_be_enclosed_in_an_explicit_session() {
        let (output, _) = run_test(Path::new("single").join("dump_debug.rs"));
        let out = output.stdout.str().to_string();

        TestResults::new().fail("should_fail").assert(output);

        let lines = out
            .lines()
            .skip_while(|l| !l.contains("TEST ARGUMENTS"))
            .take_while(|l| !l.contains("TEST START"))
            .collect::<Vec<_>>();

        let expected = 4;
        assert_eq!(
            expected,
            lines.len(),
            "Not contains {expected} lines but {}: '{}'",
            lines.len(),
            lines.join("\n")
        );
    }
}

mod single {
    use super::*;

    fn res(name: impl AsRef<Path>) -> impl AsRef<Path> {
        Path::new("single").join(name.as_ref())
    }

    #[test]
    fn one_success_and_one_fail() {
        let (output, _) = run_test(res("simple.rs"));

        TestResults::new()
            .ok("should_success")
            .fail("should_fail")
            .assert(output);
    }

    #[test]
    fn should_resolve_generics_fixture_outputs() {
        let (output, _) = run_test(res("resolve.rs"));

        TestResults::new()
            .ok("generics_u32")
            .ok("generics_i32")
            .assert(output);
    }

    #[test]
    fn should_apply_partial_fixture() {
        let (output, _) = run_test(res("partial.rs"));

        TestResults::new()
            .ok("default")
            .ok("partial_1")
            .ok("partial_attr_1")
            .ok("partial_2")
            .ok("partial_attr_2")
            .ok("complete")
            .ok("complete_attr")
            .assert(output);
    }

    #[rstest]
    #[case("async.rs")]
    #[case("async_awt.rs")]
    #[case("async_awt_global.rs")]
    fn should_run_async_function(#[case] name: &str) {
        let prj = prj(res(name));
        prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .ok("should_pass")
            .fail("should_fail")
            .ok("should_panic_pass")
            .fail("should_panic_fail")
            .assert(output);
    }

    #[test]
    fn should_use_injected_test_attr() {
        let prj = prj(res("inject.rs"));
        prj.add_dependency("actix-rt", r#""1.1.0""#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .ok("sync_case")
            .ok("sync_case_panic")
            .fail("sync_case_fail")
            .fail("sync_case_panic_fail")
            .ok("async_case")
            .ok("async_case_test_attr")
            .ok("async_case_panic")
            .fail("async_case_fail")
            .fail("async_case_panic_fail")
            .assert(output);
    }
}

mod cases {
    use super::*;

    fn res(name: impl AsRef<Path>) -> impl AsRef<Path> {
        Path::new("cases").join(name.as_ref())
    }

    #[test]
    fn should_compile() {
        let output = prj(res("simple.rs")).compile().unwrap();

        assert_eq!(
            Some(0),
            output.status.code(),
            "Compile error due: {}",
            output.stderr.str()
        )
    }

    #[test]
    fn happy_path() {
        let (output, _) = run_test(res("simple.rs"));

        TestResults::new()
            .ok("strlen_test::case_1")
            .ok("strlen_test::case_2")
            .assert(output);
    }

    #[test]
    fn use_attr() {
        let (output, _) = run_test(res("use_attr.rs"));

        TestResults::new()
            .ok("all::case_1_ciao")
            .ok("all::case_2_panic")
            .ok("all::case_3_foo")
            .ok("just_cases::case_1_ciao")
            .ok("just_cases::case_2_foo")
            .ok("just_cases::case_3_panic")
            .ok("just_args::case_1_ciao")
            .ok("just_args::case_2_foo")
            .ok("just_args::case_3_panic")
            .ok("all_panic::case_1")
            .ok("all_panic::case_2")
            .assert(output);
    }

    #[test]
    fn case_description() {
        let (output, _) = run_test(res("description.rs"));

        TestResults::new()
            .ok("description::case_1_user_test_description")
            .ok("description::case_2")
            .fail("description::case_3_user_test_description_fail")
            .assert(output);
    }

    #[test]
    fn should_apply_partial_fixture() {
        let (output, _) = run_test(res("partial.rs"));

        TestResults::new()
            .ok("default::case_1")
            .ok("partial_1::case_1")
            .ok("partial_2::case_1")
            .ok("complete::case_1")
            .ok("partial_attr_1::case_1")
            .ok("partial_attr_2::case_1")
            .ok("complete_attr::case_1")
            .fail("default::case_2")
            .fail("partial_1::case_2")
            .fail("partial_2::case_2")
            .fail("complete::case_2")
            .fail("partial_attr_1::case_2")
            .fail("partial_attr_2::case_2")
            .fail("complete_attr::case_2")
            .assert(output);
    }

    #[test]
    fn should_use_case_attributes() {
        let (output, _) = run_test(res("case_attributes.rs"));

        TestResults::new()
            .ok("attribute_per_case::case_1_no_panic")
            .ok("attribute_per_case::case_2_panic")
            .ok("attribute_per_case::case_3_panic_with_message")
            .fail("attribute_per_case::case_4_no_panic_but_fail")
            .fail("attribute_per_case::case_5_panic_but_fail")
            .fail("attribute_per_case::case_6_panic_with_wrong_message")
            .assert(output);
    }

    #[rstest]
    #[case("async.rs")]
    #[case("async_awt.rs")]
    #[case("async_awt_global.rs")]
    fn should_run_async_function(#[case] name: &str) {
        let prj = prj(res(name));
        prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .ok("my_async_test::case_1_pass")
            .fail("my_async_test::case_2_fail")
            .ok("my_async_test::case_3_pass_panic")
            .fail("my_async_test::case_4_fail_panic")
            .ok("my_async_test_revert::case_1_pass")
            .assert(output);
    }

    #[rstest]
    fn should_run_async_mut() {
        let prj = prj(res("async_awt_mut.rs"));
        prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .ok("my_mut_test_global_awt::case_1_pass")
            .ok("my_mut_test_local_awt::case_1_pass")
            .assert(output);
    }

    #[test]
    fn should_use_injected_test_attr() {
        let prj = prj(res("inject.rs"));
        prj.add_dependency("actix-rt", r#""1.1.0""#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .ok("sync::case_1_pass")
            .ok("sync::case_2_panic")
            .fail("sync::case_3_fail")
            .ok("fn_async::case_1_pass")
            .ok("fn_async::case_2_panic")
            .fail("fn_async::case_3_fail")
            .ok("fn_async_test_attr::case_1_pass")
            .ok("fn_async_test_attr::case_2_panic")
            .fail("fn_async_test_attr::case_3_fail")
            .assert(output);
    }

    #[test]
    fn trace_just_one_test() {
        let (output, _) = run_test(res("dump_just_one_case.rs"));
        let out = output.stdout.str().to_string();

        TestResults::new()
            .fail("cases::case_1_first_no_dump")
            .fail("cases::case_2_dump_me")
            .fail("cases::case_3_last_no_dump")
            .assert(output);

        assert_in!(out, r#"s = "Trace it!""#);
        assert_not_in!(out, r#"s = "Please don't trace me""#);
    }

    mod not_compile_if_missed_arguments {
        use super::*;

        #[test]
        fn happy_path() {
            let (output, _) = run_test(res("missed_argument.rs"));
            let stderr = output.stderr.str();

            assert_ne!(Some(0), output.status.code());
            assert_in!(stderr, "Missed argument");
            assert_in!(
                stderr,
                "
                  |
                4 | #[rstest(f, case(42), case(24))]
                  |          ^
                "
                .unindent()
            );
        }

        #[test]
        fn should_reports_all() {
            let (output, _) = run_test(res("missed_some_arguments.rs"));
            let stderr = output.stderr.str();

            assert_in!(
                stderr,
                "
                  |
                4 | #[rstest(a,b,c, case(1,2,3), case(3,2,1))]
                  |          ^
                "
                .unindent()
            );
            assert_in!(
                stderr,
                "
                  |
                4 | #[rstest(a,b,c, case(1,2,3), case(3,2,1))]
                  |              ^
                "
                .unindent()
            );

            assert_eq!(
                2,
                stderr.count("Missed argument"),
                "Should contain message exactly 2 occurrences in error message:\n{}",
                stderr
            )
        }

        #[test]
        fn should_report_just_one_error_message_for_all_test_cases() {
            let (output, _) = run_test(res("missed_argument.rs"));
            let stderr = output.stderr.str();

            assert_eq!(
                1,
                stderr.count("Missed argument"),
                "More than one message occurrence in error message:\n{}",
                stderr
            )
        }

        #[test]
        fn should_not_report_error_in_macro_syntax() {
            let (output, _) = run_test(res("missed_argument.rs"));
            let stderr = output.stderr.str();

            assert!(!stderr.contains("macros that expand to items"));
        }
    }

    mod not_compile_if_a_case_has_a_wrong_signature {
        use std::process::Output;

        use lazy_static::lazy_static;

        use super::*;

        //noinspection RsTypeCheck
        fn execute() -> &'static (Output, String) {
            lazy_static! {
                static ref OUTPUT: (Output, String) = run_test(res("case_with_wrong_args.rs"));
            }
            assert_ne!(Some(0), OUTPUT.0.status.code(), "Should not compile");
            &OUTPUT
        }

        #[test]
        fn with_too_much_arguments() {
            let (output, _) = execute();
            let stderr = output.stderr.str();

            assert_in!(
                stderr,
                "
                  |
                8 | #[rstest(a, case(42, 43), case(12), case(24, 34))]
                  |                  ^^^^^^
                "
                .unindent()
            );

            assert_in!(
                stderr,
                "
                  |
                8 | #[rstest(a, case(42, 43), case(12), case(24, 34))]
                  |                                          ^^^^^^
                "
                .unindent()
            );
        }

        #[test]
        fn with_less_arguments() {
            let (output, _) = execute();
            let stderr = output.stderr.str();

            assert_in!(
                stderr,
                "
                  |
                4 | #[rstest(a, b, case(42), case(1, 2), case(43))]
                  |                     ^^
                "
                .unindent()
            );

            assert_in!(
                stderr,
                "
                  |
                4 | #[rstest(a, b, case(42), case(1, 2), case(43))]
                  |                                           ^^
                "
                .unindent()
            );
        }

        #[test]
        fn and_reports_all_errors() {
            let (output, _) = execute();
            let stderr = output.stderr.str();

            // Exactly 4 cases are wrong
            assert_eq!(
                4,
                stderr.count("Wrong case signature: should match the given parameters list."),
                "Should contain message exactly 4 occurrences in error message:\n{}",
                stderr
            );
        }
    }

    mod not_compile_if_args_but_no_cases {
        use std::process::Output;

        use lazy_static::lazy_static;

        use super::*;

        //noinspection RsTypeCheck
        fn execute() -> &'static (Output, String) {
            lazy_static! {
                static ref OUTPUT: (Output, String) = run_test(res("args_with_no_cases.rs"));
            }
            assert_ne!(Some(0), OUTPUT.0.status.code(), "Should not compile");
            &OUTPUT
        }

        #[test]
        fn report_error() {
            let (output, name) = execute();
            let stderr = output.stderr.str();

            assert_in!(
                stderr,
                format!(
                    "
                error: No cases for this argument.
                 --> {name}{SRC_LIB_RS}:3:10
                  |
                3 | #[rstest(one, two, three)]
                  |          ^^^
                "
                )
                .unindent()
            );
        }

        #[test]
        fn and_reports_all_errors() {
            let (output, _) = execute();
            let stderr = output.stderr.str();

            // Exactly 3 cases are wrong
            assert_eq!(
                3,
                stderr.count("No cases for this argument."),
                "Should contain message exactly 3 occurrences in error message:\n{}",
                stderr
            );
        }
    }
}

mod matrix {
    use super::*;

    fn res(name: impl AsRef<Path>) -> impl AsRef<Path> {
        Path::new("matrix").join(name.as_ref())
    }

    #[test]
    fn should_compile() {
        let output = prj(res("simple.rs")).compile().unwrap();

        assert_eq!(
            Some(0),
            output.status.code(),
            "Compile error due: {}",
            output.stderr.str()
        )
    }

    #[test]
    fn happy_path() {
        let (output, _) = run_test(res("simple.rs"));

        TestResults::new()
            .with_contains(true)
            .ok("strlen_test::expected_1_4::input_1___ciao__")
            .ok("strlen_test::expected_1_4::input_2___buzz__")
            .ok("strlen_test::expected_2_2_3_2::input_1___ciao__")
            .ok("strlen_test::expected_2_2_3_2::input_2___buzz__")
            .assert(output);
    }

    #[test]
    fn should_apply_partial_fixture() {
        let (output, _) = run_test(res("partial.rs"));

        TestResults::new()
            .with_contains(true)
            .ok_times("default::a_1", 2)
            .ok("default::a_2")
            .ok("partial_2::a_2")
            .ok("partial_attr_2::a_2")
            .ok("complete::a_2")
            .ok("complete_attr::a_2")
            .fail("default::a_2")
            .fail_times("partial_1::a_1", 2)
            .fail_times("partial_1::a_2", 2)
            .fail_times("partial_2::a_1", 2)
            .fail("partial_2::a_2")
            .fail_times("complete::a_1", 2)
            .fail("complete::a_2")
            .fail_times("partial_attr_1::a_1", 2)
            .fail_times("partial_attr_1::a_2", 2)
            .fail_times("partial_attr_2::a_1", 2)
            .fail("partial_attr_2::a_2")
            .fail_times("complete_attr::a_1", 2)
            .fail("complete_attr::a_2")
            .assert(output);
    }

    #[rstest]
    #[case("async.rs")]
    #[case("async_awt.rs")]
    #[case("async_awt_global.rs")]
    fn should_run_async_function(#[case] name: &str) {
        let prj = prj(res(name));
        prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .with_contains(true)
            .ok("my_async_test::first_1")
            .fail("my_async_test::first_1")
            .fail("my_async_test::first_2")
            .ok("my_async_test::first_2")
            .assert(output);
    }

    #[test]
    fn should_use_injected_test_attr() {
        let prj = prj(res("inject.rs"));
        prj.add_dependency("actix-rt", r#""1.1.0""#);

        let output = prj.run_tests().unwrap();

        TestResults::new()
            .with_contains(true)
            .ok("sync::first_1")
            .fail("sync::first_1")
            .fail("sync::first_2")
            .ok("sync::first_2")
            .ok("fn_async::first_1")
            .fail("fn_async::first_1")
            .fail("fn_async::first_2")
            .ok("fn_async::first_2")
            .ok("fn_async_test_attr::first_1")
            .fail("fn_async_test_attr::first_1")
            .fail("fn_async_test_attr::first_2")
            .ok("fn_async::first_2")
            .assert(output);
    }

    #[test]
    fn use_args_attributes() {
        let (output, _) = run_test(res("use_attr.rs"));

        TestResults::new()
            .ok("both::expected_1_4::input_1___ciao__")
            .ok("both::expected_1_4::input_2___buzz__")
            .ok("both::expected_2_2_3_2::input_1___ciao__")
            .ok("both::expected_2_2_3_2::input_2___buzz__")
            .ok("first::input_1___ciao__::expected_1_4")
            .ok("first::input_2___buzz__::expected_1_4")
            .ok("first::input_1___ciao__::expected_2_2_3_2")
            .ok("first::input_2___buzz__::expected_2_2_3_2")
            .ok("second::expected_1_4::input_1___ciao__")
            .ok("second::expected_1_4::input_2___buzz__")
            .ok("second::expected_2_2_3_2::input_1___ciao__")
            .ok("second::expected_2_2_3_2::input_2___buzz__")
            .assert(output);
    }
}

#[test]
fn convert_string_literal() {
    let (output, _) = run_test("convert_string_literal.rs");

    assert_regex!(
        "Cannot parse 'error' to get [a-z:_0-9]*MyType",
        output.stdout.str()
    );

    TestResults::new()
        .ok("cases::case_1")
        .ok("cases::case_2")
        .ok("cases::case_3")
        .ok("cases::case_4")
        .fail("cases::case_5")
        .fail("cases::case_6")
        .ok_in("values::addr_1")
        .ok_in("values::addr_2")
        .fail_in("values::addr_3")
        .fail_in("values::addr_4")
        .ok_in("not_convert_byte_array::case_1::values_1")
        .ok("not_convert_impl::case_1")
        .ok("not_convert_generics::case_1")
        .ok("not_convert_generics::case_2")
        .ok("convert_without_debug::case_1")
        .fail("convert_without_debug::case_2")
        .assert(output);
}

#[test]
fn happy_path() {
    let (output, _) = run_test("happy_path.rs");

    TestResults::new()
        .ok("happy::case_1::expected_1_4::input_1___ciao__")
        .ok("happy::case_1::expected_1_4::input_2___buzz__")
        .ok("happy::case_1::expected_2_2_3_2::input_1___ciao__")
        .ok("happy::case_1::expected_2_2_3_2::input_2___buzz__")
        .ok("happy::case_2_second::expected_1_4::input_1___ciao__")
        .ok("happy::case_2_second::expected_1_4::input_2___buzz__")
        .ok("happy::case_2_second::expected_2_2_3_2::input_1___ciao__")
        .ok("happy::case_2_second::expected_2_2_3_2::input_2___buzz__")
        .assert(output);
}

#[test]
fn destruct() {
    let (output, _) = run_test("destruct.rs");

    TestResults::new()
        .ok("cases_destruct::case_1_two_times_twenty_one::__destruct_3_1_T__new_42_1_")
        .ok("cases_destruct::case_1_two_times_twenty_one::__destruct_3_2_T_a_3_b_14_")
        .ok("cases_destruct::case_2_six_times_seven::__destruct_3_1_T__new_42_1_")
        .ok("cases_destruct::case_2_six_times_seven::__destruct_3_2_T_a_3_b_14_")
        .ok("cases_destruct_named_tuple::case_1_two_times_twenty_one::__destruct_3_1_S_42_1_")
        .ok("cases_destruct_named_tuple::case_1_two_times_twenty_one::__destruct_3_2_S_3_14_")
        .ok("cases_destruct_named_tuple::case_2_six_times_seven::__destruct_3_1_S_42_1_")
        .ok("cases_destruct_named_tuple::case_2_six_times_seven::__destruct_3_2_S_3_14_")
        .ok("cases_destruct_tuple::case_1_two_times_twenty_one::__destruct_3_1__42_1_")
        .ok("cases_destruct_tuple::case_1_two_times_twenty_one::__destruct_3_2__3_14_")
        .ok("cases_destruct_tuple::case_2_six_times_seven::__destruct_3_1__42_1_")
        .ok("cases_destruct_tuple::case_2_six_times_seven::__destruct_3_2__3_14_")
        .ok("swapped")
        .assert(output);
}

#[test]
fn rename() {
    let (output, _) = run_test("rename.rs");

    TestResults::new()
        .ok("compact")
        .ok("compact_mod")
        .ok("compact_injected")
        .ok("attribute")
        .ok("attribute_mod")
        .ok("attribute_injected")
        .assert(output);
}

#[test]
fn ignore_underscore_args() {
    let (output, _) = run_test("ignore_args.rs");

    TestResults::new()
        .with_contains(true)
        .ok("test::case_1::_ignore3_1")
        .ok("test::case_1::_ignore3_2")
        .ok("test::case_1::_ignore3_3")
        .ok("test::case_1::_ignore3_4")
        .ok("test::case_2::_ignore3_1")
        .ok("test::case_2::_ignore3_2")
        .ok("test::case_2::_ignore3_3")
        .ok("test::case_2::_ignore3_4")
        .assert(output);
}

#[test]
fn ignore_attributes_args_if_any() {
    let prj = prj("ignore_attributes_args_if_any.rs");
    prj.add_dependency(
        "sqlx",
        r#"{version="*", features=["sqlite","macros","runtime-tokio"]}"#,
    );

    let output = prj.run_tests().unwrap();

    TestResults::new()
        .with_contains(true)
        .ok("test_sum::case_1::c_1_1")
        .ok("test_sum::case_1::c_2_2")
        .ok("test_sum::case_2::c_1_1")
        .ok("test_sum::case_2::c_2_2")
        .assert(output);
}

#[test]
fn timeout() {
    let mut prj = prj("timeout.rs");
    prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);
    prj.set_default_timeout(1);
    let output = prj.run_tests().unwrap();

    TestResults::new()
        .ok("thread::single_pass")
        .fail("thread::single_fail_value")
        .ok("thread::fail_with_user_message")
        .fail("thread::single_fail_timeout")
        .ok("thread::one_pass::case_1")
        .fail("thread::one_fail_value::case_1")
        .fail("thread::one_fail_timeout::case_1")
        .ok("thread::group_same_timeout::case_1_pass")
        .fail("thread::group_same_timeout::case_2_fail_timeout")
        .fail("thread::group_same_timeout::case_3_fail_value")
        .ok("thread::group_single_timeout::case_1_pass")
        .fail("thread::group_single_timeout::case_2_fail_timeout")
        .fail("thread::group_single_timeout::case_3_fail_value")
        .ok("thread::group_one_timeout_override::case_1_pass")
        .fail("thread::group_one_timeout_override::case_2_fail_timeout")
        .fail("thread::group_one_timeout_override::case_3_fail_value")
        .ok("thread::compile_with_no_copy_arg::case_1")
        .ok("thread::compile_with_no_copy_fixture")
        .fail("thread::default_timeout_failure")
        .ok("async_std_cases::single_pass")
        .fail("async_std_cases::single_fail_value")
        .ok("async_std_cases::fail_with_user_message")
        .fail("async_std_cases::single_fail_timeout")
        .ok("async_std_cases::one_pass::case_1")
        .fail("async_std_cases::one_fail_value::case_1")
        .fail("async_std_cases::one_fail_timeout::case_1")
        .ok("async_std_cases::group_same_timeout::case_1_pass")
        .fail("async_std_cases::group_same_timeout::case_2_fail_timeout")
        .fail("async_std_cases::group_same_timeout::case_3_fail_value")
        .ok("async_std_cases::group_single_timeout::case_1_pass")
        .fail("async_std_cases::group_single_timeout::case_2_fail_timeout")
        .fail("async_std_cases::group_single_timeout::case_3_fail_value")
        .ok("async_std_cases::group_one_timeout_override::case_1_pass")
        .fail("async_std_cases::group_one_timeout_override::case_2_fail_timeout")
        .fail("async_std_cases::group_one_timeout_override::case_3_fail_value")
        .ok("async_std_cases::compile_with_no_copy_arg::case_1")
        .ok("async_std_cases::compile_with_no_copy_fixture")
        .ok("async_std_cases::compile_with_async_fixture")
        .ok("async_std_cases::compile_with_async_awt_fixture")
        .assert(output);
}

mod import_crate_with_other_name {
    use super::*;

    fn prj(res: &str, features: Option<&[&str]>) -> Project {
        let prj = crate::base_prj();
        let default_features = features.is_none();
        let features = features
            .map(|features| {
                features
                    .iter()
                    .map(|f| format!(r#""{}""#, f))
                    .collect::<Vec<_>>()
                    .join(",")
            })
            .unwrap_or_else(|| "".to_string());
        prj.add_dependency(
            "other_name",
            &format!(
                r#"{{path={}, package = "rstest", default-features = {}, features = [{}]}}"#,
                rstest_test::to_toml_string(prj.exec_dir_str()),
                default_features,
                features
            ),
        );
        prj.set_code_file(resources(res))
    }

    #[test]
    fn should_fails_to_compile_if_crate_name_feature_is_not_enabled() {
        let prj = prj("timeout_other_name.rs", Some(&[]));
        assert!(!prj.compile().unwrap().status.success());
    }

    #[test]
    fn should_always_compile_project_that_use_default_name() {
        let prj = crate::base_prj();
        prj.add_dependency(
            "rstest",
            &format!(
                r#"{{path={}, default-features = false}}"#,
                rstest_test::to_toml_string(prj.exec_dir_str()),
            ),
        );
        let prj = prj.set_code_file(resources("convert_string_literal.rs"));

        assert!(prj.compile().unwrap().status.success());
    }

    #[rstest]
    #[case::default_features(None)]
    #[case::with_crate_name_feature(Some(["crate-name"].as_slice()))]
    fn timeout_should_compile_and_run(#[case] features: Option<&[&str]>) {
        let prj = prj("timeout_other_name.rs", features);
        assert!(prj.compile().unwrap().status.success());
    }

    #[rstest]
    #[case::default(None)]
    #[case::with_crate_name_feature(Some(["crate-name"].as_slice()))]
    fn convert_string_literal_should_compile_and_run(#[case] features: Option<&[&str]>) {
        let prj = prj("convert_string_literal_other_name.rs", features);
        assert!(prj.compile().unwrap().status.success());
    }
}

#[test]
fn local_lifetime() {
    let (output, _) = run_test("local_lifetime.rs");

    TestResults::new()
        .ok("tests::it_works::case_1")
        .assert(output);
}

#[test]
fn by_ref() {
    let prj = prj("by_ref.rs");
    let files_path: std::path::PathBuf = prj.path().join("files");
    std::fs::create_dir(&files_path).unwrap();
    let name = "my_name.txt";
    let mut out = File::create(files_path.join(name)).unwrap();
    out.write_all(name.as_bytes()).unwrap();
    let output = prj.run_tests().unwrap();

    TestResults::new()
        .ok("test::case_1::v_1_42")
        .ok("test::case_1::v_2_142")
        .ok("start_with_name::path_1_files_my_name_txt")
        .assert(output);
}

#[test]
fn no_std() {
    let prj = prj("no_std.rs");
    prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);
    let output = prj.run_tests().unwrap();

    TestResults::new()
        .ok("it_works::case_1")
        .ok("async_works")
        .assert(output);
}

#[test]
fn context() {
    let (output, _) = run_test("context.rs");

    TestResults::new()
        .ok("with_case::case_1_description")
        .ok("without_case")
        .ok("first::inner::test")
        .ok("measure_time")
        .assert(output);
}

#[test]
fn should_works_with_smol_async_runtime() {
    let prj = prj("smol.rs");
    prj.add_dependency("smol", r#""2.0.2""#);
    prj.add_dependency("smol-macros", r#""0.1.1""#);
    prj.add_dependency("macro_rules_attribute", r#""0.2.2""#);

    let output = prj.run_tests().unwrap();

    TestResults::new()
        .with_contains(true)
        .ok("async_test::case_1::c_1_2")
        .ok("async_test::case_1::c_2_4")
        .ok("async_test::case_1::c_3_16")
        .ok("async_test::case_2::c_1_2")
        .ok("async_test::case_2::c_2_4")
        .ok("async_test::case_2::c_3_16")
        .assert(output);
}


mod async_timeout_feature {
    use super::*;

    fn build_prj(features: &[&str]) -> Project {
        let prj = crate::base_prj();
        let features = match features.is_empty() {
            true => String::new(),
            false => format!(r#", features=["{}"]"#, features.join(r#"",""#)),
        };
        prj.add_dependency(
            "rstest",
            &format!(
                r#"{{path={}, default-features = false {}}}"#,
                rstest_test::to_toml_string(prj.exec_dir_str()),
                features
            ),
        );
        prj.add_dependency("async-std", r#"{version="*", features=["attributes"]}"#);
        prj
    }

    #[test]
    fn should_not_compile_if_feature_disable() {
        let prj = build_prj(&[]);
        let output = prj
            .set_code_file(resources("timeout_async.rs"))
            .run_tests()
            .unwrap();

        assert_in!(output.stderr.str(), "error: Enable async-timeout feature");
    }

    #[test]
    fn should_work_if_feature_enabled() {
        let prj = build_prj(&["async-timeout"]);

        let output = prj
            .set_code_file(resources("timeout_async.rs"))
            .run_tests()
            .unwrap();

        TestResults::new().ok("single_pass").assert(output);
    }
}

mod should_show_correct_errors {
    use std::process::Output;

    use lazy_static::lazy_static;

    use super::*;

    //noinspection RsTypeCheck
    fn execute() -> &'static (Output, String) {
        lazy_static! {
            static ref OUTPUT: (Output, String) = run_test("errors.rs");
        }
        &OUTPUT
    }

    #[test]
    fn if_no_fixture() {
        let (output, name) = execute();

        assert_in!(output.stderr.str(), "error[E0433]: ");
        assert_in!(
            output.stderr.str(),
            format!(
                "
                  --> {name}{SRC_LIB_RS}:13:33
                   |
                13 | fn error_cannot_resolve_fixture(no_fixture: u32, f: u32) {{}}"
            )
            .unindent()
        );
    }

    #[test]
    fn if_inject_wrong_fixture() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Missed argument: 'not_a_fixture' should be a test function argument.
                  --> {name}{SRC_LIB_RS}:28:23
                   |
                28 | #[rstest(f, case(42), not_a_fixture(24))]
                   |                       ^^^^^^^^^^^^^
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_wrong_type() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                r#"
                error[E0308]: mismatched types
                 --> {name}{SRC_LIB_RS}:9:18
                  |
                9 |     let a: u32 = "";
                "#
            )
            .unindent()
        );
    }

    #[test]
    fn if_wrong_type_fixture() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error[E0308]: mismatched types
                  --> {name}{SRC_LIB_RS}:16:29
                   |
                16 | fn error_fixture_wrong_type(fixture: String, f: u32) {{}}
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_wrong_type_case_param() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error[E0308]: mismatched types
                  --> {name}{SRC_LIB_RS}:19:26
                   |
                19 | fn error_case_wrong_type(f: &str) {{}}"
            )
            .unindent()
        );
    }

    #[test]
    fn if_wrong_type_matrix_param() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error[E0308]: mismatched types
                  --> {name}{SRC_LIB_RS}:51:28
                   |
                51 | fn error_matrix_wrong_type(f: &str) {{}}"
            )
            .unindent()
        );
    }

    #[test]
    fn if_arbitrary_rust_code_has_some_errors() {
        let (output, name) = execute();
        let src_lib_rs = regex::escape(SRC_LIB_RS);

        assert_regex!(
            format!(
                r#"error\[E0308\]: mismatched types
                \s+--> {name}{src_lib_rs}:22:31"#
            )
            .unindent(),
            output.stderr.str()
        );
        assert_regex!(
            r#"22\s+|\s+case\(vec!\[1,2,3\]\.contains\(2\)\)\)"#,
            output.stderr.str()
        );

        assert_regex!(
            format!(
                r#"error\[E0308\]: mismatched types
                \s+--> {name}{src_lib_rs}:53:45"#
            )
            .unindent(),
            output.stderr.str()
        );
        assert_regex!(
            r#"53\s+|\s+#\[rstest\(condition => \[vec!\[1,2,3\]\.contains\(2\)\] \)\]"#,
            output.stderr.str()
        );
    }

    #[test]
    fn if_inject_a_fixture_that_is_already_a_case() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'f' is already defined.
                  --> {name}{SRC_LIB_RS}:41:13
                   |
                41 | #[rstest(f, f(42), case(12))]
                   |             ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_a_case_arg_that_is_already_an_injected_fixture() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'f' is already defined.
                  --> {name}{SRC_LIB_RS}:44:17
                   |
                44 | #[rstest(f(42), f, case(12))]
                   |                 ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_inject_a_fixture_more_than_once() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'f' is already defined.
                  --> {name}{SRC_LIB_RS}:47:20
                   |
                47 | #[rstest(v, f(42), f(42), case(12))]
                   |                    ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_list_argument_dont_match_function_signature() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Missed argument: 'not_exist_1' should be a test function argument.
                  --> {name}{SRC_LIB_RS}:61:10
                   |
                61 | #[rstest(not_exist_1 => [42],
                   |          ^^^^^^^^^^^"
            )
            .unindent()
        );

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Missed argument: 'not_exist_2' should be a test function argument.
                  --> {name}{SRC_LIB_RS}:62:10
                   |
                62 |          not_exist_2 => [42])]
                   |          ^^^^^^^^^^^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_inject_a_fixture_that_is_already_a_value_list() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'f' is already defined.
                  --> {name}{SRC_LIB_RS}:65:25
                   |
                65 | #[rstest(f => [41, 42], f(42))]
                   |                         ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_value_list_more_that_once() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'a' is already defined.
                  --> {name}{SRC_LIB_RS}:77:25
                   |
                77 | #[rstest(a => [42, 24], a => [24, 42])]
                   |                         ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_value_list_that_is_already_an_injected_fixture() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'f' is already defined.
                  --> {name}{SRC_LIB_RS}:68:17
                   |
                68 | #[rstest(f(42), f => [41, 42])]
                   |                 ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_value_list_that_is_already_a_case_arg() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'a' is already defined.
                  --> {name}{SRC_LIB_RS}:71:23
                   |
                71 | #[rstest(a, case(42), a => [42])]
                   |                       ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_a_case_arg_that_is_already_a_value_list() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'a' is already defined.
                  --> {name}{SRC_LIB_RS}:74:21
                   |
                74 | #[rstest(a => [42], a, case(42))]
                   |                     ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_define_a_case_arg_more_that_once() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Duplicate argument: 'a' is already defined.
                  --> {name}{SRC_LIB_RS}:80:13
                   |
                80 | #[rstest(a, a, case(42))]
                   |             ^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_a_value_contains_empty_list() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Values list should not be empty
                  --> {name}{SRC_LIB_RS}:58:19
                   |
                58 | #[rstest(empty => [])]
                   |                   ^^"
            )
            .unindent()
        );
    }

    #[test]
    fn if_try_to_convert_literal_string_to_a_type_that_not_implement_from_str() {
        let (output, name) = execute();

        assert_in!(output.stderr.str(), format!("--> {name}{SRC_LIB_RS}:84:1"));
        assert_in!(
            output.stderr.str(),
            "| -------- doesn't satisfy `S: FromStr`"
        );
    }

    #[test]
    fn if_try_to_use_future_on_an_impl() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                  --> {name}{SRC_LIB_RS}:93:8
                   |
                93 |     s: impl AsRef<str>,
                   |        ^^^^^^^^^^^^^^^
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_try_to_use_future_more_that_once() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                   --> {name}{SRC_LIB_RS}:102:5
                    |
                102 |     #[future]
                    |     ^^^^^^^^^
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_use_timeout_without_arg() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: expected attribute arguments in parentheses: #[timeout(...)]
                   --> {name}{SRC_LIB_RS}:108:3
                    |
                108 | #[timeout]
                    |   ^^^^^^^
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_timeout_is_not_an_expression() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: expected an expression
                   --> {name}{SRC_LIB_RS}:112:17
                    |
                112 | #[timeout(some -> strange -> invalid -> expression)]
                    |                 ^
                "
            )
            .unindent()
        );
    }

    #[test]
    fn if_timeout_is_not_a_duration() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error[E0308]: mismatched types
                   --> {name}{SRC_LIB_RS}:116:11"
            )
            .unindent()
        );

        assert_in!(
            output.stderr.str(),
            "
            116 | #[timeout(42)]
                |           ^^ expected `Duration`, found integer
            "
            .unindent()
        );
    }

    #[test]
    fn if_files_contains_absolute_path() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                "
                error: Invalid glob path: path contains non-relative component
                   --> {name}{SRC_LIB_RS}:120:30"
            )
            .unindent()
        );

        assert_in!(
            output.stderr.str(),
            r#"
                120 | fn error_absolute_path_files(#[files("/tmp/tmp.Q81idVZYAV/*.txt")] path: std::path::PathBuf) {}
                    |                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
            "#
            .unindent()
        );
    }

    #[test]
    fn try_to_destruct_implicit_fixture() {
        let (output, name) = execute();

        assert_in!(
            output.stderr.str(),
            format!(
                r#"
                error: To destruct a fixture you should provide a path to resolve it by '#[from(...)]' attribute.
                   --> {name}{SRC_LIB_RS}:126:27
                    |
                126 | fn wrong_destruct_fixture(T(a, b): T, #[with(42)] T(c, d): T) {{}}
                    |                           ^^^^^^^^^^"#,
            )
            .unindent()
        );

        assert_in!(
            output.stderr.str(),
            format!(
                r#"
                error: To destruct a fixture you should provide a path to resolve it by '#[from(...)]' attribute.
                   --> {name}{SRC_LIB_RS}:126:51
                    |
                126 | fn wrong_destruct_fixture(T(a, b): T, #[with(42)] T(c, d): T) {{}}
                    |                                                   ^^^^^^^^^^"#,
            )
            .unindent()
        );

        assert_not_in!(output.stderr.str(), "#[case] T(e, f): T");
        assert_not_in!(output.stderr.str(), "#[values(T(1, 2))] T(g, h): T");
    }
}
