garden-tools 2.6.0

Garden grows and cultivates collections of Git trees Garden lets you define and run commands over collections of configuration-defined multi-worktree Git environments.
Documentation
pub mod common;

use anyhow::{Context, Result};

use garden::string;

#[test]
fn read_includes() -> Result<()> {
    let app_context =
        garden::model::ApplicationContext::from_path_string("tests/data/garden.yaml")?;
    let config = app_context.get_root_config();

    // var_0 is from the included variables.yaml..
    let actual = garden::eval::value(&app_context, config, "${var_0}");
    assert_eq!(actual, "zero");
    // var_1 is provided by variables-transitive.yaml and overridden by includes.yaml.
    let actual = garden::eval::value(&app_context, config, "${var_1}");
    assert_eq!(actual, "ONE");
    // var_2 is provided by variables-transitive.yaml.
    let actual = garden::eval::value(&app_context, config, "${var_2}");
    assert_eq!(actual, "two");

    assert!(config.trees.contains_key("tree-zero")); // includes/trees.yaml
    assert!(config.trees.contains_key("tree-echo-nested")); // includes/trees.yaml
    assert!(config.trees.contains_key("tree-echo-extended-tree-inner")); // includes/trees.yaml
    assert!(config.trees.contains_key("tree-echo-extended-tree")); // garden.yaml overrides includes/trees.yaml
    assert!(config.trees.contains_key("example/tree")); // garden.yaml
    assert!(config.trees.contains_key("example/link")); // garden.yaml
    assert!(config.trees.contains_key("link")); // garden.yaml
    assert!(config.trees.contains_key("current")); // garden.yaml
    assert!(config.trees.contains_key("example/shallow")); // garden.yaml
    assert!(config.trees.contains_key("example/single-branch")); // garden.yaml
    assert!(config.trees.contains_key("tree1")); // garden.yaml
    assert!(config.trees.contains_key("tree2")); // garden.yaml
    assert!(config.trees.contains_key("tree-echo-extended")); // garden.yaml

    // Nested include files are relative to the file that included them.
    // If the nested include file is not found relative to the parent include file
    // then a file relative to the config directory can be used.
    let actual = garden::eval::value(&app_context, config, "${var_included}");
    assert_eq!(actual, "relative to config");

    Ok(())
}

/// Ensure that templates can be included.
#[test]
fn template_includes() -> Result<()> {
    let app_context =
        garden::model::ApplicationContext::from_path_string("tests/data/garden.yaml")?;
    let config_id = app_context.get_root_id();
    let context = garden::query::find_tree(&app_context, config_id, "tree-echo", None)?;
    let config = app_context.get_root_config();

    let tree = config.trees.get(&context.tree).context("tree-echo")?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "template");
    let constant = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-constant}",
        &context.tree,
        None,
    );
    assert_eq!(constant, "constant");
    assert_eq!(1, tree.commands.len());
    let echo_cmd_opt = tree.commands.get("echo");
    assert!(echo_cmd_opt.is_some());
    let echo_cmd = echo_cmd_opt.context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo Hello, ${TREE_NAME}");

    // Test a template that uses "extend" on a template defined via an include file.
    // The "tree-echo-extended" uses "extend: tree-echo".
    let context = garden::query::find_tree(&app_context, config_id, "tree-echo-extended", None)?;
    let tree = config
        .trees
        .get(&context.tree)
        .context("tree-echo-extended")?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-variable}",
        &context.tree,
        None,
    );
    let constant = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-constant}",
        &context.tree,
        None,
    );
    assert_eq!(result, "extended");
    assert_eq!(constant, "constant");
    assert_eq!(tree.commands.len(), 1);

    let echo_cmd_opt = tree.commands.get("echo");
    assert!(echo_cmd_opt.is_some());

    let echo_cmd = echo_cmd_opt.context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo extended");

    // Test a tree that uses "templates" with a template from a nested include file.
    let context = garden::query::find_tree(&app_context, config_id, "tree-echo-nested", None)?;
    let tree = &config
        .trees
        .get(&context.tree)
        .context("tree-echo-nested")?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-variable}",
        &context.tree,
        None,
    );
    let constant = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-constant}",
        &context.tree,
        None,
    );
    assert_eq!(constant, "constant");
    assert_eq!(result, "nested");

    let echo_cmd_opt = tree.commands.get("echo");
    assert!(echo_cmd_opt.is_some());

    let echo_cmd = echo_cmd_opt.context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo Hello, ${TREE_NAME}");

    // Test a tree that uses "extend" on a tree defined via an include file.
    let context = garden::query::find_tree(
        &app_context,
        config_id,
        "tree-echo-extended-tree-inner",
        None,
    )?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "extended-tree");

    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${tree-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "nested");

    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${tree-override}",
        &context.tree,
        None,
    );
    assert_eq!(result, "extended-tree");

    // Test a tree that uses "extend" on a tree defined via an include file.
    // This tree is overridden by the top-level garden.yaml.
    let context =
        garden::query::find_tree(&app_context, config_id, "tree-echo-extended-tree", None)?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${template-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "top-level");

    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${tree-override}",
        &context.tree,
        None,
    );
    assert_eq!(result, "top-level");

    // "tree-variable" is provided by "tree-echo-nested" via "extend" and is not overriden.
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${tree-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "nested");

    // "extended-variable" is provided by the inner-most "tree-echo-extended-tree".
    // "tree-echo-extended" is sparsely overridden by the top-level garden.yaml.
    // "extended-variable" is not overridden so the inner-most value is retained.
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${extended-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "extended-tree");

    // "replacement-tree" is not sparsely overriden -- it is replaced. The variables should
    // evaluate to an empty string because the replacement tree does not define the variable.
    let context = garden::query::find_tree(&app_context, config_id, "replacement-tree", None)?;
    let result = garden::eval::tree_value(
        &app_context,
        config,
        None,
        "${tree-variable}",
        &context.tree,
        None,
    );
    assert_eq!(result, "");

    let replacement_tree = config
        .trees
        .get(&context.tree)
        .context("replacement-tree")?;
    // The replacement tree provides no commands.
    assert!(!replacement_tree.commands.contains_key("tree-command"));
    // The replacement tree replaces the URL for the origin remote.
    assert_eq!(
        "https://example.com/replacement/tree",
        replacement_tree
            .remotes
            .get("origin")
            .context("origin")?
            .get_expr()
    );

    Ok(())
}

/// Ensure that commands are overridden when defined in multiple files.
#[test]
fn command_overrides() -> Result<()> {
    let string = string!(
        r#"
    garden:
      includes: tests/data/includes/commands.yaml
    "#
    );

    // Base case: the "echo" command is read.
    let app_context = common::garden_context_from_string(&string)?;
    let config = app_context.get_root_config();
    assert_eq!(config.commands.len(), 2);
    assert!(config.commands.contains_key("echo"));
    assert!(config.commands.contains_key("test"));

    let echo_cmd = config.commands.get("echo").context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo commands.yaml");

    // If the same command is seen twice it is only defined once.
    let string = string!(
        r#"
    garden:
      includes:
        - tests/data/includes/commands.yaml
        - tests/data/includes/commands.yaml
    "#
    );
    let app_context = common::garden_context_from_string(&string)?;
    let config = app_context.get_root_config();
    assert_eq!(config.commands.len(), 2);

    // If the same command is seen twice the last one wins.
    let string = string!(
        r#"
    garden:
      includes:
        - tests/data/includes/commands.yaml
        - tests/data/includes/commands-override.yaml
    "#
    );
    let app_context = common::garden_context_from_string(&string)?;
    let config = app_context.get_root_config();
    assert_eq!(config.commands.len(), 2);
    assert!(config.commands.contains_key("echo"));
    assert!(config.commands.contains_key("test"));

    let echo_cmd = config.commands.get("echo").context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo commands-override.yaml");

    let test_cmd = config.commands.get("test").context("test")?;
    assert_eq!(1, test_cmd.len());
    assert_eq!(test_cmd[0].get_expr(), "echo override test");

    // If the same command is seen in the garden.yaml then it overrides includes.
    let string = string!(
        r#"
    garden:
      includes:
        - tests/data/includes/commands.yaml
        - tests/data/includes/commands-override.yaml
    commands:
      echo: echo top-level override
    "#
    );
    let app_context = common::garden_context_from_string(&string)?;
    let config = app_context.get_root_config();
    assert_eq!(config.commands.len(), 2);
    assert!(config.commands.contains_key("echo"));
    assert!(config.commands.contains_key("test"));

    let echo_cmd = config.commands.get("echo").context("echo")?;
    assert_eq!(1, echo_cmd.len());
    assert_eq!(echo_cmd[0].get_expr(), "echo top-level override");

    Ok(())
}