Implement tests for `pijul-config`

finchie
Aug 1, 2025, 10:31 AM
IWN4G7PCFKVK7IN5HMXS5NFCSIT5HFDLSUJ4ULP4QOZTBYFCFWDAC

Dependencies

  • [2] FIMDS32Z Use subcommand-specific repository config paths
  • [3] Z4PPQZUG Refactor `pijul-config` to support layered configuration
  • [4] H4AU6QRP New config for HTTP remotes
  • [5] HGJETVAN Create `pijul_config::global_config_directory()`
  • [6] HM6QW3CY Handle missing configurations in `pijul_config`
  • [7] SXEYMYF7 Fixing the bad changes in history (unfortunately, by rebooting).
  • [8] VQPAUKBQ channel switch as an alias to reset
  • [9] VL7ZYKHB Running hooks through shell on Windows and Unix
  • [10] FVQYZQFL Create dialoguer themes based on global config
  • [11] 2ZKE4XMJ Replace `pijul_repository::init_dot_ignore` with `pijul_config::Config::dot_ignore_contents`
  • [12] Y6EVFMTA Don't output files if they aren't in the current channel
  • [*] 7UU3TV5W Refactor `pijul::config` into new crate
  • [*] OWO4EWK7 Add global `--config` argument to override configuration values

Change contents

  • file addition: tests (d--r------)
    [14.24]
  • file addition: parse_config_arg.rs (----------)
    [0.17]
    use pijul_config::parse_config_arg;
    #[test]
    fn top_level() -> Result<(), anyhow::Error> {
    let (key, value) = parse_config_arg("unrecord_changes=1")?;
    assert_eq!(key, "unrecord_changes");
    assert_eq!(value, "1");
    Ok(())
    }
    #[test]
    fn nested() -> Result<(), anyhow::Error> {
    let (key, value) = parse_config_arg("author.username=Ferris")?;
    assert_eq!(key, "author.username");
    assert_eq!(value, "Ferris");
    Ok(())
    }
    #[test]
    fn missing_equals_sign() {
    parse_config_arg("unrecord_changes1").unwrap_err();
    }
    #[test]
    fn empty_argument() {
    parse_config_arg("").unwrap_err();
    }
  • file addition: layering.rs (----------)
    [0.17]
    use pijul_config::{Choice, Config};
    use std::path::PathBuf;
    const GLOBAL_CONFIG: &str = r#"
    colors = "never"
    pager = "never"
    reset_overwrites_changes = "never"
    "#;
    const LOCAL_CONFIG: &str = r#"
    colors = "always"
    pager = "always"
    reset_overwrites_changes = "always"
    "#;
    const CONFIG_OVERRIDES: [(&str, &str); 3] = [
    ("colors", "auto"),
    ("pager", "auto"),
    ("reset_overwrites_changes", "auto"),
    ];
    fn check_config_fields(config: &Config, choice: Choice) {
    assert_eq!(config.colors, choice);
    assert_eq!(config.pager, choice);
    assert_eq!(config.reset_overwrites_changes, choice);
    }
    /// Default config values should be Choice::Auto
    #[test]
    fn default() -> Result<(), anyhow::Error> {
    let default_config = Config::load(None, Vec::new())?;
    // Double-check that the defaults still use Choice::Auto
    check_config_fields(&default_config, Choice::Auto);
    Ok(())
    }
    /// Global config values should override defaults
    #[test]
    fn global() -> Result<(), anyhow::Error> {
    let global_config = Config::load_with(
    Some((PathBuf::new(), String::from(GLOBAL_CONFIG))),
    None,
    Vec::new(),
    )?;
    // Make sure the global config overrides these fields
    check_config_fields(&global_config, Choice::Never);
    Ok(())
    }
    /// Local config values should override defaults and global config
    #[test]
    fn local() -> Result<(), anyhow::Error> {
    let local_config = Config::load_with(
    Some((PathBuf::new(), String::from(GLOBAL_CONFIG))),
    Some((PathBuf::new(), String::from(LOCAL_CONFIG))),
    Vec::new(),
    )?;
    // Make sure the local config overrides these fields
    check_config_fields(&local_config, Choice::Always);
    Ok(())
    }
    /// Config overrides should override everything
    #[test]
    fn overrides() -> Result<(), anyhow::Error> {
    let override_config = Config::load_with(
    Some((PathBuf::new(), String::from(GLOBAL_CONFIG))),
    Some((PathBuf::new(), String::from(LOCAL_CONFIG))),
    CONFIG_OVERRIDES
    .iter()
    .map(|(key, value)| (key.to_string(), value.to_string()))
    .collect(),
    )?;
    // Make sure the overrides apply to these fields
    check_config_fields(&override_config, Choice::Auto);
    Ok(())
    }
    /// Different layers merge correctly
    #[test]
    fn merging() -> Result<(), anyhow::Error> {
    let layered_config = Config::load_with(
    Some((PathBuf::new(), String::from(r#"colors = "never""#))),
    Some((PathBuf::new(), String::from(r#"pager = "always""#))),
    vec![(
    String::from("reset_overwrites_changes"),
    String::from("never"),
    )],
    )?;
    // Make sure the layers merge correctly
    assert_eq!(layered_config.colors, Choice::Never);
    assert_eq!(layered_config.pager, Choice::Always);
    assert_eq!(layered_config.reset_overwrites_changes, Choice::Never);
    Ok(())
    }
  • file addition: config.rs (----------)
    [0.17]
    use pijul_config::Config;
    use std::path::PathBuf;
    #[test]
    fn load_simple() -> Result<(), anyhow::Error> {
    Config::load(None, Vec::new())?;
    Ok(())
    }
    #[test]
    fn load_defaults() -> Result<(), anyhow::Error> {
    Config::load_with(None, None, Vec::new())?;
    Ok(())
    }
    #[test]
    fn empty_global_config() -> Result<(), anyhow::Error> {
    let empty_config = Some((PathBuf::new(), String::new()));
    Config::load_with(empty_config, None, Vec::new())?;
    Ok(())
    }
    #[test]
    fn empty_local_config() -> Result<(), anyhow::Error> {
    let empty_config = Some((PathBuf::new(), String::new()));
    Config::load_with(None, empty_config, Vec::new())?;
    Ok(())
    }
  • edit in pijul-config/src/lib.rs at line 50
    [3.5257]
    [3.5257]
    #[serde(default)]
  • edit in pijul-config/src/lib.rs at line 74
    [15.137]
    [3.6162]
    let global_config = match Global::config_file() {
    Some(global_config_path) => match Global::read_contents(&global_config_path) {
    Ok(contents) => Some((global_config_path, contents)),
    Err(error) => {
    warn!("Unable to read global config file: {error:#?}");
    None
    }
    },
    None => {
    warn!("Unable to find global configuration path");
    None
    }
    };
    let local_config = match repository_path {
    Some(repository_path) => match Local::read_contents(&repository_path) {
    Ok(contents) => Some((repository_path.to_path_buf(), contents)),
    Err(error) => {
    warn!("Unable to read global config file: {error:#?}");
    None
    }
    },
    None => {
    info!(
    "Skipping local configuration path - repository path was not supplied by caller"
    );
    None
    }
    };
    Self::load_with(global_config, local_config, config_overrides)
    }
    pub fn load_with(
    global_config_file: Option<(PathBuf, String)>,
    local_config_file: Option<(PathBuf, String)>,
    config_overrides: Vec<(String, String)>,
    ) -> Result<Self, anyhow::Error> {
  • replacement in pijul-config/src/lib.rs at line 131
    [3.667][3.332:811](),[3.86][3.332:811]()
    let global_config = match Global::config_file() {
    Some(global_config_path) => match Global::read_contents(&global_config_path) {
    Ok(contents) => {
    // Parse the config (and make sure it's valid!)
    let global_config = Global::parse_contents(&global_config_path, &contents)?;
    // Add the configuration layer as a string
    layers = layers.merge(Toml::string(&contents));
    [3.667]
    [3.475]
    let global_config = match global_config_file {
    Some((path, contents)) => {
    // Parse the config (and make sure it's valid!)
    let global_config = Global::parse_contents(&path, &contents)?;
    // Add the configuration layer as a string
    layers = layers.merge(Toml::string(&contents));
  • replacement in pijul-config/src/lib.rs at line 138
    [3.476][3.812:1125](),[3.1125][3.626:647](),[3.626][3.626:647]()
    Some(global_config)
    }
    Err(error) => {
    warn!("Unable to read global config file: {error:#?}");
    None
    }
    },
    None => {
    warn!("Unable to find global configuration path");
    None
    [3.476]
    [3.647]
    Some(global_config)
  • edit in pijul-config/src/lib.rs at line 140
    [3.661]
    [3.661]
    None => None,
  • replacement in pijul-config/src/lib.rs at line 144
    [3.695][2.65:525]()
    let local_config = match repository_path {
    Some(repository_path) => match Local::read_contents(&repository_path) {
    Ok(contents) => {
    // Parse the config (and make sure it's valid!)
    let local_config = Local::parse_contents(&repository_path, &contents)?;
    // Add the configuration layer as a string
    layers = layers.merge(Toml::string(&contents));
    [3.695]
    [3.1083]
    let local_config = match local_config_file {
    Some((path, contents)) => {
    // Parse the config (and make sure it's valid!)
    let global_config = Local::parse_contents(&path, &contents)?;
    // Add the configuration layer as a string
    layers = layers.merge(Toml::string(&contents));
  • replacement in pijul-config/src/lib.rs at line 151
    [3.1084][2.526:914](),[2.914][3.1232:1253](),[3.1232][3.1232:1253]()
    Some(local_config)
    }
    Err(error) => {
    warn!("Unable to read global config file: {error:#?}");
    None
    }
    },
    None => {
    info!(
    "Skipping local configuration path - repository path was not supplied by caller"
    );
    None
    [3.1084]
    [3.1253]
    Some(global_config)
  • edit in pijul-config/src/lib.rs at line 153
    [3.1267]
    [3.1267]
    None => None,
  • replacement in pijul-config/src/lib.rs at line 225
    [3.35][3.8205:8268]()
    #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
    [3.35]
    [3.8268]
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]