/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_analysis::attrs::resolve::configured_attr::ConfiguredAttrExt;
use buck2_build_api::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use buck2_build_api::interpreter::rule_defs::cmd_args::DefaultCommandLineContext;
use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_common::executor_config::PathSeparatorKind;
use buck2_common::package_listing::listing::testing::PackageListingExt;
use buck2_common::package_listing::listing::PackageListing;
use buck2_core::cells::cell_root_path::CellRootPathBuf;
use buck2_core::cells::name::CellName;
use buck2_core::cells::CellResolver;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::buck_out_path::BuckOutPathResolver;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_interpreter_for_build::attrs::coerce::attr_type::AttrTypeExt;
use buck2_interpreter_for_build::attrs::coerce::testing::coercion_ctx;
use buck2_interpreter_for_build::attrs::coerce::testing::coercion_ctx_listing;
use buck2_interpreter_for_build::attrs::coerce::testing::to_value;
use buck2_interpreter_for_build::interpreter::selector::register_select;
use buck2_node::attrs::attr_type::AttrType;
use buck2_node::attrs::coerced_deps_collector::CoercedDepsCollector;
use buck2_node::attrs::configurable::AttrIsConfigurable;
use buck2_node::attrs::configuration_context::AttrConfigurationContext;
use buck2_node::attrs::configured_info::ConfiguredAttrInfo;
use buck2_node::attrs::display::AttrDisplayWithContextExt;
use buck2_node::attrs::fmt_context::AttrFmtContext;
use buck2_node::attrs::testing::configuration_ctx;
use buck2_node::provider_id_set::ProviderIdSet;
use dupe::Dupe;
use gazebo::prelude::*;
use indoc::indoc;
use starlark::environment::GlobalsBuilder;
use starlark::environment::Module;
use starlark::values::Heap;
use starlark::values::Value;

use crate::attrs::resolve::testing::resolution_ctx;
use crate::attrs::resolve::testing::resolution_ctx_with_providers;

#[test]
fn test() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    // Check that `x` is captured with the function
    let value = to_value(
        &env,
        &globals,
        indoc!(
            r#"
                [[
                    ["hello", "world!"]
                    + select({
                        "//some:config": ["some"],
                        "DEFAULT": ["okay"] + select({
                            "//other:config": ["other"],
                            "DEFAULT": ["default", "for", "realz"],
                        }),
                    })
                    + ["..."]
                    + ["..."]
                ]]
                "#
        ),
    );

    let attr = AttrType::list(AttrType::list(AttrType::list(AttrType::string())));

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "[[[\"hello\",\"world!\"]+select(\"root//some:config\"=[\"some\"],\"DEFAULT\"=[\"okay\"]+select(\"root//other:config\"=[\"other\"],\"DEFAULT\"=[\"default\",\"for\",\"realz\"]))+[\"...\"]+[\"...\"]]]",
        coerced.as_display_no_ctx().to_string()
    );

    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "[[[\"hello\",\"world!\",\"okay\",\"other\",\"...\",\"...\"]]]",
        configured.as_display_no_ctx().to_string()
    );

    let ctx = resolution_ctx(&env);
    let resolved = configured.resolve_single(PackageLabel::testing(), &ctx)?;
    assert_eq!(
        "[[[\"hello\", \"world!\", \"okay\", \"other\", \"...\", \"...\"]]]",
        resolved.to_string()
    );

    Ok(())
}

#[test]
fn test_string() -> anyhow::Result<()> {
    let env = Module::new();
    let globals = GlobalsBuilder::extended().with(register_select).build();
    let attr = AttrType::string();
    let value = to_value(&env, &globals, r#""a" + select({"DEFAULT": "b"})"#);

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(r#""ab""#, configured.as_display_no_ctx().to_string());

    Ok(())
}

#[test]
fn test_invalid_concat_coercion_into_one_of() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let value = to_value(
        &env,
        &globals,
        indoc!(
            r#"
            [True] + select({"DEFAULT": ["foo"]})
            "#
        ),
    );
    let attr = AttrType::one_of(vec![
        AttrType::list(AttrType::bool()),
        AttrType::list(AttrType::string()),
    ]);

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let err = coerced
        .configure(&attr, &configuration_ctx())
        .expect_err("Should fail to concatenate configured lists");
    assert!(
        err.to_string()
            .contains("Cannot concatenate values coerced/configured to different oneof variants: `attrs.list(attrs.bool())` and `attrs.list(attrs.string())`"),
        "err: {}",
        err
    );
    Ok(())
}

#[test]
fn test_concat_option_one_of() {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let value = to_value(
        &env,
        &globals,
        indoc!(
            r#"
            ["foo"] + select({"DEFAULT": ["bar"]})
            "#
        ),
    );

    let attr = AttrType::option(AttrType::one_of(vec![
        AttrType::list(AttrType::string()),
        AttrType::list(AttrType::bool()),
    ]));

    let coerced = attr
        .coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)
        .unwrap();
    let configured = coerced.configure(&attr, &configuration_ctx()).unwrap();
    assert_eq!(
        r#"["foo","bar"]"#,
        configured.as_display_no_ctx().to_string()
    );
}

#[test]
fn test_any() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc(vec!["//some:target", "cell1//named:target[foo]"]);
    let attr = AttrType::any();

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "[\"//some:target\",\"cell1//named:target[foo]\"]",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "[\"//some:target\",\"cell1//named:target[foo]\"]",
        configured.as_display_no_ctx().to_string()
    );

    let value = Value::new_none();
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!("None", coerced.as_display_no_ctx().to_string());
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!("None", configured.as_display_no_ctx().to_string());

    let value = Value::new_bool(true);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!("True", coerced.as_display_no_ctx().to_string());
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!("True", configured.as_display_no_ctx().to_string());

    let value = heap.alloc(42);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!("42", coerced.as_display_no_ctx().to_string());
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!("42", configured.as_display_no_ctx().to_string());

    Ok(())
}

#[test]
fn test_option() -> anyhow::Result<()> {
    let heap = Heap::new();
    let attr = AttrType::option(AttrType::list(AttrType::string()));

    let value = heap.alloc(vec!["string1", "string2"]);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "[\"string1\",\"string2\"]",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "[\"string1\",\"string2\"]",
        configured.as_display_no_ctx().to_string()
    );

    let value = Value::new_none();
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!("None", coerced.as_display_no_ctx().to_string());
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!("None", configured.as_display_no_ctx().to_string());

    Ok(())
}

#[test]
fn test_dict() -> anyhow::Result<()> {
    let env = Module::new();
    let globals = GlobalsBuilder::extended().with(register_select).build();
    let value = to_value(&env, &globals, r#"{"b":["1"],"a":[]}"#);

    let attr = AttrType::dict(AttrType::string(), AttrType::list(AttrType::string()), true);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "{\"a\": [],\"b\": [\"1\"]}",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "{\"a\": [],\"b\": [\"1\"]}",
        configured.as_display_no_ctx().to_string()
    );

    let attr = AttrType::dict(
        AttrType::string(),
        AttrType::list(AttrType::string()),
        false,
    );
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "{\"b\": [\"1\"],\"a\": []}",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "{\"b\": [\"1\"],\"a\": []}",
        configured.as_display_no_ctx().to_string()
    );

    let value = to_value(
        &env,
        &globals,
        r#"{"b":["1"],"a":[]} + select({"DEFAULT": { "c": []}})"#,
    );
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        r#"{"b": ["1"],"a": [],"c": []}"#,
        configured.as_display_no_ctx().to_string()
    );

    Ok(())
}

#[test]
fn test_one_of() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc("one");
    let values = heap.alloc(vec!["test", "extra"]);

    let attr = AttrType::one_of(vec![AttrType::string(), AttrType::list(AttrType::string())]);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!("\"one\"", coerced.as_display_no_ctx().to_string());
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!("\"one\"", configured.as_display_no_ctx().to_string());

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), values)?;
    assert_eq!(
        "[\"test\",\"extra\"]",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "[\"test\",\"extra\"]",
        configured.as_display_no_ctx().to_string()
    );

    let attr = AttrType::one_of(Vec::new());
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value);
    assert!(coerced.is_err());

    Ok(())
}

#[test]
fn test_label() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc(vec!["//some:target", "cell1//named:target[foo]"]);

    let attr = AttrType::list(AttrType::dep(ProviderIdSet::EMPTY));

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "[\"root//some:target\",\"cell1//named:target[foo]\"]",
        coerced.as_display_no_ctx().to_string()
    );

    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        format!(
            "[\"root//some:target ({})\",\"cell1//named:target[foo] ({})\"]",
            ConfigurationData::testing_new(),
            ConfigurationData::testing_new()
        ),
        configured.as_display_no_ctx().to_string()
    );

    Ok(())
}

#[test]
fn test_coerced_deps() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let content = indoc!(
        r#"
            ["//some:target", "cell1//named:target[foo]"] + select({
                "//some:config": ["cell1//named:target[bar]"],
                "DEFAULT": ["cell1//:okay"] + select({
                    "cell1//other:config": ["//some:target2"],
                    "DEFAULT": ["//:default1", "//:default2"],
                }),
            }) + ["//:other"]
            "#
    );
    // Check that `x` is captured with the function
    let value = to_value(&env, &globals, content);

    let attr = AttrType::list(AttrType::dep(ProviderIdSet::EMPTY));
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;

    let mut visitor = CoercedDepsCollector::new();
    coerced.traverse(&attr, PackageLabel::testing(), &mut visitor)?;
    let CoercedDepsCollector {
        deps,
        configuration_deps,
        ..
    } = visitor;
    let deps: Vec<_> = deps.iter().map(|t| t.to_string()).collect();
    let config_deps: Vec<_> = configuration_deps.iter().map(|t| t.to_string()).collect();

    let expected_deps = vec![
        "root//some:target",
        "cell1//named:target",
        "cell1//:okay",
        "root//some:target2",
        "root//:default1",
        "root//:default2",
        "root//:other",
    ];

    assert_eq!(expected_deps, deps);

    let expected_config_deps = vec!["root//some:config", "cell1//other:config"];
    assert_eq!(expected_config_deps, config_deps);

    Ok(())
}

#[test]
fn test_configured_deps() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let content = indoc!(
        r#"
            ["//some:target", "cell1//named:target[foo]"] + select({
                "//some:config": ["cell1//named:target[bar]"],
                "DEFAULT": ["cell1//:okay"] + select({
                    "cell1//other:config": ["//some:target2"],
                    "DEFAULT": ["//:default1", "//:default2"],
                }),
            }) + ["//:other"]
            "#
    );
    // Check that `x` is captured with the function
    let value = to_value(&env, &globals, content);

    let attr = AttrType::list(AttrType::dep(ProviderIdSet::EMPTY));
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;

    let mut info = ConfiguredAttrInfo::new();
    configured.traverse(PackageLabel::testing(), &mut info)?;

    let expected_deps = vec![
        "root//some:target",
        "cell1//named:target[foo]",
        "cell1//:okay",
        "root//:default1",
        "root//:default2",
        "root//:other",
    ];

    assert_eq!(
        expected_deps.map(|s| format!("{} ({})", s, ConfigurationData::testing_new())),
        info.deps
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
    );

    // Check also that execution deps are handled slightly differently.
    let attr_exec = AttrType::list(AttrType::exec_dep(ProviderIdSet::EMPTY));
    let coerced_exec = attr_exec.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured_exec = coerced_exec.configure(&attr_exec, &configuration_ctx())?;
    let mut info = ConfiguredAttrInfo::new();
    configured_exec.traverse(PackageLabel::testing(), &mut info)?;
    eprintln!("{:?}", info);
    assert_eq!(
        expected_deps.map(|s| format!("{} ({})", s, configuration_ctx().exec_cfg())),
        info.execution_deps
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
    );

    Ok(())
}

#[test]
fn test_resolved_deps() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended()
        .with(register_select)
        .with(buck2_build_api::interpreter::rule_defs::register_rule_defs)
        .build();

    let env = Module::new();
    let content = indoc!(
        r#"
            ["//sub/dir:foo", "//sub/dir:foo[bar]"]
            "#
    );
    // Check that `x` is captured with the function
    let value = to_value(&env, &globals, content);

    let attr = AttrType::list(AttrType::dep(ProviderIdSet::EMPTY));
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    let resolution_ctx = resolution_ctx(&env);
    let resolved = configured.resolve_single(PackageLabel::testing(), &resolution_ctx)?;

    env.set("res", resolved);
    let content = indoc!(
        r#"
            foo = res[0]
            bar = res[1]
            def assert_eq(a, b):
                if a != b:
                    fail("Expected {} == {}".format(a, b))

            assert_eq(foo[DefaultInfo].sub_targets["bar"][DefaultInfo], bar[DefaultInfo])
            assert_eq(
                ("sub/dir", "foo", None),
                (foo.label.package, foo.label.name, foo.label.sub_target)
            )
            assert_eq(
                ("sub/dir", "foo", ["bar"]),
                (bar.label.package, bar.label.name, bar.label.sub_target),
            )
            None
            "#
    );

    let success = to_value(&env, &globals, content);
    assert_eq!(true, success.is_none());
    Ok(())
}

#[test]
fn test_dep_requires_providers() -> anyhow::Result<()> {
    let env = Module::new();
    let (resolution_ctx, provider_ids) = resolution_ctx_with_providers(&env);

    let heap = Heap::new();
    let foo_only = heap.alloc("//sub/dir:foo[foo_only]");

    let attr = AttrType::dep(provider_ids.dupe());
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), foo_only)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;

    let err = configured
        .resolve_single(PackageLabel::testing(), &resolution_ctx)
        .expect_err("Should have failed");
    assert_eq!(
        true,
        err.to_string()
            .contains("required provider `BarInfo` was not found")
    );

    let foo_and_bar = heap.alloc("//sub/dir:foo[foo_and_bar]");

    let attr = AttrType::dep(provider_ids);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), foo_and_bar)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;

    // This dep has both FooInfo and BarInfo, so it should resolve properly
    configured.resolve_single(PackageLabel::testing(), &resolution_ctx)?;

    Ok(())
}

#[test]
fn test_source_missing() {
    let heap = Heap::new();
    let value = heap.alloc(vec!["foo/bar.cpp"]);
    let attr = AttrType::list(AttrType::source(false));

    // FIXME: T85510500 Enable this test properly once we can error out on missing files
    match attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value) {
        Ok(_) => eprintln!("Todo, turn this into an error once T85510500 is fixed"),
        Err(e) => {
            let s = format!("{:#}", e);
            assert!(
                s.contains("Source file `foo/bar.cpp` does not exist"),
                "Got error {}",
                s
            )
        }
    }
}

#[test]
fn test_source_label() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc(vec![
        "//some:target",
        "cell1//named:target[foo]",
        "foo/bar.cpp",
    ]);

    let attr = AttrType::list(AttrType::source(false));

    let coerced = attr.coerce(
        AttrIsConfigurable::Yes,
        &coercion_ctx_listing(PackageListing::testing_files(&["foo/bar.cpp"])),
        value,
    )?;
    assert_eq!(
        "[\"root//some:target\",\"cell1//named:target[foo]\",\"root//package/subdir/foo/bar.cpp\"]",
        coerced
            .as_display(&AttrFmtContext {
                package: Some(PackageLabel::testing())
            })
            .to_string(),
    );

    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        format!(
            "[{},{},{}]",
            format_args!(
                "\"root//some:target ({})\"",
                ConfigurationData::testing_new(),
            ),
            format_args!(
                "\"cell1//named:target[foo] ({})\"",
                ConfigurationData::testing_new()
            ),
            "\"root//package/subdir/foo/bar.cpp\"",
        ),
        configured
            .as_display(&AttrFmtContext {
                package: Some(PackageLabel::testing())
            })
            .to_string(),
    );

    Ok(())
}

#[test]
fn test_source_label_deps() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let content = indoc!(
        r#"
            ["//some:target", "cell1//named:target[foo]", "some/target.cpp"] + select({
                "//some:config": ["cell1//named:target[bar]", "cell1/named/target/bar.cpp"],
                "DEFAULT": ["cell1//:okay", "cell1/okay.cpp"] + select({
                    "cell1//other:config": ["//some:target2", "some/target2.cpp"],
                    "DEFAULT": ["//:default1", "//:default2", "default.cpp"],
                }),
            }) + ["//:other", "other.cpp"]
            "#
    );
    // Check that `x` is captured with the function
    let value = to_value(&env, &globals, content);

    let attr = AttrType::list(AttrType::source(false));
    let coerced = attr.coerce(
        AttrIsConfigurable::Yes,
        &coercion_ctx_listing(PackageListing::testing_files(&[
            "some/target.cpp",
            "cell1/named/target/bar.cpp",
            "cell1/okay.cpp",
            "some/target2.cpp",
            "other.cpp",
            "default.cpp",
        ])),
        value,
    )?;

    let mut visitor = CoercedDepsCollector::new();
    coerced.traverse(&attr, PackageLabel::testing(), &mut visitor)?;
    let CoercedDepsCollector {
        deps,
        configuration_deps,
        ..
    } = visitor;
    let deps: Vec<_> = deps.iter().map(|t| t.to_string()).collect();
    let config_deps: Vec<_> = configuration_deps.iter().map(|t| t.to_string()).collect();

    let expected_deps = vec![
        "root//some:target",
        "cell1//named:target",
        "cell1//:okay",
        "root//some:target2",
        "root//:default1",
        "root//:default2",
        "root//:other",
    ];

    assert_eq!(expected_deps, deps);

    let expected_config_deps = vec!["root//some:config", "cell1//other:config"];
    assert_eq!(expected_config_deps, config_deps);

    Ok(())
}

#[test]
fn test_source_label_resolution() -> anyhow::Result<()> {
    fn resolve_and_test(content: &str, test_content: &str, files: &[&str]) -> anyhow::Result<()> {
        let env = Module::new();

        let globals = GlobalsBuilder::extended()
            .with(register_select)
            .with(register_builtin_providers)
            .build();

        let value = to_value(&env, &globals, content);

        let attr = AttrType::list(AttrType::source(false));
        let coerced = attr.coerce(
            AttrIsConfigurable::Yes,
            &coercion_ctx_listing(PackageListing::testing_files(files)),
            value,
        )?;
        let configured = coerced.configure(&attr, &configuration_ctx())?;
        let resolution_ctx = resolution_ctx(&env);
        let resolved = configured.resolve_single(PackageLabel::testing(), &resolution_ctx)?;

        env.set("res", resolved);
        let success = to_value(&env, &globals, test_content);
        assert_eq!(true, success.is_none());
        Ok(())
    }

    let content = indoc!(r#"["//sub/dir:foo", "//sub/dir:foo[multiple]", "baz/quz.cpp"]"#);
    let test_content = indoc!(
        r#"
            def assert_eq(a, b):
                if a != b:
                    fail("Expected {} == {}".format(a, b))

            expected = ["default.cpp", "bar1.cpp", "bar2.cpp", "bar3.cpp", "quz.cpp"]
            names = [f.basename for f in res]
            assert_eq(expected, names)
            None
            "#
    );
    resolve_and_test(content, test_content, &["baz/quz.cpp"])?;

    let content = indoc!(r#"["//sub/dir:foo", "//sub/dir:foo[single]", "baz/quz.cpp"]"#);
    let test_content = indoc!(
        r#"
            def assert_eq(a, b):
                if a != b:
                    fail("Expected {} == {}".format(a, b))

            expected = ["default.cpp", "bar1.cpp", "quz.cpp"]
            names = [f.basename for f in res]
            assert_eq(expected, names)
            None
            "#
    );
    resolve_and_test(content, test_content, &["baz/quz.cpp"])?;

    let content = indoc!(r#"["//sub/dir:foo", "//sub/dir:foo[zero]", "baz/quz.cpp"]"#);
    let test_content = indoc!(
        r#"
            def assert_eq(a, b):
                if a != b:
                    fail("Expected {} == {}".format(a, b))

            expected = ["default.cpp", "quz.cpp"]
            names = [f.basename for f in res]
            assert_eq(expected, names)
            None
            "#
    );
    resolve_and_test(content, test_content, &["baz/quz.cpp"])
}

#[test]
fn test_single_source_label_fails_if_multiple_returned() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc("//sub/dir:foo[multiple]");
    let env = Module::new();

    let attr = AttrType::source(false);
    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    let resolution_ctx = resolution_ctx(&env);
    let err = configured
        .resolve_single(PackageLabel::testing(), &resolution_ctx)
        .expect_err("Getting multiple values when expecting a single one should fail");

    assert_eq!(true, err.to_string().contains("Expected a single artifact"));
    assert_eq!(true, err.to_string().contains("3 artifacts"));
    Ok(())
}

#[test]
fn test_arg() -> anyhow::Result<()> {
    let heap = Heap::new();
    let value = heap.alloc("$(exe //some:exe) --file=$(location \"//some:location\")");

    let attr = AttrType::arg(false);

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    // Note that targets are canonicalized.
    assert_eq!(
        "\"$(exe root//some:exe) --file=$(location root//some:location)\"",
        coerced.as_display_no_ctx().to_string()
    );
    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        format!(
            "\"$(exe root//some:exe ({})) --file=$(location root//some:location ({}))\"",
            configuration_ctx().exec_cfg(),
            ConfigurationData::testing_new(),
        ),
        configured.as_display_no_ctx().to_string()
    );

    let mut visitor = CoercedDepsCollector::new();
    coerced.traverse(&attr, PackageLabel::testing(), &mut visitor)?;
    let CoercedDepsCollector {
        deps, exec_deps, ..
    } = visitor;
    let deps: Vec<_> = deps.iter().map(|t| t.to_string()).collect();
    let exec_deps: Vec<_> = exec_deps.iter().map(|t| t.to_string()).collect();

    let mut info = ConfiguredAttrInfo::new();
    configured.traverse(PackageLabel::testing(), &mut info)?;

    let expected_deps = vec!["root//some:location"];
    let expected_exec_deps = vec!["root//some:exe"];
    let expected_configured_deps = vec![format!(
        "root//some:location ({})",
        ConfigurationData::testing_new()
    )];
    let expected_configured_exec_deps = vec![format!(
        "root//some:exe ({})",
        configuration_ctx().exec_cfg()
    )];

    assert_eq!(expected_deps, deps);
    assert_eq!(expected_exec_deps, exec_deps);

    assert_eq!(
        expected_configured_deps,
        info.deps
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
    );
    assert_eq!(
        expected_configured_exec_deps,
        info.execution_deps
            .iter()
            .map(ToString::to_string)
            .collect::<Vec<_>>()
    );

    Ok(())
}

#[test]
fn test_bool() -> anyhow::Result<()> {
    let globals = GlobalsBuilder::extended().with(register_select).build();

    let env = Module::new();
    let value = to_value(
        &env,
        &globals,
        indoc!(
            r#"
                (
                    [True, False]
                    + select({
                        "//some:config": [True],
                        "DEFAULT": [False],
                    })
                    + [True]
                )
                "#
        ),
    );

    let attr = AttrType::list(AttrType::bool());

    let coerced = attr.coerce(AttrIsConfigurable::Yes, &coercion_ctx(), value)?;
    assert_eq!(
        "[True,False]+select(\"root//some:config\"=[True],\"DEFAULT\"=[False])+[True]",
        coerced.as_display_no_ctx().to_string()
    );

    let configured = coerced.configure(&attr, &configuration_ctx())?;
    assert_eq!(
        "[True,False,False,True]",
        configured.as_display_no_ctx().to_string()
    );

    let ctx = resolution_ctx(&env);
    let resolved = configured.resolve_single(PackageLabel::testing(), &ctx)?;
    assert_eq!("[True, False, False, True]", resolved.to_string());

    Ok(())
}

#[test]
fn test_user_placeholders() -> anyhow::Result<()> {
    let env = Module::new();

    let globals = GlobalsBuilder::extended()
        .with(register_select)
        .with(register_builtin_providers)
        .build();

    let resolve = move |value: &str| {
        let attr = AttrType::arg(false);
        let coerced = attr.coerce(
            AttrIsConfigurable::Yes,
            &coercion_ctx(),
            to_value(&env, &globals, value),
        )?;
        let configured = coerced.configure(&attr, &configuration_ctx())?;
        let resolution_ctx = resolution_ctx(&env);
        configured
            .resolve_single(PackageLabel::testing(), &resolution_ctx)
            .map(|v| {
                // TODO: this is way too unnecessarily verbose for a test.
                let project_fs = ProjectRoot::new(
                    AbsNormPathBuf::try_from(std::env::current_dir().unwrap()).unwrap(),
                )
                .unwrap();
                let fs = ArtifactFs::new(
                    CellResolver::testing_with_name_and_path(
                        CellName::testing_new("cell"),
                        CellRootPathBuf::new(ProjectRelativePathBuf::unchecked_new(
                            "cell_path".into(),
                        )),
                    ),
                    BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new(
                        "buck_out/v2".into(),
                    )),
                    project_fs,
                );
                let executor_fs = ExecutorFs::new(&fs, PathSeparatorKind::Unix);

                let mut cli = Vec::<String>::new();
                let mut ctx = DefaultCommandLineContext::new(&executor_fs);
                v.as_command_line()
                    .unwrap()
                    .add_to_command_line(&mut cli, &mut ctx)
                    .unwrap();
                cli.join(" ")
            })
    };

    assert_eq!("clang++", resolve(r#""$(CXX)""#)?);
    assert_eq!(
        "hello",
        resolve(r#""$(user_key //sub/dir:keyed_placeholder)""#)?
    );
    assert_eq!(
        "world",
        resolve(r#""$(key_with_args //sub/dir:keyed_placeholder)""#)?
    );
    assert_eq!(
        "big world",
        resolve(r#""$(key_with_args //sub/dir:keyed_placeholder big)""#)?
    );

    let value = r#""$(CXXabcdef)""#;
    match resolve(value) {
        Ok(..) => panic!("expected error resolving {}", value),
        Err(e) => {
            let expected = "no mapping for CXXabcdef";
            let message = format!("{:?}", e);
            assert!(
                message.contains(expected),
                "expected `{}` to contain `{}`",
                message,
                expected
            );
        }
    }

    let value = r#""$(missing_user_key //sub/dir:keyed_placeholder)""#;
    match resolve(value) {
        Ok(..) => panic!("expected error resolving {}", value),
        Err(e) => {
            let expected = "no mapping for missing_user_key";
            let message = format!("{:?}", e);
            assert!(
                message.contains(expected),
                "expected `{}` to contain `{}`",
                message,
                expected
            );
        }
    }

    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub(crate) mod testing;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::sync::Arc;

use buck2_analysis::attrs::resolve::ctx::AnalysisQueryResult;
use buck2_analysis::attrs::resolve::ctx::AttrResolutionContext;
use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_build_api::interpreter::rule_defs::cmd_args::value::FrozenCommandLineArg;
use buck2_build_api::interpreter::rule_defs::provider::builtin::template_placeholder_info::FrozenTemplatePlaceholderInfo;
use buck2_build_api::interpreter::rule_defs::provider::collection::FrozenProviderCollectionValue;
use buck2_build_api::interpreter::rule_defs::provider::registration::register_builtin_providers;
use buck2_common::result::SharedResult;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::provider::label::ConfiguredProvidersLabel;
use buck2_interpreter::types::provider::callable::ValueAsProviderCallableLike;
use buck2_interpreter_for_build::attrs::coerce;
use buck2_interpreter_for_build::attrs::coerce::testing;
use buck2_node::attrs::coercion_context::AttrCoercionContext;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use buck2_node::provider_id_set::ProviderIdSet;
use dupe::Dupe;
use dupe::OptionDupedExt;
use indoc::indoc;
use starlark::environment::FrozenModule;
use starlark::environment::Globals;
use starlark::environment::GlobalsBuilder;
use starlark::environment::Module;
use starlark::values::dict::FrozenDictRef;
use starlark_map::small_map::SmallMap;
use starlark_map::smallmap;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

pub(crate) fn resolution_ctx<'v>(module: &'v Module) -> impl AttrResolutionContext<'v> {
    resolution_ctx_with_providers(module).0
}

pub(crate) fn resolution_ctx_with_providers<'v>(
    module: &'v Module,
) -> (impl AttrResolutionContext<'v>, ProviderIdSet) {
    struct Ctx<'v> {
        module: &'v Module,
        // This module needs to be kept alive in order for the FrozenValues to stick around
        _deps_env: FrozenModule,
        deps: SmallMap<ConfiguredProvidersLabel, FrozenProviderCollectionValue>,
    }

    impl<'v> Ctx<'v> {
        fn eval(env: &Module, globals: &Globals) {
            testing::to_value(
                env,
                globals,
                // Note that putting a list of providers into the subtargets of a DefaultInfo will
                // convert it into a provider collection (which we expect later on). This is why
                // we do that and don't use lists of providers directly.
                indoc!(
                    r#"
             foo_a = source_artifact("", "default.cpp")
             bar_a = source_artifact("", "bar.cpp")
             bar1_a = source_artifact("", "bar1.cpp")
             bar2_a = source_artifact("", "bar2.cpp")
             bar3_a = source_artifact("", "bar3.cpp")
             bar = DefaultInfo(default_outputs=[bar_a])
             zero = DefaultInfo(default_outputs=[])
             single = DefaultInfo(default_outputs=[bar1_a])
             multiple = DefaultInfo(default_outputs=[bar1_a, bar2_a, bar3_a])
             foo = DefaultInfo(
                 sub_targets={
                     "bar": [bar],
                     "zero": [zero],
                     "single": [single],
                     "multiple": [multiple],
                     "foo_only": [FooInfo(foo="f1")],
                     "foo_and_bar": [FooInfo(foo="f1"), BarInfo(bar="b1")],
                 },
                 default_outputs=[foo_a],
             )
             outer = DefaultInfo(sub_targets={
                 "foo": [foo],
                 "toolchain": [zero, TemplatePlaceholderInfo(unkeyed_variables = {"CXX": "clang++"})],
                 "keyed_placeholder": [zero, TemplatePlaceholderInfo(keyed_variables = {
                     "user_key": "hello",
                     "key_with_args": { "DEFAULT": "world", "big": "big world" },
                 })]
             })
             ret = {
                 "foo": outer.sub_targets["foo"],
                 "bar": foo.sub_targets["bar"],
                 "zero": foo.sub_targets["zero"],
                 "single": foo.sub_targets["single"],
                 "multiple": foo.sub_targets["multiple"],
                 "foo_only": foo.sub_targets["foo_only"],
                 "foo_and_bar": foo.sub_targets["foo_and_bar"],
                 "toolchain": outer.sub_targets["toolchain"],
                 "keyed_placeholder": outer.sub_targets["keyed_placeholder"],
             }
             "#
                ),
            );
        }

        /// Create a simple label -> impl result mapping
        fn simple_deps() -> (
            FrozenModule,
            SmallMap<ConfiguredProvidersLabel, FrozenProviderCollectionValue>,
            ProviderIdSet,
        ) {
            let globals = GlobalsBuilder::extended()
                .with(register_builtin_providers)
                .with(register_provider)
                .with(artifactory)
                .build();

            // Add some providers into the environment
            let provider_env = Module::new();
            let provider_content = indoc!(
                r#"
                 FooInfo = provider(fields=["foo"])
                 BarInfo = provider(fields=["bar"])
                 None
                 "#
            );
            testing::to_value(&provider_env, &globals, provider_content);
            let frozen_provider_env = provider_env
                .freeze()
                .expect("provider should freeze successfully");
            let foo_info = frozen_provider_env.get("FooInfo").unwrap();
            let bar_info = frozen_provider_env.get("BarInfo").unwrap();

            let env = Module::new();
            env.frozen_heap()
                .add_reference(frozen_provider_env.frozen_heap());
            env.set("FooInfo", foo_info.value());
            env.set("BarInfo", bar_info.value());
            Self::eval(&env, &globals);

            let frozen = env.freeze().expect("should freeze successfully");
            let label_and_result = |label, var_name| {
                let configured_label = coerce::testing::coercion_ctx()
                    .coerce_label(label)
                    .unwrap()
                    .configure(ConfigurationData::testing_new());
                let val = FrozenProviderCollectionValue::try_from_value(
                    frozen.get("ret").unwrap().map(|x| {
                        FrozenDictRef::from_frozen_value(x)
                            .unwrap()
                            .get_str(var_name)
                            .unwrap()
                    }),
                )
                .unwrap();
                (configured_label, val)
            };

            let (foo_label, foo_result) = label_and_result("//sub/dir:foo", "foo");
            let (bar_label, bar_result) = label_and_result("//sub/dir:foo[bar]", "bar");
            let (zero_label, zero_result) = label_and_result("//sub/dir:foo[zero]", "zero");
            let (single_label, single_result) = label_and_result("//sub/dir:foo[single]", "single");
            let (multiple_label, multiple_result) =
                label_and_result("//sub/dir:foo[multiple]", "multiple");
            let (foo_only_label, foo_only_result) =
                label_and_result("//sub/dir:foo[foo_only]", "foo_only");
            let (foo_and_bar_label, foo_and_bar_result) =
                label_and_result("//sub/dir:foo[foo_and_bar]", "foo_and_bar");

            let (toolchain_label, toolchain_result) =
                label_and_result("//sub/dir:toolchain", "toolchain");
            let (keyed_placeholder_label, keyed_placeholder_result) =
                label_and_result("//sub/dir:keyed_placeholder", "keyed_placeholder");

            let res = smallmap![
                foo_label => foo_result,
                bar_label => bar_result,
                zero_label => zero_result,
                single_label => single_result,
                multiple_label => multiple_result,
                foo_only_label => foo_only_result,
                foo_and_bar_label => foo_and_bar_result,
                toolchain_label => toolchain_result,
                keyed_placeholder_label => keyed_placeholder_result
            ];

            let provider_ids = ProviderIdSet::from(vec![
                foo_info
                    .value()
                    .as_provider_callable()
                    .unwrap()
                    .id()
                    .unwrap()
                    .dupe(),
                bar_info
                    .value()
                    .as_provider_callable()
                    .unwrap()
                    .id()
                    .unwrap()
                    .dupe(),
            ]);
            (frozen, res, provider_ids)
        }
    }

    impl<'v> AttrResolutionContext<'v> for Ctx<'v> {
        fn starlark_module(&self) -> &'v Module {
            self.module
        }

        fn get_dep(
            &self,
            target: &ConfiguredProvidersLabel,
        ) -> anyhow::Result<FrozenProviderCollectionValue> {
            self.deps
                .get(target)
                .duped()
                .ok_or_else(|| anyhow::anyhow!("missing dep"))
        }

        fn resolve_unkeyed_placeholder(
            &self,
            name: &str,
        ) -> anyhow::Result<Option<FrozenCommandLineArg>> {
            for providers in self.deps.values() {
                if let Some(placeholders) =
                    FrozenTemplatePlaceholderInfo::from_providers(providers.provider_collection())
                {
                    if let Some(value) = placeholders.unkeyed_variables().get(name) {
                        return Ok(Some(*value));
                    }
                }
            }
            Ok(None)
        }

        fn resolve_query(&self, _query: &str) -> SharedResult<Arc<AnalysisQueryResult>> {
            unimplemented!("This test resolution context doesn't handle queries")
        }

        fn execution_platform_resolution(&self) -> &ExecutionPlatformResolution {
            unimplemented!("This test resolution context doesn't use execution platform resolution")
        }
    }
    let (deps_env, deps, provider_ids) = Ctx::simple_deps();
    (
        Ctx {
            module,
            _deps_env: deps_env,
            deps,
        },
        provider_ids,
    )
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use std::any;
use std::any::Demand;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;

use allocative::Allocative;
use buck2_analysis::analysis::calculation::AnalysisKey;
use buck2_build_api::analysis::AnalysisResult;
use buck2_build_api::deferred::calculation::DeferredCalculation;
use buck2_build_api::deferred::types::BaseKey;
use buck2_build_api::deferred::types::Deferred;
use buck2_build_api::deferred::types::DeferredCtx;
use buck2_build_api::deferred::types::DeferredInput;
use buck2_build_api::deferred::types::DeferredRegistry;
use buck2_build_api::deferred::types::DeferredTable;
use buck2_build_api::deferred::types::DeferredValue;
use buck2_common::dice::data::testing::SetTestingIoProvider;
use buck2_common::executor_config::CommandExecutorConfig;
use buck2_common::result::ToSharedResultExt;
use buck2_configured::nodes::calculation::ConfiguredTargetNodeKey;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::configuration::compatibility::MaybeCompatible;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::fs::project::ProjectRootTemp;
use buck2_core::target::label::TargetLabel;
use buck2_execute::digest_config::DigestConfig;
use buck2_execute::digest_config::SetDigestConfig;
use buck2_execute::execute::dice_data::set_fallback_executor_config;
use buck2_node::nodes::configured::ConfiguredTargetNode;
use dice::testing::DiceBuilder;
use dice::UserComputationData;
use dupe::Dupe;
use indexmap::IndexSet;
use indoc::indoc;

use crate::interpreter::rule_defs::provider::testing::FrozenProviderCollectionValueExt;

#[derive(Allocative)]
struct FakeDeferred(usize, IndexSet<DeferredInput>, Arc<AtomicBool>);

impl any::Provider for FakeDeferred {
    fn provide<'a>(&'a self, _demand: &mut Demand<'a>) {}
}

impl Deferred for FakeDeferred {
    type Output = usize;

    fn inputs(&self) -> &IndexSet<DeferredInput> {
        &self.1
    }

    fn execute(&self, _ctx: &mut dyn DeferredCtx) -> anyhow::Result<DeferredValue<Self::Output>> {
        self.2.store(true, Ordering::SeqCst);
        Ok(DeferredValue::Ready(self.0))
    }
}

#[tokio::test]
async fn lookup_deferred_from_analysis() -> anyhow::Result<()> {
    let target =
        TargetLabel::testing_parse("cell//pkg:foo").configure(ConfigurationData::testing_new());
    let analysis_key = AnalysisKey(target.dupe());
    let configured_node_key = ConfiguredTargetNodeKey(target.dupe());

    let provider_collection = FrozenProviderCollectionValueExt::testing_new(indoc!(
        r#"
        Foo = provider(fields=["x"])
        [DefaultInfo(default_outputs=[]), Foo(x=1)]
        "#
    ));

    let mut deferred =
        DeferredRegistry::new(BaseKey::Base(BaseDeferredKey::TargetLabel(target.dupe())));

    let executed0 = Arc::new(AtomicBool::new(false));
    let executed1 = Arc::new(AtomicBool::new(false));
    let data0 = deferred.defer(FakeDeferred(1, IndexSet::new(), executed0.dupe()));
    let data1 = deferred.defer(FakeDeferred(5, IndexSet::new(), executed1.dupe()));
    let deferred_result = DeferredTable::new(deferred.take_result()?);

    let fs = ProjectRootTemp::new()?;
    let dice = DiceBuilder::new()
        .set_data(|data| {
            data.set_testing_io_provider(&fs);
            data.set_digest_config(DigestConfig::testing_default());
        })
        .mock_and_return(
            analysis_key,
            anyhow::Ok(MaybeCompatible::Compatible(AnalysisResult::new(
                provider_collection,
                deferred_result,
                None,
            )))
            .shared_error(),
        )
        .mock_and_return(
            configured_node_key,
            Ok(MaybeCompatible::Compatible(
                ConfiguredTargetNode::testing_new(target.dupe(), "foo_lib"),
            )),
        );

    let mut dice_data = UserComputationData::new();
    set_fallback_executor_config(&mut dice_data.data, CommandExecutorConfig::testing_local());

    let dice = dice.build(dice_data)?.commit().await;
    let deferred_result = dice.compute_deferred_data(&data0).await?;
    assert_eq!(*deferred_result, 1);
    assert_eq!(executed0.load(Ordering::SeqCst), true);
    // we should cache deferred execution
    executed0.store(false, Ordering::SeqCst);
    let deferred_result = dice.compute_deferred_data(&data0).await?;
    assert_eq!(*deferred_result, 1);
    assert_eq!(executed0.load(Ordering::SeqCst), false);

    let deferred_result = dice.compute_deferred_data(&data1).await?;
    assert_eq!(*deferred_result, 5);
    assert_eq!(executed1.load(Ordering::SeqCst), true);
    // we should cache deferred execution
    executed1.store(false, Ordering::SeqCst);
    assert_eq!(*deferred_result, 5);
    assert_eq!(executed1.load(Ordering::SeqCst), false);

    Ok(())
}

#[tokio::test]
async fn lookup_deferred_that_has_deferreds() -> anyhow::Result<()> {
    #[derive(Allocative)]
    struct DeferringDeferred(usize, IndexSet<DeferredInput>, Arc<AtomicBool>);

    impl any::Provider for DeferringDeferred {
        fn provide<'a>(&'a self, _demand: &mut Demand<'a>) {}
    }

    impl Deferred for DeferringDeferred {
        type Output = usize;

        fn inputs(&self) -> &IndexSet<DeferredInput> {
            &self.1
        }

        fn execute(
            &self,
            ctx: &mut dyn DeferredCtx,
        ) -> anyhow::Result<DeferredValue<Self::Output>> {
            let data = ctx
                .registry()
                .defer(FakeDeferred(self.0, self.1.clone(), self.2.dupe()));
            Ok(DeferredValue::Deferred(data))
        }
    }

    let target =
        TargetLabel::testing_parse("cell//pkg:foo").configure(ConfigurationData::testing_new());
    let analysis_key = AnalysisKey(target.dupe());
    let configured_node_key = ConfiguredTargetNodeKey(target.dupe());

    let provider_collection = FrozenProviderCollectionValueExt::testing_new(indoc!(
        r#"
        Foo = provider(fields=["x"])
        [DefaultInfo(default_outputs=[]), Foo(x=1)]
        "#
    ));

    let mut deferred =
        DeferredRegistry::new(BaseKey::Base(BaseDeferredKey::TargetLabel(target.dupe())));

    let executed = Arc::new(AtomicBool::new(false));
    let data = deferred.defer(DeferringDeferred(8, IndexSet::new(), executed.dupe()));
    let deferred_result = DeferredTable::new(deferred.take_result()?);

    let fs = ProjectRootTemp::new()?;
    let dice = DiceBuilder::new()
        .set_data(|data| {
            data.set_testing_io_provider(&fs);
            data.set_digest_config(DigestConfig::testing_default());
        })
        .mock_and_return(
            analysis_key,
            anyhow::Ok(MaybeCompatible::Compatible(AnalysisResult::new(
                provider_collection,
                deferred_result,
                None,
            )))
            .shared_error(),
        )
        .mock_and_return(
            configured_node_key,
            Ok(MaybeCompatible::Compatible(
                ConfiguredTargetNode::testing_new(target.dupe(), "foo_lib"),
            )),
        );

    let mut dice_data = UserComputationData::new();
    set_fallback_executor_config(&mut dice_data.data, CommandExecutorConfig::testing_local());

    let dice = dice.build(dice_data)?.commit().await;
    let deferred_result = dice.compute_deferred_data(&data).await?;
    assert_eq!(*deferred_result, 8);
    assert_eq!(executed.load(Ordering::SeqCst), true);
    // we should cache deferred execution
    executed.store(false, Ordering::SeqCst);
    let deferred_result = dice.compute_deferred_data(&data).await?;
    assert_eq!(*deferred_result, 8);
    assert_eq!(executed.load(Ordering::SeqCst), false);

    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

mod calculation;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_build_api::interpreter::build_defs::register_provider;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

#[test]
fn test_provider() -> anyhow::Result<()> {
    // TODO: test restricting field names
    let mut tester = Tester::new().unwrap();
    tester.additional_globals(register_provider);
    tester.run_starlark_test(indoc!(
        r#"
            SomeInfo = provider(fields=["x", "y"])
            SomeOtherInfo = provider(fields={"x": "docs for x", "y": "docs for y"})
            DocInfo = provider(doc="Some docs", fields=["x", "y"])

            def test():
                instance = SomeInfo(x = 2, y = True)
                assert_eq(2, instance.x)
                assert_eq(True, instance.y)
                assert_eq(SomeInfo(x = 2, y = True), instance)

                instance = SomeOtherInfo(x = 2, y = True)
                assert_eq(2, instance.x)
                assert_eq(True, instance.y)
                assert_eq(SomeOtherInfo(x = 2, y = True), instance)

                instance = DocInfo(x = 2, y = True)
                assert_eq(2, instance.x)
                assert_eq(True, instance.y)
                assert_eq(DocInfo(x = 2, y = True), instance)
            "#
    ))?;
    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

mod build_defs;
pub(crate) mod rule_defs;
pub(crate) mod transitive_set;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub(crate) mod artifact;
pub(crate) mod artifact_tagging;
pub(crate) mod cmd_args;
pub(crate) mod provider;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_common::result::SharedResult;
use buck2_interpreter_for_build::interpreter::testing::expect_error;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact::testing::artifactory;

pub(crate) mod testing;

#[test]
fn source_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
        r#"
            a1 = source_artifact("foo/bar", "baz/quz.h")
            a2 = source_artifact("foo/bar", "baz/file1")

            def test():
                a3 = source_artifact("foo/bar", "baz/quz.cpp")
                a4 = source_artifact("foo/bar", "baz/file2")

                assert_eq("<source foo/bar/baz/quz.h>", repr(a1))
                assert_eq("quz.h", a1.basename)
                assert_eq("baz/quz.h", a1.short_path)
                assert_eq(".h", a1.extension)
                assert_eq(True, a1.is_source)
                assert_eq(None, a1.owner)

                assert_eq("<source foo/bar/baz/file1>", repr(a2))
                assert_eq("file1", a2.basename)
                assert_eq("baz/file1", a2.short_path)
                assert_eq("", a2.extension)
                assert_eq(True, a2.is_source)
                assert_eq(None, a2.owner)

                assert_eq("<source foo/bar/baz/quz.cpp>", repr(a3))
                assert_eq("quz.cpp", a3.basename)
                assert_eq("baz/quz.cpp", a3.short_path)
                assert_eq(".cpp", a3.extension)
                assert_eq(True, a3.is_source)
                assert_eq(None, a3.owner)

                assert_eq("<source foo/bar/baz/file2>", repr(a4))
                assert_eq("file2", a4.basename)
                assert_eq("baz/file2", a4.short_path)
                assert_eq("", a4.extension)
                assert_eq(True, a4.is_source)
                assert_eq(None, a4.owner)

                # Validate that attrs are setup properly
                for a in (a1, a2, a3, a4):
                    for prop in dir(a):
                        assert_eq(True, hasattr(a, prop))
                        if prop != "as_output":
                            getattr(a, prop)
            "#
    ))?;

    let as_output = indoc!(
        r#"
            def test():
                source_artifact("foo/bar", "baz/quz.cpp").as_output()
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(as_output),
        as_output,
        "Source artifacts may not be outputs",
    );
    Ok(())
}

#[test]
fn bound_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(buck2_build_api::interpreter::rule_defs::register_rule_defs);
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
            r#"
            a1 = bound_artifact("//foo:bar", "baz/quz.h")
            a2 = bound_artifact("//foo:bar", "baz/file1")

            def test():
                a3 = bound_artifact("//foo:bar", "baz/quz.cpp")
                a4 = bound_artifact("//foo:bar", "baz/file2")

                assert_eq_ignore_hash("<build artifact baz/quz.h bound to root//foo:bar (<testing>#<HASH>)>", repr(a1))
                assert_eq("quz.h", a1.basename)
                assert_eq("baz/quz.h", a1.short_path)
                assert_eq(".h", a1.extension)
                assert_eq(False, a1.is_source)
                assert_eq("bar", a1.owner.name)

                assert_eq_ignore_hash("<build artifact baz/file1 bound to root//foo:bar (<testing>#<HASH>)>", repr(a2))
                assert_eq("file1", a2.basename)
                assert_eq("baz/file1", a2.short_path)
                assert_eq("", a2.extension)
                assert_eq(False, a2.is_source)
                assert_eq("bar", a2.owner.name)

                assert_eq_ignore_hash("<build artifact baz/quz.cpp bound to root//foo:bar (<testing>#<HASH>)>", repr(a3))
                assert_eq("quz.cpp", a3.basename)
                assert_eq("baz/quz.cpp", a3.short_path)
                assert_eq(".cpp", a3.extension)
                assert_eq(False, a3.is_source)
                assert_eq("bar", a3.owner.name)

                assert_eq_ignore_hash("<build artifact baz/file2 bound to root//foo:bar (<testing>#<HASH>)>", repr(a4))
                assert_eq("file2", a4.basename)
                assert_eq("baz/file2", a4.short_path)
                assert_eq("", a4.extension)
                assert_eq(False, a4.is_source)
                assert_eq("bar", a4.owner.name)

                # Validate that attrs are setup properly
                for a in (a1, a2, a3, a4):
                    for prop in dir(a):
                        assert_eq(True, hasattr(a, prop))
                        if prop != "as_output":
                            getattr(a, prop)
            "#
        ))?;

    let as_output = indoc!(
        r#"
            def test():
                bound_artifact("//foo:bar", "baz/quz.cpp").as_output()
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(as_output),
        as_output,
        "already used",
    );
    Ok(())
}

#[test]
fn declared_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
        r#"
            def test():
                a1 = declared_artifact("baz/quz.cpp")
                a2 = declared_artifact("baz/file2")

                assert_eq("<build artifact baz/quz.cpp>", repr(a1))
                assert_eq("quz.cpp", a1.basename)
                assert_eq(".cpp", a1.extension)
                assert_eq(False, a1.is_source)
                assert_eq(None, a1.owner)
                assert_eq("<output artifact for baz/quz.cpp>", repr(a1.as_output()))

                assert_eq("<build artifact baz/file2>", repr(a2))
                assert_eq("file2", a2.basename)
                assert_eq("", a2.extension)
                assert_eq(False, a2.is_source)
                assert_eq(None, a2.owner)
                assert_eq("<output artifact for baz/file2>", repr(a2.as_output()))

                # Validate that attrs are setup properly
                for a in (a1, a2):
                    for prop in dir(a):
                        assert_eq(True, hasattr(a, prop))
                        if prop != "as_output":
                            getattr(a, prop)
            "#
    ))?;
    Ok(())
}

#[test]
fn declared_bound() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(buck2_build_api::interpreter::rule_defs::register_rule_defs);
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
            r#"
            a1 = declared_bound_artifact("//foo:bar", "baz/quz.h")
            a2 = declared_bound_artifact("//foo:bar", "baz/file1")

            def test():
                a3 = declared_bound_artifact("//foo:bar", "baz/quz.cpp")
                a4 = declared_bound_artifact("//foo:bar", "baz/file2")

                assert_eq_ignore_hash("<build artifact baz/quz.h bound to root//foo:bar (<testing>#<HASH>)>", repr(a1))
                assert_eq("quz.h", a1.basename)
                assert_eq("baz/quz.h", a1.short_path)
                assert_eq(".h", a1.extension)
                assert_eq(False, a1.is_source)
                assert_eq("bar", a1.owner.name)

                assert_eq_ignore_hash("<build artifact baz/file1 bound to root//foo:bar (<testing>#<HASH>)>", repr(a2))
                assert_eq("file1", a2.basename)
                assert_eq("baz/file1", a2.short_path)
                assert_eq("", a2.extension)
                assert_eq(False, a2.is_source)
                assert_eq("bar", a2.owner.name)

                assert_eq_ignore_hash("<build artifact baz/quz.cpp bound to root//foo:bar (<testing>#<HASH>)>", repr(a3))
                assert_eq("quz.cpp", a3.basename)
                assert_eq("baz/quz.cpp", a3.short_path)
                assert_eq(".cpp", a3.extension)
                assert_eq(False, a3.is_source)
                assert_eq("bar", a3.owner.name)

                assert_eq_ignore_hash("<build artifact baz/file2 bound to root//foo:bar (<testing>#<HASH>)>", repr(a4))
                assert_eq("file2", a4.basename)
                assert_eq("baz/file2", a4.short_path)
                assert_eq("", a4.extension)
                assert_eq(False, a4.is_source)
                assert_eq("bar", a4.owner.name)

                # Validate that attrs are setup properly
                for a in (a1, a2, a3, a4):
                    for prop in dir(a):
                        assert_eq(True, hasattr(a, prop))
                        if prop != "as_output":
                            getattr(a, prop)
            "#
        ))?;

    Ok(())
}

#[test]
fn project_declared_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
            r#"
            def test():
                bound = declared_bound_artifact("//foo:bar", "out").project("baz.o")
                assert_eq_ignore_hash("<build artifact out/baz.o bound to root//foo:bar (<testing>#<HASH>)>", repr(bound))
                assert_eq("baz.o", bound.basename)
                assert_eq(".o", bound.extension)

                unbound = declared_artifact("out").project("qux.so")
                assert_eq("<build artifact out/qux.so>", repr(unbound))
                assert_eq("<output artifact for out/qux.so>", repr(unbound.as_output()))
                assert_eq("qux.so", unbound.basename)
                assert_eq(".so", unbound.extension)
            "#
        ))?;
    Ok(())
}

#[test]
fn test_short_path() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
            r#"
            def test():
                test = declared_artifact("foo/bar/baz")
                assert_eq("foo/bar/baz", test.short_path)

                test = declared_artifact("foo").project("bar/baz")
                assert_eq("foo/bar/baz", test.short_path)

                test = declared_artifact("foo").project("bar").project("baz")
                assert_eq("foo/bar/baz", test.short_path)

                test = declared_artifact("foo").project("bar/baz", hide_prefix=True)
                assert_eq("bar/baz", test.short_path)

                test = declared_artifact("foo").project("bar").project("baz", hide_prefix=True)
                assert_eq("baz", test.short_path)

                test = declared_artifact("foo").project("bar", hide_prefix=True).project("baz")
                assert_eq("bar/baz", test.short_path)

                test = declared_artifact("foo").project("bar", hide_prefix=True).project("baz", hide_prefix=True)
                assert_eq("baz", test.short_path)
            "#
        ))?;
    Ok(())
}

#[test]
fn project_source_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    let test = indoc!(
        r#"
            def test():
                source_artifact("foo/bar", "baz").project("foo")
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(test),
        test,
        "Source artifacts cannot be projected",
    );
    Ok(())
}

#[test]
fn project_artifact() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    let test = indoc!(
        r#"
            def test():
                bound_artifact("//foo:bar", "baz").project("foo")
            "#
    );
    expect_error(
        tester.run_starlark_bzl_test(test),
        test,
        "This artifact was declared by another rule",
    );
    Ok(())
}

#[test]
fn stringifies_for_command_line() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
            r#"
            a1 = bound_artifact("//foo:bar", "baz/quz.h")
            a2 = source_artifact("foo/bar", "baz/file1")

            def test():
                a3 = bound_artifact("//foo:bar", "baz/quz.cpp")
                a4 = source_artifact("foo/bar", "baz/file2")

                assert_eq_ignore_hash("buck-out/v2/gen/root/<HASH>/foo/__bar__/baz/quz.h", stringify_for_cli(a1))
                assert_eq("foo/bar/baz/file1", stringify_for_cli(a2))
                assert_eq_ignore_hash("buck-out/v2/gen/root/<HASH>/foo/__bar__/baz/quz.cpp", stringify_for_cli(a3))
                assert_eq("foo/bar/baz/file2", stringify_for_cli(a4))
            "#
        ))
}

#[test]
fn bound_artifact_with_associated_artifacts() -> SharedResult<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(buck2_build_api::interpreter::rule_defs::register_rule_defs);
    tester.additional_globals(artifactory);
    tester.run_starlark_bzl_test(indoc!(
        r#"
            def test():
                # declare an artifact (a2) with string and add an associated artifact (a1)
                a1 = source_artifact("foo/bar", "baz/file1")
                a2 = declared_bound_artifact_with_associated_artifacts("baz/quz.h", [a1])
                assert_eq(a2.short_path, "baz/quz.h")
                assert_eq(get_associated_artifacts_as_string(a1), "")
                assert_eq(get_associated_artifacts_as_string(a2), "root//foo/bar/baz/file1")

                # use a predeclared artifact (a3) and add an associated artifact (a4)
                a3 = declared_artifact("wom/bat.h")
                a4 = source_artifact("foo/bar", "baz/file2")
                a5 = declared_bound_artifact_with_associated_artifacts(a3, [a4])
                assert_eq(a3.short_path, "wom/bat.h")
                assert_eq(a5.short_path, "wom/bat.h")
                assert_eq(get_associated_artifacts_as_string(a3), "")
                assert_eq(get_associated_artifacts_as_string(a5), "root//foo/bar/baz/file2")

                # use a predeclared artifact (a3) with no associated artifacts
                a6 = declared_bound_artifact_with_associated_artifacts(a3, [])
                assert_eq(a6.short_path, "wom/bat.h")
                assert_eq(get_associated_artifacts_as_string(a6), "")

                a7 = a5.without_associated_artifacts()
                assert_eq(a5.short_path, a7.short_path)
                assert_eq(get_associated_artifacts_as_string(a7), "")
            "#
    ))
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_artifact::artifact::artifact_type::testing::BuildArtifactTestingExt;
use buck2_artifact::artifact::artifact_type::Artifact;
use buck2_artifact::artifact::build_artifact::BuildArtifact;
use buck2_artifact::artifact::source_artifact::SourceArtifact;
use buck2_artifact::deferred::id::DeferredId;
use buck2_build_api::actions::registry::ActionsRegistry;
use buck2_build_api::analysis::registry::AnalysisRegistry;
use buck2_build_api::artifact_groups::ArtifactGroup;
use buck2_build_api::deferred::types::BaseKey;
use buck2_build_api::deferred::types::DeferredRegistry;
use buck2_build_api::interpreter::rule_defs::artifact::associated::AssociatedArtifacts;
use buck2_build_api::interpreter::rule_defs::artifact::output_artifact_like::OutputArtifactArg;
use buck2_build_api::interpreter::rule_defs::artifact::starlark_artifact_like::ValueAsArtifactLike;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkArtifact;
use buck2_build_api::interpreter::rule_defs::artifact::StarlarkDeclaredArtifact;
use buck2_build_api::interpreter::rule_defs::cmd_args::DefaultCommandLineContext;
use buck2_common::executor_config::PathSeparatorKind;
use buck2_core::base_deferred_key::BaseDeferredKey;
use buck2_core::buck_path::path::BuckPath;
use buck2_core::category::Category;
use buck2_core::cells::paths::CellRelativePath;
use buck2_core::configuration::data::ConfigurationData;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::buck_out_path::BuckOutPathResolver;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::paths::forward_rel_path::ForwardRelativePathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_core::package::package_relative_path::PackageRelativePathBuf;
use buck2_core::package::PackageLabel;
use buck2_core::pattern::pattern_type::TargetPatternExtra;
use buck2_core::pattern::ParsedPattern;
use buck2_core::target::label::ConfiguredTargetLabel;
use buck2_core::target::label::TargetLabel;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_execute::execute::request::OutputType;
use buck2_interpreter_for_build::interpreter::build_context::BuildContext;
use buck2_interpreter_for_build::interpreter::testing::cells;
use buck2_node::configuration::execution::ExecutionPlatformResolution;
use dupe::Dupe;
use indexmap::indexset;
use indexmap::IndexSet;
use starlark::environment::GlobalsBuilder;
use starlark::eval::Evaluator;
use starlark::starlark_module;
use starlark::values::Value;

use crate::actions::testings::SimpleUnregisteredAction;

fn get_label(eval: &Evaluator, target: &str) -> anyhow::Result<ConfiguredTargetLabel> {
    let ctx = BuildContext::from_context(eval)?;
    match ParsedPattern::<TargetPatternExtra>::parse_precise(
        target,
        ctx.build_file_cell().name(),
        ctx.cell_resolver(),
    ) {
        Ok(ParsedPattern::Target(package, target_name, TargetPatternExtra)) => {
            Ok(TargetLabel::new(package, target_name.as_ref())
                .configure(ConfigurationData::testing_new()))
        }
        _ => panic!("expected a valid target"),
    }
}

#[starlark_module]
pub(crate) fn artifactory(builder: &mut GlobalsBuilder) {
    fn source_artifact(
        package: &str,
        path: &str,
        eval: &mut Evaluator,
    ) -> anyhow::Result<StarlarkArtifact> {
        let ctx = BuildContext::from_context(eval)?;
        let package = PackageLabel::new(
            ctx.build_file_cell().name(),
            CellRelativePath::from_path(package).unwrap(),
        );
        let path = BuckPath::testing_new(
            package,
            PackageRelativePathBuf::try_from(path.to_owned()).unwrap(),
        );
        Ok(StarlarkArtifact::new(SourceArtifact::new(path).into()))
    }

    fn bound_artifact(
        target: &str,
        path: &str,
        eval: &mut Evaluator,
    ) -> anyhow::Result<StarlarkArtifact> {
        let target_label = get_label(eval, target)?;
        let id = DeferredId::testing_new(0);
        let artifact = Artifact::from(BuildArtifact::testing_new(
            target_label,
            ForwardRelativePathBuf::try_from(path.to_owned()).unwrap(),
            id,
        ));
        Ok(StarlarkArtifact::new(artifact))
    }

    fn declared_artifact(
        path: &str,
        eval: &mut Evaluator,
    ) -> anyhow::Result<StarlarkDeclaredArtifact> {
        let target_label = get_label(eval, "//foo:bar")?;
        let mut registry = ActionsRegistry::new(
            BaseDeferredKey::TargetLabel(target_label),
            ExecutionPlatformResolution::unspecified(),
        );
        let artifact = registry.declare_artifact(
            None,
            ForwardRelativePathBuf::try_from(path.to_owned()).unwrap(),
            OutputType::File,
            None,
        )?;
        Ok(StarlarkDeclaredArtifact::new(
            None,
            artifact,
            AssociatedArtifacts::new(),
        ))
    }

    fn declared_bound_artifact(
        target: &str,
        path: &str,
        eval: &mut Evaluator,
    ) -> anyhow::Result<StarlarkDeclaredArtifact> {
        let target_label = get_label(eval, target)?;
        let mut deferred = DeferredRegistry::new(BaseKey::Base(BaseDeferredKey::TargetLabel(
            target_label.dupe(),
        )));
        let mut registry = ActionsRegistry::new(
            BaseDeferredKey::TargetLabel(target_label),
            ExecutionPlatformResolution::unspecified(),
        );
        let artifact = registry.declare_artifact(
            None,
            ForwardRelativePathBuf::try_from(path.to_owned()).unwrap(),
            OutputType::File,
            None,
        )?;
        let outputs = indexset![artifact.as_output()];
        registry.register(
            &mut deferred,
            IndexSet::new(),
            outputs,
            SimpleUnregisteredAction::new(vec![], Category::try_from("fake_action").unwrap(), None),
        )?;
        Ok(StarlarkDeclaredArtifact::new(
            None,
            artifact,
            AssociatedArtifacts::new(),
        ))
    }

    fn stringify_for_cli<'v>(artifact: ValueAsArtifactLike<'v>) -> anyhow::Result<String> {
        let cell_info = cells(None).unwrap();
        let project_fs =
            ProjectRoot::new(AbsNormPathBuf::try_from(std::env::current_dir().unwrap()).unwrap())
                .unwrap();
        let fs = ArtifactFs::new(
            cell_info.1,
            BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new(
                "buck-out/v2".to_owned(),
            )),
            project_fs,
        );
        let executor_fs = ExecutorFs::new(&fs, PathSeparatorKind::Unix);
        let mut cli = Vec::<String>::new();
        let mut ctx = DefaultCommandLineContext::new(&executor_fs);
        artifact
            .0
            .as_command_line_like()
            .add_to_command_line(&mut cli, &mut ctx)
            .unwrap();
        assert_eq!(1, cli.len());
        Ok(cli.get(0).unwrap().to_owned())
    }

    // Mainly tests get_or_declare_output function that can transfer associated artifacts
    // artifact parameter can be either string or artifact
    #[allow(clippy::from_iter_instead_of_collect)]
    fn declared_bound_artifact_with_associated_artifacts<'v>(
        artifact: OutputArtifactArg<'v>,
        associated_artifacts: Vec<StarlarkArtifact>,
        eval: &mut Evaluator<'v, '_>,
    ) -> anyhow::Result<Value<'v>> {
        let target_label = get_label(eval, "//foo:bar")?;
        let mut analysis_registry = AnalysisRegistry::new_from_owner(
            BaseDeferredKey::TargetLabel(target_label.dupe()),
            ExecutionPlatformResolution::unspecified(),
        )?;
        let mut actions_registry = ActionsRegistry::new(
            BaseDeferredKey::TargetLabel(target_label.dupe()),
            ExecutionPlatformResolution::unspecified(),
        );
        let mut deferred = DeferredRegistry::new(BaseKey::Base(BaseDeferredKey::TargetLabel(
            target_label.dupe(),
        )));

        let associated_artifacts = AssociatedArtifacts::from(
            associated_artifacts
                .iter()
                .map(|a| ArtifactGroup::Artifact(a.artifact())),
        );
        let (declaration, output_artifact) =
            analysis_registry.get_or_declare_output(eval, artifact, OutputType::File)?;

        actions_registry.register(
            &mut deferred,
            IndexSet::new(),
            indexset![output_artifact],
            SimpleUnregisteredAction::new(vec![], Category::try_from("fake_action").unwrap(), None),
        )?;

        let value = declaration
            .into_declared_artifact(associated_artifacts)
            .to_value();
        Ok(value)
    }

    fn get_associated_artifacts_as_string<'v>(
        artifact: ValueAsArtifactLike<'v>,
    ) -> anyhow::Result<String> {
        let associated_artifacts = artifact.0.get_associated_artifacts();
        let s: String = associated_artifacts
            .iter()
            .flat_map(|v| v.iter())
            .map(|a| a.to_string())
            .collect();
        Ok(s)
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_build_api::interpreter::rule_defs::artifact_tagging::ArtifactTag;
use buck2_interpreter_for_build::interpreter::testing::Tester;
use dupe::Dupe;
use indoc::indoc;

use crate::interpreter::rule_defs::artifact_tagging::testing::artifact_tag_factory;

#[test]
fn test_artifact_tag_eq() {
    let t1 = ArtifactTag::new();
    let t2 = ArtifactTag::new();

    assert_eq!(t1, t1.dupe());
    assert_ne!(t1, t2);
}

#[test]
fn test_artifact_tag_starlark_eq() -> anyhow::Result<()> {
    let mut tester = Tester::new()?;
    tester.additional_globals(artifact_tag_factory);

    tester.run_starlark_bzl_test(indoc!(
        r#"
        def test():
            t1 = make_tag()
            t2 = make_tag()

            assert_eq(t1, t1)
            assert_ne(t1, t2)
        "#
    ))?;

    Ok(())
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

mod artifact_tag;
pub(crate) mod testing;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_build_api::interpreter::rule_defs::artifact_tagging::ArtifactTag;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;

#[starlark_module]
pub(crate) fn artifact_tag_factory(builder: &mut GlobalsBuilder) {
    fn make_tag() -> anyhow::Result<ArtifactTag> {
        Ok(ArtifactTag::new())
    }
}


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

pub(crate) mod testing;
mod tests;


/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under both the MIT license found in the
 * LICENSE-MIT file in the root directory of this source tree and the Apache
 * License, Version 2.0 found in the LICENSE-APACHE file in the root directory
 * of this source tree.
 */

use buck2_build_api::interpreter::rule_defs::cmd_args::value_as::ValueAsCommandLineLike;
use buck2_build_api::interpreter::rule_defs::cmd_args::DefaultCommandLineContext;
use buck2_common::executor_config::PathSeparatorKind;
use buck2_core::fs::artifact_path_resolver::ArtifactFs;
use buck2_core::fs::buck_out_path::BuckOutPathResolver;
use buck2_core::fs::paths::abs_norm_path::AbsNormPathBuf;
use buck2_core::fs::project::ProjectRoot;
use buck2_core::fs::project_rel_path::ProjectRelativePathBuf;
use buck2_execute::artifact::fs::ExecutorFs;
use buck2_interpreter_for_build::interpreter::testing::cells;
use starlark::environment::GlobalsBuilder;
use starlark::starlark_module;
use starlark::values::Value;

fn artifact_fs() -> ArtifactFs {
    let cell_info = cells(None).unwrap();
    ArtifactFs::new(
        cell_info.1,
        BuckOutPathResolver::new(ProjectRelativePathBuf::unchecked_new(
            "buck-out/v2".to_owned(),
        )),
        ProjectRoot::new(AbsNormPathBuf::try_from(std::env::current_dir().unwrap()).unwrap())
            .unwrap(),
    )
}

fn get_command_line(value: Value) -> anyhow::Result<Vec<String>> {
    let fs = artifact_fs();
    let executor_fs = ExecutorFs::new(&fs, PathSeparatorKind::Unix);
    let mut cli = Vec::<String>::new();
    let mut ctx = DefaultCommandLineContext::new(&executor_fs);

    match value.as_command_line() {
        Some(v) => v.add_to_command_line(&mut cli, &mut ctx),
        None => value
            .as_command_line_err()?
            .add_to_command_line(&mut cli, &mut ctx),
    }?;
    Ok(cli)
}

#[starlark_module]
pub(crate) fn command_line_stringifier(builder: &mut GlobalsBuilder) {
    fn get_args<'v>(value: Value<'v>) -> anyhow::Result<Vec<String>> {
        get_command_line(value)
    }

    fn stringify_cli_arg<'v>(value: Value<'v>) -> anyhow::Result<String> {
        let fs = artifact_fs();
        let executor_fs = ExecutorFs::new(&fs, PathSeparatorKind::Unix);
        let mut cli = Vec::<String>::new();
        let mut ctx = DefaultCommandLineContext::new(&executor_fs);
        value
            .as_command_line_err()?
            .add_to_command_line(&mut cli, &mut ctx)?;
        assert_eq!(1, cli.len());
        Ok(cli.get(0).unwrap().clone())
    }
}