use std::env;
use std::fmt;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use tracing::warn;
use crate::error::ShikumiError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, Ord, PartialOrd)]
#[non_exhaustive]
pub enum Format {
#[default]
Yaml,
Toml,
Lisp,
Nix,
}
impl Format {
pub const ALL: &'static [Format] = &[Self::Yaml, Self::Toml, Self::Lisp, Self::Nix];
#[must_use]
pub fn extensions(self) -> &'static [&'static str] {
match self {
Self::Yaml => &["yaml", "yml"],
Self::Toml => &["toml"],
Self::Lisp => &["lisp", "lsp", "el"],
Self::Nix => &["nix"],
}
}
#[must_use]
pub fn from_extension(ext: &str) -> Option<Self> {
match ext.to_ascii_lowercase().as_str() {
"yaml" | "yml" => Some(Self::Yaml),
"toml" => Some(Self::Toml),
"lisp" | "lsp" | "el" => Some(Self::Lisp),
"nix" => Some(Self::Nix),
_ => None,
}
}
#[must_use]
pub fn from_path(path: &Path) -> Option<Self> {
path.extension()
.and_then(|e| e.to_str())
.and_then(Self::from_extension)
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::Yaml => "yaml",
Self::Toml => "toml",
Self::Lisp => "lisp",
Self::Nix => "nix",
}
}
#[must_use]
pub const fn dict_required_message(self) -> &'static str {
match self {
Self::Yaml => "top-level yaml document must be a mapping",
Self::Toml => "top-level toml document must be a table",
Self::Lisp => "top-level lisp form must be a kwargs list",
Self::Nix => "top-level nix expression must evaluate to an attrset",
}
}
#[must_use]
pub fn provenance(self) -> FormatProvenance {
match self {
Self::Lisp | Self::Nix => FormatProvenance::ShikumiBuilt,
Self::Yaml | Self::Toml => FormatProvenance::FigmentBuiltin,
}
}
#[must_use]
pub fn format_coordinates(self) -> FormatCoordinates {
FormatCoordinates {
format: self,
provenance: self.provenance(),
}
}
#[must_use]
pub fn has_shikumi_provider(self) -> bool {
matches!(self.provenance(), FormatProvenance::ShikumiBuilt)
}
#[must_use]
pub fn metadata_name(self, path: &Path) -> String {
format!("{self}: {}", path.display())
}
#[must_use]
pub fn strip_metadata_name(name: &str) -> Option<(Self, &str)> {
FormatProvenance::ShikumiBuilt
.formats()
.iter()
.find_map(|f| {
let prefix = format!("{f}: ");
name.strip_prefix(&prefix).map(|rest| (*f, rest))
})
}
#[must_use]
pub fn parse_metadata_tag(name: &str) -> Option<FormatMetadataTag<'_>> {
Self::strip_metadata_name(name).map(|(format, rest)| FormatMetadataTag {
format,
path: Path::new(rest),
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum FormatProvenance {
FigmentBuiltin,
ShikumiBuilt,
}
impl FormatProvenance {
pub const ALL: &'static [Self] = &[Self::FigmentBuiltin, Self::ShikumiBuilt];
#[must_use]
pub fn is_shikumi_built(self) -> bool {
matches!(self, Self::ShikumiBuilt)
}
#[must_use]
pub fn is_figment_builtin(self) -> bool {
matches!(self, Self::FigmentBuiltin)
}
#[must_use]
pub fn file_attribution_rule(self) -> crate::AttributionRule {
match self {
Self::FigmentBuiltin => crate::AttributionRule::FileBySource,
Self::ShikumiBuilt => crate::AttributionRule::FileByMetadataName,
}
}
#[must_use]
pub fn file_attribution_axis(self) -> crate::AttributionAxis {
self.file_attribution_rule().metadata_axis()
}
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
Self::FigmentBuiltin => "figment-builtin",
Self::ShikumiBuilt => "shikumi-built",
}
}
#[must_use]
pub const fn formats(self) -> &'static [Format] {
match self {
Self::FigmentBuiltin => &[Format::Yaml, Format::Toml],
Self::ShikumiBuilt => &[Format::Lisp, Format::Nix],
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FormatCoordinates {
pub format: Format,
pub provenance: FormatProvenance,
}
impl FormatCoordinates {
pub const ALL: &'static [Self] = &[
Self {
format: Format::Yaml,
provenance: FormatProvenance::FigmentBuiltin,
},
Self {
format: Format::Yaml,
provenance: FormatProvenance::ShikumiBuilt,
},
Self {
format: Format::Toml,
provenance: FormatProvenance::FigmentBuiltin,
},
Self {
format: Format::Toml,
provenance: FormatProvenance::ShikumiBuilt,
},
Self {
format: Format::Lisp,
provenance: FormatProvenance::FigmentBuiltin,
},
Self {
format: Format::Lisp,
provenance: FormatProvenance::ShikumiBuilt,
},
Self {
format: Format::Nix,
provenance: FormatProvenance::FigmentBuiltin,
},
Self {
format: Format::Nix,
provenance: FormatProvenance::ShikumiBuilt,
},
];
#[must_use]
pub fn format_or_none(self) -> Option<Format> {
if self.format.provenance() == self.provenance {
Some(self.format)
} else {
None
}
}
#[must_use]
pub fn is_realizable(self) -> bool {
self.format_or_none().is_some()
}
}
impl crate::ClosedAxis for Format {
const ALL: &'static [Self] = Self::ALL;
}
impl crate::ClosedAxisLabel for Format {
fn as_str(self) -> &'static str {
Self::as_str(self)
}
}
impl crate::ClosedAxis for FormatProvenance {
const ALL: &'static [Self] = Self::ALL;
}
impl crate::ClosedAxisLabel for FormatProvenance {
fn as_str(self) -> &'static str {
Self::as_str(self)
}
}
impl crate::ClosedAxis for FormatCoordinates {
const ALL: &'static [Self] = Self::ALL;
}
impl crate::ProductCube for FormatCoordinates {
fn is_realizable(self) -> bool {
Self::is_realizable(self)
}
}
impl crate::PartialInverseCube for FormatCoordinates {
type Image = Format;
fn invert(self) -> Option<Format> {
self.format_or_none()
}
fn forward(image: Format) -> Self {
image.format_coordinates()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct FormatMetadataTag<'a> {
pub format: Format,
pub path: &'a Path,
}
impl fmt::Display for Format {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
impl TryFrom<&Path> for Format {
type Error = ShikumiError;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
Self::from_path(path).ok_or_else(|| {
ShikumiError::Parse(format!(
"cannot determine config format from path: {}",
path.display()
))
})
}
}
impl FromStr for Format {
type Err = ShikumiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_extension(s)
.ok_or_else(|| ShikumiError::Parse(format!("unknown config format: {s}")))
}
}
impl serde::Serialize for Format {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
impl<'de> serde::Deserialize<'de> for Format {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
struct FormatVisitor;
impl serde::de::Visitor<'_> for FormatVisitor {
type Value = Format;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
"a canonical Format lowercase label \
(`yaml`, `toml`, `lisp`, `nix`; aliases `yml`/`lsp`/`el` accepted)",
)
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Format, E> {
v.parse::<Format>().map_err(E::custom)
}
}
deserializer.deserialize_str(FormatVisitor)
}
}
fn dir_override_or_env(override_dir: Option<&Path>, env_var: &str) -> Option<PathBuf> {
override_dir
.map(Path::to_path_buf)
.or_else(|| env::var(env_var).ok().map(PathBuf::from))
}
pub struct ConfigDiscovery {
app_name: String,
env_override: Option<String>,
formats: Vec<Format>,
hierarchical: bool,
start_dir: Option<PathBuf>,
xdg_config_home: Option<PathBuf>,
home_dir: Option<PathBuf>,
}
impl ConfigDiscovery {
#[must_use]
pub fn new(app_name: impl Into<String>) -> Self {
Self {
app_name: app_name.into(),
env_override: None,
formats: vec![Format::Yaml, Format::Toml],
hierarchical: false,
start_dir: None,
xdg_config_home: None,
home_dir: None,
}
}
#[must_use]
pub fn env_override(mut self, var: impl Into<String>) -> Self {
self.env_override = Some(var.into());
self
}
#[must_use]
pub fn formats(mut self, formats: &[Format]) -> Self {
self.formats = formats.to_vec();
self
}
#[must_use]
pub fn standard_paths(&self) -> Vec<PathBuf> {
let mut paths = Vec::new();
let app = &self.app_name;
let xdg = self.resolve_xdg_config_home();
let home = self.resolve_home();
for ext in self.configured_extensions() {
if let Some(ref xdg) = xdg {
paths.push(xdg.join(format!("{app}/{app}.{ext}")));
}
if let Some(ref home) = home {
paths.push(home.join(format!(".config/{app}/{app}.{ext}")));
}
}
if let Some(ref home) = home {
paths.push(home.join(format!(".{app}")));
paths.push(home.join(format!(".{app}.toml")));
}
paths
}
#[must_use]
pub fn hierarchical(mut self) -> Self {
self.hierarchical = true;
self
}
#[must_use]
pub fn start_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.start_dir = Some(dir.into());
self
}
#[must_use]
pub fn xdg_config_home(mut self, dir: impl Into<PathBuf>) -> Self {
self.xdg_config_home = Some(dir.into());
self
}
#[must_use]
pub fn home_dir(mut self, dir: impl Into<PathBuf>) -> Self {
self.home_dir = Some(dir.into());
self
}
pub fn discover(&self) -> Result<PathBuf, ShikumiError> {
let mut tried: Vec<PathBuf> = Vec::new();
if let Some(env_path) = self.resolve_env_override() {
tried.push(env_path.clone());
if env_path.exists() {
return Ok(env_path);
}
if let Some(ref var) = self.env_override {
warn!(
"${var} is set to {}, but the file does not exist. Falling back to defaults.",
env_path.display()
);
}
}
let paths = self.standard_paths();
for path in &paths {
if path.exists() {
return Ok(path.clone());
}
}
tried.extend(paths);
Err(ShikumiError::NotFound { tried })
}
#[must_use]
pub fn discover_or_default(&self) -> PathBuf {
self.discover().unwrap_or_else(|_| {
self.standard_paths()
.into_iter()
.next()
.unwrap_or_else(|| PathBuf::from(format!(".{}.yaml", self.app_name)))
})
}
pub fn discover_all(&self) -> Result<Vec<PathBuf>, ShikumiError> {
let mut found: Vec<PathBuf> = Vec::new();
let mut tried: Vec<PathBuf> = Vec::new();
let app = &self.app_name;
if self.hierarchical {
self.collect_configs(
&PathBuf::from(format!("/etc/{app}")),
app,
NameStyle::Bare,
&mut found,
&mut tried,
);
if let Some(config_dir) = self.user_config_dir() {
self.collect_configs(
&config_dir.join(app),
app,
NameStyle::Bare,
&mut found,
&mut tried,
);
}
let start = self.start_dir.clone().or_else(|| env::current_dir().ok());
if let Some(cwd) = start {
let mut ancestors: Vec<PathBuf> = Vec::new();
let mut current = Some(cwd.as_path());
while let Some(dir) = current {
ancestors.push(dir.to_path_buf());
current = dir.parent();
}
ancestors.reverse();
for dir in &ancestors {
self.collect_configs(dir, app, NameStyle::Dotfile, &mut found, &mut tried);
}
}
} else {
if let Some(env_path) = self.resolve_env_override() {
tried.push(env_path.clone());
if env_path.exists() {
found.push(env_path);
}
}
for path in self.standard_paths() {
tried.push(path.clone());
if path.exists() {
found.push(path);
}
}
}
if found.is_empty() {
Err(ShikumiError::NotFound { tried })
} else {
Ok(found)
}
}
fn resolve_env_override(&self) -> Option<PathBuf> {
let var = self.env_override.as_ref()?;
let path_str = env::var(var).ok()?;
Some(PathBuf::from(path_str))
}
fn resolve_xdg_config_home(&self) -> Option<PathBuf> {
dir_override_or_env(self.xdg_config_home.as_deref(), "XDG_CONFIG_HOME")
}
fn resolve_home(&self) -> Option<PathBuf> {
dir_override_or_env(self.home_dir.as_deref(), "HOME")
}
fn user_config_dir(&self) -> Option<PathBuf> {
if let Some(xdg) = self.resolve_xdg_config_home() {
return Some(xdg);
}
self.resolve_home().map(|home| home.join(".config"))
}
fn collect_configs(
&self,
dir: &Path,
app: &str,
style: NameStyle,
found: &mut Vec<PathBuf>,
tried: &mut Vec<PathBuf>,
) {
for ext in self.configured_extensions() {
let main_path = dir.join(style.main_filename(app, ext));
tried.push(main_path.clone());
if main_path.exists() {
found.push(main_path);
}
}
self.collect_partials(dir, app, style, found);
}
fn collect_partials(&self, dir: &Path, app: &str, style: NameStyle, found: &mut Vec<PathBuf>) {
if !dir.is_dir() {
return;
}
let mut partials: Vec<PathBuf> = Vec::new();
if let Ok(entries) = std::fs::read_dir(dir) {
for entry in entries.flatten() {
let name = entry.file_name();
let name_str = name.to_string_lossy();
if self.is_partial_match(&name_str, app, style) {
partials.push(entry.path());
}
}
}
partials.sort();
found.extend(partials);
}
fn is_partial_match(&self, name: &str, app: &str, style: NameStyle) -> bool {
name.starts_with(&style.partial_prefix(app))
&& self
.configured_extensions()
.any(|ext| name.ends_with(&format!(".{ext}")))
}
fn configured_extensions(&self) -> impl Iterator<Item = &'static str> + '_ {
self.formats
.iter()
.flat_map(|f| f.extensions().iter().copied())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum NameStyle {
Bare,
Dotfile,
}
impl NameStyle {
fn main_filename(self, app: &str, ext: &str) -> String {
match self {
Self::Bare => format!("{app}.{ext}"),
Self::Dotfile => format!(".{app}.{ext}"),
}
}
fn partial_prefix(self, app: &str) -> String {
match self {
Self::Bare => format!("{app}-"),
Self::Dotfile => format!(".{app}-"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::TempDir;
#[test]
fn format_display_round_trip() {
for fmt in [Format::Yaml, Format::Toml] {
let s = fmt.to_string();
let parsed: Format = s.parse().unwrap();
assert_eq!(fmt, parsed);
}
}
#[test]
fn format_from_str_case_insensitive() {
assert_eq!("YAML".parse::<Format>().unwrap(), Format::Yaml);
assert_eq!("yml".parse::<Format>().unwrap(), Format::Yaml);
assert_eq!("TOML".parse::<Format>().unwrap(), Format::Toml);
assert!("json".parse::<Format>().is_err());
}
#[test]
fn format_default_is_yaml() {
assert_eq!(Format::default(), Format::Yaml);
}
#[test]
fn format_from_extension() {
assert_eq!(Format::from_extension("yaml"), Some(Format::Yaml));
assert_eq!(Format::from_extension("yml"), Some(Format::Yaml));
assert_eq!(Format::from_extension("toml"), Some(Format::Toml));
assert_eq!(Format::from_extension("json"), None);
assert_eq!(Format::from_extension(""), None);
}
#[test]
fn format_from_extension_is_case_insensitive() {
assert_eq!(Format::from_extension("YAML"), Some(Format::Yaml));
assert_eq!(Format::from_extension("Yml"), Some(Format::Yaml));
assert_eq!(Format::from_extension("TOML"), Some(Format::Toml));
assert_eq!(Format::from_extension("LISP"), Some(Format::Lisp));
assert_eq!(Format::from_extension("Lsp"), Some(Format::Lisp));
assert_eq!(Format::from_extension("EL"), Some(Format::Lisp));
assert_eq!(Format::from_extension("Nix"), Some(Format::Nix));
assert_eq!(Format::from_extension("JSON"), None);
}
#[test]
fn format_from_path_case_insensitive_extension() {
assert_eq!(
Format::from_path(Path::new("Config.YAML")),
Some(Format::Yaml)
);
assert_eq!(Format::from_path(Path::new("app.YML")), Some(Format::Yaml));
assert_eq!(Format::from_path(Path::new("App.TOML")), Some(Format::Toml));
assert_eq!(Format::from_path(Path::new("init.NIX")), Some(Format::Nix));
assert_eq!(
Format::try_from(Path::new("Config.YAML")).unwrap(),
Format::Yaml
);
}
#[test]
fn format_from_str_agrees_with_from_extension() {
for s in [
"yaml", "YAML", "yml", "Yml", "toml", "TOML", "lisp", "lsp", "el", "EL", "nix", "NIX",
"json", "JSON", "conf", "", "ya ml",
] {
assert_eq!(
s.parse::<Format>().ok(),
Format::from_extension(s),
"FromStr and from_extension disagree on {s:?}"
);
}
}
#[test]
fn format_try_from_path() {
assert_eq!(
Format::try_from(Path::new("config.yaml")).unwrap(),
Format::Yaml
);
assert_eq!(
Format::try_from(Path::new("config.yml")).unwrap(),
Format::Yaml
);
assert_eq!(
Format::try_from(Path::new("config.toml")).unwrap(),
Format::Toml
);
assert!(Format::try_from(Path::new("config.json")).is_err());
assert!(Format::try_from(Path::new("no_extension")).is_err());
}
#[test]
fn format_from_path_recognizes_every_extension() {
assert_eq!(Format::from_path(Path::new("app.yaml")), Some(Format::Yaml));
assert_eq!(Format::from_path(Path::new("app.yml")), Some(Format::Yaml));
assert_eq!(Format::from_path(Path::new("app.toml")), Some(Format::Toml));
assert_eq!(Format::from_path(Path::new("app.lisp")), Some(Format::Lisp));
assert_eq!(Format::from_path(Path::new("app.lsp")), Some(Format::Lisp));
assert_eq!(Format::from_path(Path::new("app.el")), Some(Format::Lisp));
assert_eq!(Format::from_path(Path::new("app.nix")), Some(Format::Nix));
}
#[test]
fn format_from_path_none_for_unknown_or_absent_extension() {
assert_eq!(Format::from_path(Path::new("app.json")), None);
assert_eq!(Format::from_path(Path::new("app.conf")), None);
assert_eq!(Format::from_path(Path::new("no_extension")), None);
assert_eq!(Format::from_path(Path::new(".app")), None);
}
#[test]
fn format_from_path_respects_full_path() {
assert_eq!(
Format::from_path(Path::new("/etc/app.toml/app.yaml")),
Some(Format::Yaml)
);
}
#[test]
fn format_try_from_path_agrees_with_from_path() {
for path in [
"a.yaml", "a.yml", "a.toml", "a.lisp", "a.lsp", "a.el", "a.nix", "a.json", "noext",
] {
let p = Path::new(path);
assert_eq!(
Format::from_path(p),
Format::try_from(p).ok(),
"path: {path}"
);
}
}
#[test]
fn standard_paths_contains_xdg_and_home() {
let d = ConfigDiscovery::new("testapp");
let paths = d.standard_paths();
let path_strs: Vec<String> = paths.iter().map(|p| p.display().to_string()).collect();
assert!(path_strs.iter().any(|p| p.contains("testapp/testapp.yaml")));
assert!(path_strs.iter().any(|p| p.contains("testapp/testapp.toml")));
}
#[test]
fn discover_finds_existing_file() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("testapp");
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join("testapp.yaml");
fs::write(&config_file, "key: value").unwrap();
let var = "SHIKUMI_TEST_DISCOVER";
unsafe { env::set_var(var, config_file.to_str().unwrap()) };
let result = ConfigDiscovery::new("testapp").env_override(var).discover();
unsafe { env::remove_var(var) };
assert!(result.is_ok());
assert_eq!(result.unwrap(), config_file);
}
#[test]
fn discover_env_override_nonexistent_falls_back() {
let var = "SHIKUMI_TEST_NOEXIST";
unsafe { env::set_var(var, "/nonexistent/path.yaml") };
let result = ConfigDiscovery::new("shikumi_test_noapp")
.env_override(var)
.discover();
unsafe { env::remove_var(var) };
assert!(result.is_err());
match result.unwrap_err() {
ShikumiError::NotFound { tried } => {
assert!(!tried.is_empty());
}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn format_yaml_first_by_default() {
let d = ConfigDiscovery::new("myapp");
let paths = d.standard_paths();
let first_str = paths[0].display().to_string();
assert!(
first_str.ends_with(".yaml") || first_str.ends_with(".yml"),
"expected yaml first, got: {first_str}"
);
}
#[test]
fn format_toml_only() {
let d = ConfigDiscovery::new("myapp").formats(&[Format::Toml]);
let paths = d.standard_paths();
for p in &paths {
let s = p.display().to_string();
if s.contains(".config/") {
assert!(s.ends_with(".toml"), "expected toml in XDG paths, got: {s}");
}
}
}
#[test]
fn discover_or_default_returns_first_standard_path() {
let d = ConfigDiscovery::new("shikumi_fallback_xyz");
let path = d.discover_or_default();
let s = path.display().to_string();
assert!(
s.contains("shikumi_fallback_xyz"),
"default path should contain app name, got: {s}"
);
}
#[test]
fn discover_or_default_returns_existing_when_found() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("fallbackapp");
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join("fallbackapp.yaml");
fs::write(&config_file, "key: value").unwrap();
let var = "SHIKUMI_TEST_FALLBACK";
unsafe { env::set_var(var, config_file.to_str().unwrap()) };
let path = ConfigDiscovery::new("fallbackapp")
.env_override(var)
.discover_or_default();
unsafe { env::remove_var(var) };
assert_eq!(path, config_file);
}
#[test]
fn discover_returns_not_found_with_tried_paths() {
let result = ConfigDiscovery::new("shikumi_nonexistent_app_xyz").discover();
assert!(result.is_err());
if let Err(ShikumiError::NotFound { tried }) = result {
assert!(!tried.is_empty());
}
}
#[test]
fn discover_via_xdg_config_home() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("myxdgapp");
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join("myxdgapp.yaml");
fs::write(&config_file, "key: value").unwrap();
let result = ConfigDiscovery::new("myxdgapp")
.xdg_config_home(dir.path())
.discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), config_file);
}
#[test]
fn discover_via_home_dot_config() {
let dir = TempDir::new().unwrap();
let dot_config = dir.path().join(".config").join("homeapp");
fs::create_dir_all(&dot_config).unwrap();
let config_file = dot_config.join("homeapp.yaml");
fs::write(&config_file, "key: value").unwrap();
let nonexistent = dir.path().join("nonexistent_xdg");
let result = ConfigDiscovery::new("homeapp")
.xdg_config_home(&nonexistent)
.home_dir(dir.path())
.discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), config_file);
}
#[test]
fn discover_legacy_dot_app() {
let dir = TempDir::new().unwrap();
let legacy_file = dir.path().join(".legacyapp");
fs::write(&legacy_file, "some config").unwrap();
let nonexistent = dir.path().join("nonexistent_xdg");
let result = ConfigDiscovery::new("legacyapp")
.xdg_config_home(&nonexistent)
.home_dir(dir.path())
.discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), legacy_file);
}
#[test]
fn discover_legacy_dot_app_toml() {
let dir = TempDir::new().unwrap();
let legacy_file = dir.path().join(".legacytoml.toml");
fs::write(&legacy_file, "key = \"value\"").unwrap();
let nonexistent = dir.path().join("nonexistent_xdg");
let result = ConfigDiscovery::new("legacytoml")
.xdg_config_home(&nonexistent)
.home_dir(dir.path())
.discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), legacy_file);
}
#[test]
fn discover_env_override_takes_precedence_over_standard() {
let env_dir = TempDir::new().unwrap();
let env_file = env_dir.path().join("override.yaml");
fs::write(&env_file, "source: env_override").unwrap();
let xdg_dir = TempDir::new().unwrap();
let xdg_app_dir = xdg_dir.path().join("precapp");
fs::create_dir_all(&xdg_app_dir).unwrap();
let xdg_file = xdg_app_dir.join("precapp.yaml");
fs::write(&xdg_file, "source: xdg").unwrap();
let var = "SHIKUMI_TEST_PRECEDENCE";
unsafe { env::set_var(var, env_file.to_str().unwrap()) };
let result = ConfigDiscovery::new("precapp")
.env_override(var)
.xdg_config_home(xdg_dir.path())
.discover();
unsafe { env::remove_var(var) };
assert!(result.is_ok());
assert_eq!(result.unwrap(), env_file);
}
#[test]
fn standard_paths_yml_extension_included() {
let d = ConfigDiscovery::new("ymltest");
let paths = d.standard_paths();
let path_strs: Vec<String> = paths.iter().map(|p| p.display().to_string()).collect();
assert!(
path_strs.iter().any(|p| p.contains("ymltest.yml")),
"expected .yml variant in standard paths"
);
}
#[test]
fn discover_prefers_yaml_over_yml() {
let dir = TempDir::new().unwrap();
let app_dir = dir.path().join("preftest");
fs::create_dir_all(&app_dir).unwrap();
let yaml_file = app_dir.join("preftest.yaml");
let yml_file = app_dir.join("preftest.yml");
fs::write(&yaml_file, "format: yaml").unwrap();
fs::write(&yml_file, "format: yml").unwrap();
let result = ConfigDiscovery::new("preftest")
.xdg_config_home(dir.path())
.discover();
assert!(result.is_ok());
assert!(
result.unwrap().display().to_string().ends_with(".yaml"),
"expected .yaml to be preferred over .yml"
);
}
#[test]
fn discover_prefers_yaml_over_toml() {
let dir = TempDir::new().unwrap();
let app_dir = dir.path().join("fmtpref");
fs::create_dir_all(&app_dir).unwrap();
let yaml_file = app_dir.join("fmtpref.yaml");
let toml_file = app_dir.join("fmtpref.toml");
fs::write(&yaml_file, "format: yaml").unwrap();
fs::write(&toml_file, "format = \"toml\"").unwrap();
let result = ConfigDiscovery::new("fmtpref")
.xdg_config_home(dir.path())
.discover();
assert!(result.is_ok());
assert!(
result.unwrap().display().to_string().ends_with(".yaml"),
"expected yaml to be preferred over toml by default"
);
}
#[test]
fn format_toml_before_yaml() {
let d = ConfigDiscovery::new("revapp").formats(&[Format::Toml, Format::Yaml]);
let paths = d.standard_paths();
let first_config_path = paths
.iter()
.find(|p| p.display().to_string().contains(".config/"))
.expect("should have .config paths");
assert!(
first_config_path.display().to_string().ends_with(".toml"),
"expected toml first when Format::Toml is listed first"
);
}
#[test]
fn standard_paths_include_legacy_entries() {
let d = ConfigDiscovery::new("legapp");
let paths = d.standard_paths();
let path_strs: Vec<String> = paths.iter().map(|p| p.display().to_string()).collect();
assert!(
path_strs.iter().any(|p| p.ends_with(".legapp")),
"expected legacy $HOME/.legapp path"
);
assert!(
path_strs.iter().any(|p| p.ends_with(".legapp.toml")),
"expected legacy $HOME/.legapp.toml path"
);
}
#[test]
fn discover_no_env_override_set() {
let result = ConfigDiscovery::new("shikumi_test_unset_env_xyz")
.env_override("SHIKUMI_UNSET_VAR_XYZ")
.discover();
assert!(result.is_err());
}
#[test]
fn formats_empty_still_has_legacy_paths() {
let d = ConfigDiscovery::new("emptyformats").formats(&[]);
let paths = d.standard_paths();
let path_strs: Vec<String> = paths.iter().map(|p| p.display().to_string()).collect();
assert!(
path_strs.iter().any(|p| p.ends_with(".emptyformats")),
"expected legacy path even with empty formats"
);
}
#[test]
fn format_extensions_yaml() {
let exts = Format::Yaml.extensions();
assert_eq!(exts, &["yaml", "yml"]);
}
#[test]
fn format_extensions_toml() {
let exts = Format::Toml.extensions();
assert_eq!(exts, &["toml"]);
}
#[test]
fn format_eq_and_clone() {
let a = Format::Yaml;
let b = a;
assert_eq!(a, b);
let c = Format::Toml;
assert_ne!(a, c);
}
#[test]
fn not_found_error_lists_all_tried() {
let result = ConfigDiscovery::new("shikumi_trial_xyz")
.formats(&[Format::Yaml, Format::Toml])
.discover();
if let Err(ShikumiError::NotFound { tried }) = result {
assert!(
tried.len() >= 4,
"expected at least 4 tried paths, got {}",
tried.len()
);
} else {
panic!("expected NotFound error");
}
}
#[test]
fn discover_not_found_reports_env_override_path_when_set_but_absent() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let nonexistent_env_path = dir_path.join("absent-env-override.yaml");
let var = "SHIKUMI_TEST_DISC_NF_ENV_OVERRIDE";
unsafe { env::set_var(var, nonexistent_env_path.to_str().unwrap()) };
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let nonexistent_home = dir_path.join("nonexistent_home");
let result = ConfigDiscovery::new("shikumi_disc_envrep_xyz_app")
.env_override(var)
.xdg_config_home(&nonexistent_xdg)
.home_dir(&nonexistent_home)
.discover();
unsafe { env::remove_var(var) };
let ShikumiError::NotFound { tried } = result.expect_err("no files exist") else {
panic!("expected NotFound");
};
assert!(
tried.contains(&nonexistent_env_path),
"discover() NotFound.tried must include the env-override path \
that was checked first (the user-supplied path); got: {tried:?}"
);
assert!(
tried.iter().any(|p| p.starts_with(&nonexistent_xdg)),
"discover() NotFound.tried must also include the resolved XDG \
candidates; got: {tried:?}"
);
}
#[test]
fn discover_not_found_omits_env_override_when_var_unset() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let nonexistent_home = dir_path.join("nonexistent_home");
let result = ConfigDiscovery::new("shikumi_disc_envunset_xyz_app")
.env_override("SHIKUMI_TEST_DISC_NF_UNSET_VAR_XYZ_GUARANTEED_ABSENT")
.xdg_config_home(&nonexistent_xdg)
.home_dir(&nonexistent_home)
.discover();
let ShikumiError::NotFound { tried } = result.expect_err("no files exist") else {
panic!("expected NotFound");
};
assert!(
tried
.iter()
.all(|p| p.starts_with(&nonexistent_xdg) || p.starts_with(&nonexistent_home)),
"discover() NotFound.tried must contain only resolved XDG/HOME \
candidates when env-override var is unset; got: {tried:?}"
);
assert!(
!tried.is_empty(),
"standard candidates should still be reported"
);
}
#[test]
fn discover_all_non_hierarchical_not_found_reports_env_override_path() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let nonexistent_env_path = dir_path.join("absent-env-override.yaml");
let var = "SHIKUMI_TEST_DISC_ALL_NF_ENV_OVERRIDE";
unsafe { env::set_var(var, nonexistent_env_path.to_str().unwrap()) };
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let nonexistent_home = dir_path.join("nonexistent_home");
let result = ConfigDiscovery::new("shikumi_disc_all_envrep_xyz_app")
.env_override(var)
.xdg_config_home(&nonexistent_xdg)
.home_dir(&nonexistent_home)
.discover_all();
unsafe { env::remove_var(var) };
let ShikumiError::NotFound { tried } = result.expect_err("no files exist") else {
panic!("expected NotFound");
};
assert!(
tried.contains(&nonexistent_env_path),
"discover_all() non-hierarchical NotFound.tried must include \
the env-override path that was checked; got: {tried:?}"
);
}
#[test]
fn resolve_env_override_returns_none_when_var_unconfigured() {
let d = ConfigDiscovery::new("shikumi_resolve_envov_xyz_none_configured");
assert!(d.resolve_env_override().is_none());
}
#[test]
fn resolve_env_override_returns_none_when_var_unset() {
let d = ConfigDiscovery::new("shikumi_resolve_envov_xyz_var_unset")
.env_override("SHIKUMI_RESOLVE_ENVOV_UNSET_VAR_GUARANTEED_ABSENT_XYZ");
assert!(d.resolve_env_override().is_none());
}
#[test]
fn resolve_env_override_returns_path_when_var_set() {
let var = "SHIKUMI_RESOLVE_ENVOV_SET_VAR_XYZ";
let synthetic = "/this/path/need/not/exist.yaml";
unsafe { env::set_var(var, synthetic) };
let d = ConfigDiscovery::new("shikumi_resolve_envov_xyz_var_set").env_override(var);
let resolved = d.resolve_env_override();
unsafe { env::remove_var(var) };
assert_eq!(resolved, Some(PathBuf::from(synthetic)));
}
#[test]
fn hierarchical_builder_returns_self() {
let d = ConfigDiscovery::new("htest").hierarchical();
assert!(d.hierarchical);
}
#[test]
fn discover_all_non_hierarchical_returns_existing_standard_paths() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("datest");
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join("datest.yaml");
fs::write(&config_file, "key: value").unwrap();
let var = "SHIKUMI_TEST_DISC_ALL";
unsafe { env::set_var(var, config_file.to_str().unwrap()) };
let result = ConfigDiscovery::new("datest")
.env_override(var)
.discover_all();
unsafe { env::remove_var(var) };
assert!(result.is_ok());
let paths = result.unwrap();
assert!(!paths.is_empty());
assert!(paths.contains(&config_file));
}
#[test]
fn discover_all_non_hierarchical_missing_returns_error() {
let result = ConfigDiscovery::new("shikumi_disc_all_noexist_xyz").discover_all();
assert!(result.is_err());
}
#[test]
fn hierarchical_finds_xdg_config() {
let dir = TempDir::new().unwrap();
let app = "hierxdg";
let config_dir = dir.path().join(app);
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join(format!("{app}.yaml"));
fs::write(&config_file, "source: xdg").unwrap();
let result = ConfigDiscovery::new(app)
.xdg_config_home(dir.path())
.hierarchical()
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(
paths.iter().any(|p| p == &config_file),
"expected XDG config in results, got: {paths:?}"
);
}
#[test]
fn hierarchical_walkup_finds_dotfile_in_cwd() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hierwalk";
let dotfile = dir_path.join(format!(".{app}.yaml"));
fs::write(&dotfile, "source: cwd").unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(
paths.iter().any(|p| p == &dotfile),
"expected CWD dotfile in results, got: {paths:?}"
);
}
#[test]
fn hierarchical_merge_order_cwd_wins_over_parent() {
let parent = TempDir::new().unwrap();
let parent_path = parent.path().canonicalize().unwrap();
let child = parent_path.join("child");
fs::create_dir_all(&child).unwrap();
let app = "hiermerge";
let parent_file = parent_path.join(format!(".{app}.yaml"));
let child_file = child.join(format!(".{app}.yaml"));
fs::write(&parent_file, "level: parent").unwrap();
fs::write(&child_file, "level: child").unwrap();
let nonexistent_xdg = parent_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&child)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&parent_file), "should contain parent config");
assert!(paths.contains(&child_file), "should contain child config");
let parent_idx = paths.iter().position(|p| p == &parent_file).unwrap();
let child_idx = paths.iter().position(|p| p == &child_file).unwrap();
assert!(
parent_idx < child_idx,
"parent ({parent_idx}) should come before child ({child_idx}) in merge order"
);
}
#[test]
fn hierarchical_partials_merge_alphabetically() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hierpart";
let partial_b = dir_path.join(format!(".{app}-02-beta.yaml"));
let partial_a = dir_path.join(format!(".{app}-01-alpha.yaml"));
fs::write(&partial_a, "alpha: true").unwrap();
fs::write(&partial_b, "beta: true").unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&partial_a), "should contain alpha partial");
assert!(paths.contains(&partial_b), "should contain beta partial");
let a_idx = paths.iter().position(|p| p == &partial_a).unwrap();
let b_idx = paths.iter().position(|p| p == &partial_b).unwrap();
assert!(
a_idx < b_idx,
"alpha ({a_idx}) should come before beta ({b_idx}) in alphabetical order"
);
}
#[test]
fn hierarchical_main_config_before_partials_in_same_dir() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hiermainpart";
let main_file = dir_path.join(format!(".{app}.yaml"));
let partial = dir_path.join(format!(".{app}-01-extra.yaml"));
fs::write(&main_file, "main: true").unwrap();
fs::write(&partial, "extra: true").unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
let main_idx = paths.iter().position(|p| p == &main_file).unwrap();
let partial_idx = paths.iter().position(|p| p == &partial).unwrap();
assert!(
main_idx < partial_idx,
"main config ({main_idx}) should come before partial ({partial_idx})"
);
}
#[test]
fn hierarchical_missing_files_silently_skipped() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hiermiss";
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_err());
match result.unwrap_err() {
ShikumiError::NotFound { tried } => {
assert!(!tried.is_empty());
}
other => panic!("expected NotFound, got {other:?}"),
}
}
#[test]
fn hierarchical_xdg_partials_in_structured_dir() {
let dir = TempDir::new().unwrap();
let app = "hierxdgpart";
let config_dir = dir.path().join(app);
fs::create_dir_all(&config_dir).unwrap();
let main_file = config_dir.join(format!("{app}.yaml"));
let partial_a = config_dir.join(format!("{app}-01-db.yaml"));
let partial_b = config_dir.join(format!("{app}-02-cache.yaml"));
fs::write(&main_file, "app: base").unwrap();
fs::write(&partial_a, "db: postgres").unwrap();
fs::write(&partial_b, "cache: redis").unwrap();
let empty_dir = TempDir::new().unwrap();
let empty_path = empty_dir.path().canonicalize().unwrap();
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml])
.xdg_config_home(dir.path())
.hierarchical()
.start_dir(&empty_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&main_file), "should contain main XDG config");
assert!(paths.contains(&partial_a), "should contain XDG partial a");
assert!(paths.contains(&partial_b), "should contain XDG partial b");
let main_idx = paths.iter().position(|p| p == &main_file).unwrap();
let a_idx = paths.iter().position(|p| p == &partial_a).unwrap();
let b_idx = paths.iter().position(|p| p == &partial_b).unwrap();
assert!(main_idx < a_idx, "main before partial a");
assert!(a_idx < b_idx, "partial a before partial b");
}
#[test]
fn discover_still_works_after_hierarchical() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("backcompat");
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join("backcompat.yaml");
fs::write(&config_file, "key: value").unwrap();
let var = "SHIKUMI_TEST_BACKCOMPAT";
unsafe { env::set_var(var, config_file.to_str().unwrap()) };
let result = ConfigDiscovery::new("backcompat")
.env_override(var)
.hierarchical()
.discover();
unsafe { env::remove_var(var) };
assert!(result.is_ok());
assert_eq!(result.unwrap(), config_file);
}
#[test]
fn is_partial_match_correct() {
let d = ConfigDiscovery::new("myapp");
assert!(d.is_partial_match(".myapp-01-db.yaml", "myapp", NameStyle::Dotfile));
assert!(d.is_partial_match(".myapp-extra.yml", "myapp", NameStyle::Dotfile));
assert!(d.is_partial_match(".myapp-config.toml", "myapp", NameStyle::Dotfile));
assert!(!d.is_partial_match(".myapp.yaml", "myapp", NameStyle::Dotfile)); assert!(!d.is_partial_match("myapp-01.yaml", "myapp", NameStyle::Dotfile)); assert!(!d.is_partial_match(".myapp-01.txt", "myapp", NameStyle::Dotfile));
assert!(d.is_partial_match("myapp-01-db.yaml", "myapp", NameStyle::Bare));
assert!(d.is_partial_match("myapp-extra.toml", "myapp", NameStyle::Bare));
assert!(!d.is_partial_match(".myapp-01.yaml", "myapp", NameStyle::Bare)); assert!(!d.is_partial_match("myapp.yaml", "myapp", NameStyle::Bare)); }
#[test]
fn name_style_bare_main_filename() {
assert_eq!(NameStyle::Bare.main_filename("myapp", "yaml"), "myapp.yaml");
assert_eq!(NameStyle::Bare.main_filename("myapp", "yml"), "myapp.yml");
assert_eq!(NameStyle::Bare.main_filename("myapp", "toml"), "myapp.toml");
assert_eq!(NameStyle::Bare.main_filename("a", "yaml"), "a.yaml");
}
#[test]
fn name_style_dotfile_main_filename() {
assert_eq!(
NameStyle::Dotfile.main_filename("myapp", "yaml"),
".myapp.yaml"
);
assert_eq!(
NameStyle::Dotfile.main_filename("myapp", "toml"),
".myapp.toml"
);
assert_eq!(NameStyle::Dotfile.main_filename("a", "yaml"), ".a.yaml");
}
#[test]
fn name_style_bare_partial_prefix() {
assert_eq!(NameStyle::Bare.partial_prefix("myapp"), "myapp-");
assert_eq!(NameStyle::Bare.partial_prefix("a"), "a-");
}
#[test]
fn name_style_dotfile_partial_prefix() {
assert_eq!(NameStyle::Dotfile.partial_prefix("myapp"), ".myapp-");
assert_eq!(NameStyle::Dotfile.partial_prefix("a"), ".a-");
}
#[test]
fn name_style_main_and_partial_share_prefix() {
for style in [NameStyle::Bare, NameStyle::Dotfile] {
let main = style.main_filename("app", "yaml");
let prefix = style.partial_prefix("app");
let head = prefix.trim_end_matches('-');
assert!(
main.starts_with(head),
"{main} should start with {head} for {style:?}"
);
assert!(
!main.starts_with(prefix.as_str()),
"{main} must not start with partial prefix {prefix} for {style:?}"
);
}
}
#[test]
fn name_style_is_copy() {
let style = NameStyle::Dotfile;
let a = style;
let b = style;
assert_eq!(a, b);
assert_eq!(a, NameStyle::Dotfile);
}
#[test]
fn name_style_match_is_exhaustive() {
for style in [NameStyle::Bare, NameStyle::Dotfile] {
for format in [Format::Yaml, Format::Toml] {
for ext in format.extensions() {
let main = style.main_filename("test", ext);
let prefix = style.partial_prefix("test");
assert!(main.ends_with(&format!(".{ext}")));
assert!(prefix.ends_with('-'));
assert!(prefix.contains("test"));
}
}
}
}
#[test]
fn collect_configs_bare_finds_main_and_partials() {
let dir = TempDir::new().unwrap();
let app = "barecollect";
let main_file = dir.path().join(format!("{app}.yaml"));
let partial = dir.path().join(format!("{app}-01-db.yaml"));
let unrelated = dir.path().join(format!("{app}.txt")); let dotted = dir.path().join(format!(".{app}.yaml")); fs::write(&main_file, "k: v").unwrap();
fs::write(&partial, "k: v").unwrap();
fs::write(&unrelated, "k: v").unwrap();
fs::write(&dotted, "k: v").unwrap();
let mut found = Vec::new();
let mut tried = Vec::new();
let d = ConfigDiscovery::new(app).formats(&[Format::Yaml]);
d.collect_configs(dir.path(), app, NameStyle::Bare, &mut found, &mut tried);
assert!(found.contains(&main_file));
assert!(found.contains(&partial));
assert!(!found.contains(&unrelated));
assert!(!found.contains(&dotted));
}
#[test]
fn collect_configs_dotfile_finds_main_and_partials() {
let dir = TempDir::new().unwrap();
let app = "dotcollect";
let main_file = dir.path().join(format!(".{app}.yaml"));
let partial = dir.path().join(format!(".{app}-99-extra.yaml"));
let bare_main = dir.path().join(format!("{app}.yaml")); fs::write(&main_file, "k: v").unwrap();
fs::write(&partial, "k: v").unwrap();
fs::write(&bare_main, "k: v").unwrap();
let mut found = Vec::new();
let mut tried = Vec::new();
let d = ConfigDiscovery::new(app).formats(&[Format::Yaml]);
d.collect_configs(dir.path(), app, NameStyle::Dotfile, &mut found, &mut tried);
assert!(found.contains(&main_file));
assert!(found.contains(&partial));
assert!(!found.contains(&bare_main));
}
#[test]
fn collect_configs_main_before_partials() {
let dir = TempDir::new().unwrap();
let app = "ordercheck";
let main_file = dir.path().join(format!("{app}.yaml"));
let partial = dir.path().join(format!("{app}-01-extra.yaml"));
fs::write(&main_file, "k: v").unwrap();
fs::write(&partial, "k: v").unwrap();
let mut found = Vec::new();
let mut tried = Vec::new();
let d = ConfigDiscovery::new(app).formats(&[Format::Yaml]);
d.collect_configs(dir.path(), app, NameStyle::Bare, &mut found, &mut tried);
let main_idx = found.iter().position(|p| p == &main_file).unwrap();
let partial_idx = found.iter().position(|p| p == &partial).unwrap();
assert!(main_idx < partial_idx, "main must come before partials");
}
#[test]
fn hierarchical_discover_all_not_found_reports_resolved_searched_candidates() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hiernf";
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
let ShikumiError::NotFound { tried } = result.expect_err("no files exist") else {
panic!("expected NotFound");
};
assert!(!tried.is_empty());
let xdg_candidate = nonexistent_xdg.join(format!("{app}/{app}.yaml"));
let etc_candidate = PathBuf::from(format!("/etc/{app}/{app}.yaml"));
let start_dotfile = dir_path.join(format!(".{app}.yaml"));
assert!(
tried.contains(&xdg_candidate),
"must report resolved XDG candidate {xdg_candidate:?}; got: {tried:?}"
);
assert!(
tried.contains(&etc_candidate),
"must report /etc candidate {etc_candidate:?}; got: {tried:?}"
);
assert!(
tried.contains(&start_dotfile),
"must report start_dir dotfile candidate {start_dotfile:?}; got: {tried:?}"
);
assert!(
!tried
.iter()
.any(|p| p.to_string_lossy().contains("~/.config")),
"report must carry resolved paths, not a `~` literal; got: {tried:?}"
);
}
#[test]
fn hierarchical_discover_all_not_found_honors_configured_formats() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hierfmt";
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Toml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
let ShikumiError::NotFound { tried } = result.expect_err("no files exist") else {
panic!("expected NotFound");
};
assert!(!tried.is_empty());
assert!(
tried
.iter()
.all(|p| p.extension().is_some_and(|e| e == "toml")),
"every reported candidate must be .toml; got: {tried:?}"
);
assert!(
tried.contains(&dir_path.join(format!(".{app}.toml"))),
"must report the resolved start_dir .toml dotfile candidate; got: {tried:?}"
);
}
#[test]
fn xdg_config_home_builder_used_in_standard_paths() {
let dir = TempDir::new().unwrap();
let d = ConfigDiscovery::new("injapp").xdg_config_home(dir.path());
let paths = d.standard_paths();
assert!(
paths.iter().any(|p| p.starts_with(dir.path())),
"expected XDG override path in standard_paths"
);
}
#[test]
fn home_dir_builder_used_in_standard_paths() {
let dir = TempDir::new().unwrap();
let d = ConfigDiscovery::new("homeinj")
.xdg_config_home(&dir.path().join("nonexistent"))
.home_dir(dir.path());
let paths = d.standard_paths();
assert!(
paths.iter().any(|p| p.starts_with(dir.path())),
"expected HOME override path in standard_paths"
);
}
#[test]
fn home_dir_produces_legacy_paths() {
let dir = TempDir::new().unwrap();
let d = ConfigDiscovery::new("leginjapp").home_dir(dir.path());
let paths = d.standard_paths();
let path_strs: Vec<String> = paths.iter().map(|p| p.display().to_string()).collect();
assert!(
path_strs.iter().any(|p| p.ends_with(".leginjapp")),
"expected legacy path from injected HOME"
);
assert!(
path_strs.iter().any(|p| p.ends_with(".leginjapp.toml")),
"expected legacy toml path from injected HOME"
);
}
#[test]
fn xdg_config_home_overrides_env_var() {
let dir1 = TempDir::new().unwrap();
let dir2 = TempDir::new().unwrap();
let app = "xdgovr";
let config_dir = dir1.path().join(app);
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join(format!("{app}.yaml"));
fs::write(&config_file, "key: value").unwrap();
let result = ConfigDiscovery::new(app)
.xdg_config_home(dir1.path())
.home_dir(dir2.path())
.discover();
assert!(result.is_ok());
assert_eq!(result.unwrap(), config_file);
}
#[test]
fn discover_all_non_hierarchical_with_injected_xdg() {
let dir = TempDir::new().unwrap();
let app = "daninj";
let config_dir = dir.path().join(app);
fs::create_dir_all(&config_dir).unwrap();
let config_file = config_dir.join(format!("{app}.yaml"));
fs::write(&config_file, "key: value").unwrap();
let result = ConfigDiscovery::new(app)
.xdg_config_home(dir.path())
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&config_file));
}
#[test]
fn start_dir_builder_sets_field() {
let dir = TempDir::new().unwrap();
let d = ConfigDiscovery::new("sdtest").start_dir(dir.path());
assert_eq!(d.start_dir, Some(dir.path().to_path_buf()));
}
#[test]
fn discover_with_both_xdg_and_home_prefers_xdg() {
let xdg_dir = TempDir::new().unwrap();
let home_dir = TempDir::new().unwrap();
let app = "bothpref";
let xdg_config = xdg_dir.path().join(app);
fs::create_dir_all(&xdg_config).unwrap();
let xdg_file = xdg_config.join(format!("{app}.yaml"));
fs::write(&xdg_file, "from: xdg").unwrap();
let home_config = home_dir.path().join(".config").join(app);
fs::create_dir_all(&home_config).unwrap();
let home_file = home_config.join(format!("{app}.yaml"));
fs::write(&home_file, "from: home").unwrap();
let result = ConfigDiscovery::new(app)
.xdg_config_home(xdg_dir.path())
.home_dir(home_dir.path())
.discover();
assert!(result.is_ok());
assert_eq!(
result.unwrap(),
xdg_file,
"XDG should take precedence over HOME"
);
}
#[test]
fn format_debug_display() {
let yaml = Format::Yaml;
let toml = Format::Toml;
assert_eq!(format!("{yaml:?}"), "Yaml");
assert_eq!(format!("{toml:?}"), "Toml");
}
#[test]
fn discover_yml_extension_found_when_yaml_absent() {
let dir = TempDir::new().unwrap();
let app = "ymlonly";
let config_dir = dir.path().join(app);
fs::create_dir_all(&config_dir).unwrap();
let yml_file = config_dir.join(format!("{app}.yml"));
fs::write(&yml_file, "key: value").unwrap();
let result = ConfigDiscovery::new(app)
.xdg_config_home(dir.path())
.discover();
assert!(result.is_ok());
assert!(result.unwrap().display().to_string().ends_with(".yml"));
}
#[test]
fn hierarchical_toml_format_finds_dotfile() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hiertoml";
let dotfile = dir_path.join(format!(".{app}.toml"));
fs::write(&dotfile, "key = \"value\"").unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Toml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(
paths.iter().any(|p| p == &dotfile),
"expected .toml dotfile in hierarchical results"
);
}
#[test]
fn hierarchical_multiple_formats_found() {
let dir = TempDir::new().unwrap();
let dir_path = dir.path().canonicalize().unwrap();
let app = "hiermulti";
let yaml_file = dir_path.join(format!(".{app}.yaml"));
let toml_file = dir_path.join(format!(".{app}.toml"));
fs::write(&yaml_file, "format: yaml").unwrap();
fs::write(&toml_file, "format = \"toml\"").unwrap();
let nonexistent_xdg = dir_path.join("nonexistent_xdg");
let result = ConfigDiscovery::new(app)
.formats(&[Format::Yaml, Format::Toml])
.xdg_config_home(&nonexistent_xdg)
.hierarchical()
.start_dir(&dir_path)
.discover_all();
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&yaml_file), "should find yaml");
assert!(paths.contains(&toml_file), "should find toml");
}
#[test]
fn discover_all_non_hierarchical_env_override_included() {
let dir = TempDir::new().unwrap();
let override_file = dir.path().join("custom.yaml");
fs::write(&override_file, "key: value").unwrap();
let var = "SHIKUMI_TEST_DA_ENV";
unsafe { env::set_var(var, override_file.to_str().unwrap()) };
let result = ConfigDiscovery::new("shikumi_nonexist_da_env")
.env_override(var)
.discover_all();
unsafe { env::remove_var(var) };
assert!(result.is_ok());
let paths = result.unwrap();
assert!(paths.contains(&override_file));
}
#[test]
fn standard_paths_with_no_home_or_xdg() {
let nonexistent = PathBuf::from("/nonexistent_for_test_12345");
let d = ConfigDiscovery::new("nohome")
.xdg_config_home(&nonexistent)
.home_dir(&nonexistent);
let paths = d.standard_paths();
assert!(
paths.iter().all(|p| p.starts_with(&nonexistent)),
"all paths should be under the injected directories"
);
}
#[test]
fn dir_override_or_env_returns_override_when_set_and_env_unset() {
let var = "SHIKUMI_DOE_TEST_OVR_NOENV";
unsafe { env::remove_var(var) };
let pinned = PathBuf::from("/pinned/by/builder");
let resolved = dir_override_or_env(Some(pinned.as_path()), var);
assert_eq!(resolved, Some(pinned));
}
#[test]
fn dir_override_or_env_override_wins_when_both_set() {
let var = "SHIKUMI_DOE_TEST_BOTH_SET";
unsafe { env::set_var(var, "/from/env/loser") };
let pinned = PathBuf::from("/from/builder/winner");
let resolved = dir_override_or_env(Some(pinned.as_path()), var);
unsafe { env::remove_var(var) };
assert_eq!(resolved, Some(pinned));
}
#[test]
fn dir_override_or_env_falls_back_to_env_when_override_absent() {
let var = "SHIKUMI_DOE_TEST_ENV_FALLBACK";
unsafe { env::set_var(var, "/from/env/fallback") };
let resolved = dir_override_or_env(None, var);
unsafe { env::remove_var(var) };
assert_eq!(resolved, Some(PathBuf::from("/from/env/fallback")));
}
#[test]
fn dir_override_or_env_returns_none_when_both_absent() {
let var = "SHIKUMI_DOE_TEST_BOTH_ABSENT";
unsafe { env::remove_var(var) };
let resolved = dir_override_or_env(None, var);
assert_eq!(resolved, None);
}
#[test]
fn dir_override_or_env_preserves_override_path_bytes_verbatim() {
let var = "SHIKUMI_DOE_TEST_VERBATIM";
unsafe { env::remove_var(var) };
for raw in [
"/abs/path/with-hyphens",
"rel/path",
"../parent/dotdot",
"./curr/dot",
"/with/trailing/slash/",
"",
] {
let pinned = PathBuf::from(raw);
let resolved = dir_override_or_env(Some(pinned.as_path()), var);
assert_eq!(resolved, Some(pinned), "raw path: {raw:?}");
}
}
#[test]
fn dir_override_or_env_agrees_with_open_coded_form_pointwise() {
fn open_coded(override_owned: Option<PathBuf>, env_var: &str) -> Option<PathBuf> {
if let Some(dir) = override_owned {
return Some(dir);
}
env::var(env_var).ok().map(PathBuf::from)
}
let var = "SHIKUMI_DOE_TEST_AGREEMENT";
let pinned = PathBuf::from("/builder/override");
unsafe { env::remove_var(var) };
assert_eq!(dir_override_or_env(None, var), open_coded(None, var));
unsafe { env::set_var(var, "/agreement/env/value") };
assert_eq!(dir_override_or_env(None, var), open_coded(None, var));
assert_eq!(
dir_override_or_env(Some(pinned.as_path()), var),
open_coded(Some(pinned.clone()), var),
);
unsafe { env::remove_var(var) };
assert_eq!(
dir_override_or_env(Some(pinned.as_path()), var),
open_coded(Some(pinned), var),
);
}
#[test]
fn resolve_xdg_config_home_honors_builder_override() {
let pinned = PathBuf::from("/dir_override_or_env_test/pin/xdg");
let d = ConfigDiscovery::new("doe_xdg_app").xdg_config_home(&pinned);
assert_eq!(d.resolve_xdg_config_home(), Some(pinned));
}
#[test]
fn resolve_home_honors_builder_override() {
let pinned = PathBuf::from("/dir_override_or_env_test/pin/home");
let d = ConfigDiscovery::new("doe_home_app").home_dir(&pinned);
assert_eq!(d.resolve_home(), Some(pinned));
}
#[test]
fn configured_extensions_default_yields_yaml_then_toml_in_preference_order() {
let d = ConfigDiscovery::new("ext_default");
let exts: Vec<&'static str> = d.configured_extensions().collect();
assert_eq!(exts, vec!["yaml", "yml", "toml"]);
}
#[test]
fn configured_extensions_honors_custom_format_order() {
let d = ConfigDiscovery::new("ext_flip").formats(&[Format::Toml, Format::Yaml]);
let exts: Vec<&'static str> = d.configured_extensions().collect();
assert_eq!(exts, vec!["toml", "yaml", "yml"]);
}
#[test]
fn configured_extensions_flattens_multi_extension_formats() {
let d = ConfigDiscovery::new("ext_yaml_only").formats(&[Format::Yaml]);
let exts: Vec<&'static str> = d.configured_extensions().collect();
assert_eq!(exts, vec!["yaml", "yml"]);
}
#[test]
fn configured_extensions_includes_lisp_when_configured() {
let d = ConfigDiscovery::new("ext_lisp").formats(&[Format::Lisp]);
let exts: Vec<&'static str> = d.configured_extensions().collect();
assert_eq!(exts, vec!["lisp", "lsp", "el"]);
}
#[test]
fn configured_extensions_empty_when_no_formats() {
let d = ConfigDiscovery::new("ext_empty").formats(&[]);
let exts: Vec<&'static str> = d.configured_extensions().collect();
assert!(exts.is_empty());
}
#[test]
fn configured_extensions_cardinality_matches_sum_of_format_extensions() {
for formats in [
vec![Format::Yaml],
vec![Format::Toml],
vec![Format::Yaml, Format::Toml],
vec![Format::Toml, Format::Yaml, Format::Lisp, Format::Nix],
] {
let expected: usize = formats.iter().map(|f| f.extensions().len()).sum();
let d = ConfigDiscovery::new("card").formats(&formats);
assert_eq!(
d.configured_extensions().count(),
expected,
"cardinality must equal sum of format.extensions().len() for {formats:?}"
);
}
}
#[test]
fn standard_paths_extensions_match_configured_extensions() {
let xdg = PathBuf::from("/xdg_for_invariant");
let home = PathBuf::from("/home_for_invariant");
let d = ConfigDiscovery::new("inv")
.formats(&[Format::Yaml, Format::Toml])
.xdg_config_home(&xdg)
.home_dir(&home);
let paths = d.standard_paths();
for ext in d.configured_extensions() {
assert!(
paths
.iter()
.any(|p| p.extension().and_then(|e| e.to_str()) == Some(ext)),
"standard_paths must include a path with extension `.{ext}`"
);
}
}
#[test]
fn is_partial_match_accepts_every_configured_extension() {
let d = ConfigDiscovery::new("inv2").formats(&[Format::Yaml, Format::Toml, Format::Nix]);
for ext in d.configured_extensions() {
let name = format!("inv2-overlay.{ext}");
assert!(
d.is_partial_match(&name, "inv2", NameStyle::Bare),
"is_partial_match must accept partial `{name}` for configured ext `.{ext}`"
);
}
}
#[test]
fn is_partial_match_rejects_extensions_outside_configured_set() {
let d = ConfigDiscovery::new("inv3").formats(&[Format::Yaml]);
for ext in ["toml", "json", "nix", "lisp"] {
let name = format!("inv3-overlay.{ext}");
assert!(
!d.is_partial_match(&name, "inv3", NameStyle::Bare),
"ext `.{ext}` is not configured; partial `{name}` must be rejected"
);
}
}
#[test]
fn collect_configs_extensions_match_configured_extensions() {
let dir = TempDir::new().unwrap();
let app = "inv4";
let d = ConfigDiscovery::new(app).formats(&[Format::Yaml, Format::Toml]);
for ext in d.configured_extensions() {
fs::write(dir.path().join(format!("{app}.{ext}")), "k: v").unwrap();
}
let mut found = Vec::new();
let mut tried = Vec::new();
d.collect_configs(dir.path(), app, NameStyle::Bare, &mut found, &mut tried);
for ext in d.configured_extensions() {
let expected = dir.path().join(format!("{app}.{ext}"));
assert!(
found.contains(&expected),
"collect_configs must surface `{}` for configured ext `.{ext}`",
expected.display()
);
}
}
#[test]
fn configured_extensions_empty_disables_all_three_consumers() {
let dir = TempDir::new().unwrap();
let app = "emptycross";
fs::write(dir.path().join(format!("{app}.yaml")), "k: v").unwrap();
fs::write(dir.path().join(format!("{app}-x.yaml")), "k: v").unwrap();
let d = ConfigDiscovery::new(app)
.formats(&[])
.xdg_config_home(PathBuf::from("/xdg_empty"))
.home_dir(PathBuf::from("/home_empty"));
assert_eq!(d.configured_extensions().count(), 0);
assert!(
!d.is_partial_match(&format!("{app}-x.yaml"), app, NameStyle::Bare),
"no configured exts ⇒ no partial matches"
);
let mut found = Vec::new();
let mut tried = Vec::new();
d.collect_configs(dir.path(), app, NameStyle::Bare, &mut found, &mut tried);
assert!(
found.is_empty(),
"no configured exts ⇒ collect_configs surfaces nothing"
);
let paths = d.standard_paths();
let main_name = format!("{app}.yaml");
assert!(
paths.iter().all(|p| {
let by_name = p.file_name().and_then(|n| n.to_str());
by_name != Some(main_name.as_str())
}),
"no configured exts ⇒ no XDG/HOME {app}.{{ext}} paths; got {paths:?}"
);
}
#[test]
fn format_all_in_declaration_order() {
assert_eq!(
Format::ALL,
&[Format::Yaml, Format::Toml, Format::Lisp, Format::Nix]
);
}
#[test]
fn format_all_covers_every_variant() {
for f in [Format::Yaml, Format::Toml, Format::Lisp, Format::Nix] {
assert!(
Format::ALL.contains(&f),
"Format::ALL must contain every variant; missing {f:?}"
);
match f {
Format::Yaml | Format::Toml | Format::Lisp | Format::Nix => {}
}
}
assert_eq!(Format::ALL.len(), 4);
}
#[test]
fn format_has_shikumi_provider_lisp_and_nix_only() {
assert!(!Format::Yaml.has_shikumi_provider());
assert!(!Format::Toml.has_shikumi_provider());
assert!(Format::Lisp.has_shikumi_provider());
assert!(Format::Nix.has_shikumi_provider());
}
#[test]
fn format_dict_required_message_pins_per_format_wording() {
assert_eq!(
Format::Yaml.dict_required_message(),
"top-level yaml document must be a mapping",
);
assert_eq!(
Format::Toml.dict_required_message(),
"top-level toml document must be a table",
);
assert_eq!(
Format::Lisp.dict_required_message(),
"top-level lisp form must be a kwargs list",
);
assert_eq!(
Format::Nix.dict_required_message(),
"top-level nix expression must evaluate to an attrset",
);
}
#[test]
fn format_dict_required_message_starts_with_top_level_and_names_format() {
for f in Format::ALL.iter().copied() {
let msg = f.dict_required_message();
assert!(
msg.starts_with("top-level "),
"{f:?}: message must start with `top-level `, got `{msg}`",
);
assert!(
msg.contains(f.as_str()),
"{f:?}: message must cite the format's canonical name `{}`, got `{msg}`",
f.as_str(),
);
}
}
#[test]
fn format_dict_required_message_is_distinct_per_variant() {
let mut seen: Vec<&'static str> = Format::ALL
.iter()
.map(|f| f.dict_required_message())
.collect();
seen.sort_unstable();
let len_before = seen.len();
seen.dedup();
assert_eq!(
seen.len(),
len_before,
"dict_required_message must be distinct for every Format variant",
);
}
#[test]
fn format_as_str_yields_canonical_lowercase_names() {
assert_eq!(Format::Yaml.as_str(), "yaml");
assert_eq!(Format::Toml.as_str(), "toml");
assert_eq!(Format::Lisp.as_str(), "lisp");
assert_eq!(Format::Nix.as_str(), "nix");
}
#[test]
fn format_display_matches_as_str() {
for f in Format::ALL.iter().copied() {
assert_eq!(
f.to_string(),
f.as_str(),
"Display must agree with as_str for {f:?}",
);
}
}
#[test]
fn format_extensions_first_entry_matches_as_str() {
for f in Format::ALL.iter().copied() {
let extensions = f.extensions();
assert!(
!extensions.is_empty(),
"extensions() must never be empty for {f:?}",
);
assert_eq!(
extensions[0],
f.as_str(),
"extensions()[0] must equal as_str() for {f:?}",
);
}
}
#[test]
fn format_from_canonical_str_round_trips_through_trait() {
use crate::ClosedAxisLabel;
for f in Format::ALL.iter().copied() {
assert_eq!(
<Format as ClosedAxisLabel>::from_canonical_str(f.as_str()),
Some(f),
"trait from_canonical_str must round-trip for {f:?}",
);
}
assert_eq!(
<Format as ClosedAxisLabel>::from_canonical_str("yml"),
None,
"from_canonical_str must reject alias `yml` (FromStr accepts it; the trait does not)",
);
assert_eq!(
<Format as ClosedAxisLabel>::from_canonical_str("lsp"),
None,
"from_canonical_str must reject alias `lsp`",
);
assert_eq!(
<Format as ClosedAxisLabel>::from_canonical_str("el"),
None,
"from_canonical_str must reject alias `el`",
);
}
#[test]
fn format_ord_matches_all_declaration_order() {
assert!(Format::Yaml < Format::Toml);
assert!(Format::Toml < Format::Lisp);
assert!(Format::Lisp < Format::Nix);
for window in Format::ALL.windows(2) {
assert!(
window[0] < window[1],
"Ord must be strictly monotone in Format::ALL position: \
{:?} < {:?} failed",
window[0],
window[1],
);
}
}
#[test]
fn format_serde_yaml_round_trips_over_every_variant() {
for &f in Format::ALL {
let yaml = serde_yaml::to_string(&f).unwrap();
let parsed: Format = serde_yaml::from_str(&yaml)
.unwrap_or_else(|e| panic!("YAML round-trip for {f:?} failed: {e}"));
assert_eq!(
parsed, f,
"serde YAML round-trip must be identity for {f:?}"
);
}
}
#[test]
fn format_serde_json_round_trips_over_every_variant() {
for &f in Format::ALL {
let json = serde_json::to_string(&f).unwrap();
assert_eq!(
json,
format!("\"{}\"", f.as_str()),
"JSON emission for {f:?} must be the quoted canonical label",
);
let parsed: Format = serde_json::from_str(&json).unwrap_or_else(|e| {
panic!("JSON round-trip for {f:?} failed: {e}\n json: {json}")
});
assert_eq!(
parsed, f,
"serde JSON round-trip must be identity for {f:?}"
);
}
}
#[test]
fn format_serde_yaml_is_case_insensitive() {
for &f in Format::ALL {
let upper = f.as_str().to_ascii_uppercase();
let yaml = format!("\"{upper}\"\n");
let parsed: Format = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("uppercase YAML scalar for {f:?} must deserialize: {e}\n yaml: {yaml:?}")
});
assert_eq!(parsed, f);
}
}
#[test]
fn format_serde_yaml_accepts_aliases() {
let cases: &[(&str, Format)] = &[
("yml", Format::Yaml),
("lsp", Format::Lisp),
("el", Format::Lisp),
];
for &(alias, expected) in cases {
let yaml = format!("\"{alias}\"\n");
let parsed: Format = serde_yaml::from_str(&yaml).unwrap_or_else(|e| {
panic!("alias `{alias}` must deserialize to {expected:?}: {e}")
});
assert_eq!(
parsed, expected,
"alias `{alias}` must deserialize to {expected:?}"
);
}
}
#[test]
fn format_serde_yaml_unknown_format_error_carries_label_verbatim() {
let sentinel = "__shikumi_unknown_format_sentinel__";
let yaml = format!("\"{sentinel}\"\n");
let result: Result<Format, _> = serde_yaml::from_str(&yaml);
match result {
Err(e) => {
let rendered = format!("{e}");
assert!(
rendered.contains(sentinel),
"serde YAML error must carry the unknown sentinel verbatim, got: {rendered}",
);
}
Ok(other) => panic!("YAML carrying unknown format must reject, got {other:?}"),
}
}
#[test]
fn format_btreemap_emits_in_preference_order() {
use std::collections::BTreeMap;
let mut tally: BTreeMap<Format, u32> = BTreeMap::new();
for &f in Format::ALL {
tally.insert(f, 0);
}
let emitted: Vec<Format> = tally.keys().copied().collect();
assert_eq!(
emitted,
Format::ALL.to_vec(),
"BTreeMap<Format, _> key order must match Format::ALL (preference order)",
);
}
#[test]
fn format_metadata_name_uses_display_token() {
for f in Format::ALL {
let path = Path::new("/etc/app/app.x");
let name = f.metadata_name(path);
let expected = format!("{f}: /etc/app/app.x");
assert_eq!(
name, expected,
"metadata_name must use the Display token for {f:?}"
);
}
}
#[test]
fn format_strip_metadata_name_round_trips_for_shikumi_providers() {
for f in Format::ALL.iter().filter(|f| f.has_shikumi_provider()) {
let path = Path::new("/srv/cfg/app.cfg");
let name = f.metadata_name(path);
let (recovered_format, rest) =
Format::strip_metadata_name(&name).expect("round-trip must succeed");
assert_eq!(
recovered_format, *f,
"strip must recover the format that emitted the name"
);
assert_eq!(
rest, "/srv/cfg/app.cfg",
"strip must surface the trailing path verbatim"
);
}
}
#[test]
fn format_strip_metadata_name_rejects_non_shikumi_provider_prefixes() {
for f in Format::ALL.iter().filter(|f| !f.has_shikumi_provider()) {
let name = f.metadata_name(Path::new("/x.cfg"));
assert!(
Format::strip_metadata_name(&name).is_none(),
"{f:?} has no shikumi-built provider; its `metadata_name` \
shape must not be recognized by the inverse resolver"
);
}
}
#[test]
fn format_strip_metadata_name_rejects_unrelated_strings() {
for name in [
"",
"/etc/app/app.yaml",
"`MYAPP_` environment variable",
"json: /etc/app.json",
"lisp /etc/app.lisp", "lisp:/etc/app.lisp", ] {
assert!(
Format::strip_metadata_name(name).is_none(),
"unrelated metadata name `{name}` must not match"
);
}
}
#[test]
fn format_strip_metadata_name_pins_correct_variant() {
let lisp_name = Format::Lisp.metadata_name(Path::new("/a.lisp"));
let (got_lisp, _) =
Format::strip_metadata_name(&lisp_name).expect("lisp prefix must match");
assert_eq!(got_lisp, Format::Lisp);
let nix_name = Format::Nix.metadata_name(Path::new("/a.nix"));
let (got_nix, _) = Format::strip_metadata_name(&nix_name).expect("nix prefix must match");
assert_eq!(got_nix, Format::Nix);
}
#[test]
fn format_strip_metadata_name_returns_borrow_into_input() {
let name = Format::Lisp.metadata_name(Path::new("/srv/app.lisp"));
let (_, rest) = Format::strip_metadata_name(&name).unwrap();
let name_start = name.as_ptr() as usize;
let name_end = name_start + name.len();
let rest_start = rest.as_ptr() as usize;
assert!(
rest_start >= name_start && rest_start < name_end,
"rest must be a sub-slice of name"
);
}
#[test]
fn parse_metadata_tag_round_trips_for_shikumi_providers() {
for f in Format::ALL.iter().filter(|f| f.has_shikumi_provider()) {
let path = Path::new("/srv/cfg/app.cfg");
let name = f.metadata_name(path);
let tag = Format::parse_metadata_tag(&name).expect("round-trip must succeed");
assert_eq!(
tag.format, *f,
"envelope must recover the format that emitted the name"
);
assert_eq!(
tag.path, path,
"envelope must surface the trailing path verbatim, as &Path"
);
}
}
#[test]
fn parse_metadata_tag_rejects_non_shikumi_provider_prefixes() {
for f in Format::ALL.iter().filter(|f| !f.has_shikumi_provider()) {
let name = f.metadata_name(Path::new("/x.cfg"));
assert!(
Format::parse_metadata_tag(&name).is_none(),
"{f:?} has no shikumi-built provider; the typed envelope \
must mirror `strip_metadata_name`'s rejection"
);
}
}
#[test]
fn parse_metadata_tag_rejects_unrelated_strings() {
for name in [
"",
"/etc/app/app.yaml",
"`MYAPP_` environment variable",
"json: /etc/app.json",
"lisp /etc/app.lisp", "lisp:/etc/app.lisp", ] {
assert!(
Format::parse_metadata_tag(name).is_none(),
"unrelated metadata name `{name}` must not match the typed envelope"
);
}
}
#[test]
fn parse_metadata_tag_pins_correct_variant() {
let lisp_name = Format::Lisp.metadata_name(Path::new("/a.lisp"));
let lisp_tag = Format::parse_metadata_tag(&lisp_name).expect("lisp prefix must match");
assert_eq!(lisp_tag.format, Format::Lisp);
assert_eq!(lisp_tag.path, Path::new("/a.lisp"));
let nix_name = Format::Nix.metadata_name(Path::new("/a.nix"));
let nix_tag = Format::parse_metadata_tag(&nix_name).expect("nix prefix must match");
assert_eq!(nix_tag.format, Format::Nix);
assert_eq!(nix_tag.path, Path::new("/a.nix"));
}
#[test]
fn parse_metadata_tag_path_borrows_into_input() {
let name = Format::Nix.metadata_name(Path::new("/srv/app.nix"));
let tag = Format::parse_metadata_tag(&name).expect("nix prefix must match");
let name_start = name.as_ptr() as usize;
let name_end = name_start + name.len();
let path_start = tag.path.as_os_str().as_encoded_bytes().as_ptr() as usize;
assert!(
path_start >= name_start && path_start < name_end,
"envelope path must borrow into input metadata-name"
);
}
#[test]
fn parse_metadata_tag_agrees_with_strip_metadata_name() {
for name in [
Format::Lisp.metadata_name(Path::new("/a.lisp")),
Format::Nix.metadata_name(Path::new("/etc/app/app.nix")),
Format::Lisp.metadata_name(Path::new("/srv/cfg/x.lisp")),
] {
let tag = Format::parse_metadata_tag(&name).expect("envelope must match");
let (legacy_fmt, legacy_rest) =
Format::strip_metadata_name(&name).expect("legacy must match");
assert_eq!(tag.format, legacy_fmt, "format must agree across APIs");
assert_eq!(
tag.path,
Path::new(legacy_rest),
"path must agree across APIs (envelope is &Path; legacy is &str)"
);
}
for name in ["", "/etc/app.yaml", "envvar `X_` typo"] {
assert!(Format::parse_metadata_tag(name).is_none());
assert!(Format::strip_metadata_name(name).is_none());
}
}
#[test]
fn format_metadata_tag_is_copy_and_hashable() {
use std::collections::HashSet;
let name_a = Format::Lisp.metadata_name(Path::new("/a.lisp"));
let name_b = Format::Nix.metadata_name(Path::new("/b.nix"));
let tag_a = Format::parse_metadata_tag(&name_a).unwrap();
let tag_b = Format::parse_metadata_tag(&name_b).unwrap();
let tag_a2 = tag_a;
let tag_a3 = tag_a;
assert_eq!(tag_a, tag_a2);
assert_eq!(tag_a2, tag_a3);
let mut set = HashSet::new();
set.insert(tag_a);
set.insert(tag_a); set.insert(tag_b);
assert_eq!(set.len(), 2);
}
#[test]
fn format_provenance_classifies_each_variant() {
assert_eq!(Format::Yaml.provenance(), FormatProvenance::FigmentBuiltin);
assert_eq!(Format::Toml.provenance(), FormatProvenance::FigmentBuiltin);
assert_eq!(Format::Lisp.provenance(), FormatProvenance::ShikumiBuilt);
assert_eq!(Format::Nix.provenance(), FormatProvenance::ShikumiBuilt);
}
#[test]
fn format_provenance_partitions_every_variant() {
for f in Format::ALL {
let p = f.provenance();
match p {
FormatProvenance::FigmentBuiltin | FormatProvenance::ShikumiBuilt => {}
}
assert_eq!(p.is_shikumi_built(), p == FormatProvenance::ShikumiBuilt);
assert_eq!(
p.is_figment_builtin(),
p == FormatProvenance::FigmentBuiltin
);
assert_ne!(
p.is_shikumi_built(),
p.is_figment_builtin(),
"provenance is binary; the two predicates must disagree pointwise"
);
}
}
#[test]
fn format_provenance_agrees_with_has_shikumi_provider() {
for f in Format::ALL {
assert_eq!(
f.has_shikumi_provider(),
f.provenance() == FormatProvenance::ShikumiBuilt,
"has_shikumi_provider and provenance must agree on {f:?}",
);
assert_eq!(
f.has_shikumi_provider(),
f.provenance().is_shikumi_built(),
"has_shikumi_provider and provenance().is_shikumi_built() \
must agree on {f:?}",
);
}
}
#[test]
fn format_provenance_file_attribution_rule_pins_each_provenance() {
assert_eq!(
FormatProvenance::FigmentBuiltin.file_attribution_rule(),
crate::AttributionRule::FileBySource,
);
assert_eq!(
FormatProvenance::ShikumiBuilt.file_attribution_rule(),
crate::AttributionRule::FileByMetadataName,
);
}
#[test]
fn format_provenance_file_attribution_rule_layer_kind_is_always_file() {
for p in FormatProvenance::ALL.iter().copied() {
assert_eq!(
p.file_attribution_rule().layer_kind(),
crate::ConfigSourceKind::File,
"{p:?}'s file-attribution rule must attribute to a File layer",
);
}
}
#[test]
fn format_provenance_file_attribution_axis_mirrors_rule_axis() {
for p in FormatProvenance::ALL.iter().copied() {
assert_eq!(
p.file_attribution_axis(),
p.file_attribution_rule().metadata_axis(),
"file_attribution_axis must mirror rule.metadata_axis on {p:?}",
);
}
assert_eq!(
FormatProvenance::FigmentBuiltin.file_attribution_axis(),
crate::AttributionAxis::MetadataSource,
);
assert_eq!(
FormatProvenance::ShikumiBuilt.file_attribution_axis(),
crate::AttributionAxis::MetadataName,
);
}
#[test]
fn format_provenance_file_attribution_rule_is_always_exact() {
for p in FormatProvenance::ALL.iter().copied() {
assert_eq!(
p.file_attribution_rule().confidence(),
crate::AttributionConfidence::Exact,
"{p:?}'s file-axis attribution must be Exact",
);
}
}
#[test]
fn format_provenance_is_copy_and_hashable() {
use std::collections::HashSet;
let p = FormatProvenance::FigmentBuiltin;
let p2 = p;
let p3 = p;
assert_eq!(p, p2);
assert_eq!(p2, p3);
let mut set = HashSet::new();
for f in Format::ALL {
set.insert(f.provenance());
}
for prov in FormatProvenance::ALL.iter().copied() {
set.insert(prov);
}
assert_eq!(
set.len(),
FormatProvenance::ALL.len(),
"the partition has exactly FormatProvenance::ALL.len() cells today",
);
}
#[test]
fn format_provenance_all_has_no_duplicates() {
use std::collections::HashSet;
let unique: HashSet<FormatProvenance> = FormatProvenance::ALL.iter().copied().collect();
assert_eq!(
unique.len(),
FormatProvenance::ALL.len(),
"FormatProvenance::ALL must contain no duplicates",
);
}
#[test]
fn format_provenance_all_covers_every_provenance_over_format_all() {
use std::collections::HashSet;
let produced: HashSet<FormatProvenance> = Format::ALL
.iter()
.copied()
.map(Format::provenance)
.collect();
let listed: HashSet<FormatProvenance> = FormatProvenance::ALL.iter().copied().collect();
assert_eq!(
produced, listed,
"FormatProvenance::ALL must equal the provenance set produced by Format::provenance over Format::ALL",
);
}
#[test]
fn format_provenance_all_cardinality_matches_format_provenance_partition() {
use std::collections::HashSet;
let distinct: HashSet<FormatProvenance> = Format::ALL
.iter()
.copied()
.map(Format::provenance)
.collect();
assert_eq!(
FormatProvenance::ALL.len(),
distinct.len(),
"FormatProvenance::ALL.len() must equal the distinct provenance count over Format::ALL",
);
}
#[test]
fn format_provenance_all_iterates_in_declaration_order() {
assert_eq!(
FormatProvenance::ALL,
&[
FormatProvenance::FigmentBuiltin,
FormatProvenance::ShikumiBuilt,
],
"ALL must list variants in declaration order",
);
}
#[test]
fn format_provenance_all_predicates_partition_pointwise() {
for p in FormatProvenance::ALL.iter().copied() {
assert_ne!(
p.is_figment_builtin(),
p.is_shikumi_built(),
"provenance {p:?} must be exactly one of figment-builtin / shikumi-built",
);
}
}
#[test]
fn format_provenance_all_file_attribution_rule_is_injective() {
use std::collections::HashSet;
let rules: HashSet<crate::AttributionRule> = FormatProvenance::ALL
.iter()
.copied()
.map(FormatProvenance::file_attribution_rule)
.collect();
assert_eq!(
rules.len(),
FormatProvenance::ALL.len(),
"file_attribution_rule must be injective over FormatProvenance::ALL",
);
}
#[test]
fn format_provenance_all_file_attribution_axis_spans_both_metadata_axes() {
use std::collections::HashSet;
let axes: HashSet<crate::AttributionAxis> = FormatProvenance::ALL
.iter()
.copied()
.map(FormatProvenance::file_attribution_axis)
.collect();
assert!(
axes.contains(&crate::AttributionAxis::MetadataSource),
"FormatProvenance::ALL must produce a MetadataSource file-axis attribution"
);
assert!(
axes.contains(&crate::AttributionAxis::MetadataName),
"FormatProvenance::ALL must produce a MetadataName file-axis attribution"
);
}
#[test]
fn format_provenance_file_attribution_rule_agrees_with_resolver_pointwise() {
use crate::ConfigSource;
use crate::ProviderChain;
#[derive(serde::Deserialize, Debug)]
struct Cfg {
#[allow(dead_code)]
count: u32,
}
let dir = tempfile::TempDir::new().unwrap();
let yaml = dir.path().join("provenance_yaml.yaml");
std::fs::write(&yaml, "count: not_a_number\n").unwrap();
let yaml_err = ProviderChain::new()
.with_file(&yaml)
.extract::<Cfg>()
.unwrap_err();
let yaml_attr = yaml_err
.failing_attribution()
.expect("yaml extract must attribute");
assert_eq!(
yaml_attr.rule,
Format::Yaml.provenance().file_attribution_rule(),
"Yaml's resolver-fired rule must equal its provenance-projected rule",
);
let toml = dir.path().join("provenance_toml.toml");
std::fs::write(&toml, "count = \"not_a_number\"\n").unwrap();
let toml_err = ProviderChain::new()
.with_file(&toml)
.extract::<Cfg>()
.unwrap_err();
let toml_attr = toml_err
.failing_attribution()
.expect("toml extract must attribute");
assert_eq!(
toml_attr.rule,
Format::Toml.provenance().file_attribution_rule(),
"Toml's resolver-fired rule must equal its provenance-projected rule",
);
for attr in [&yaml_attr, &toml_attr] {
assert!(matches!(attr.source, ConfigSource::File(_)));
assert_eq!(
attr.rule.layer_kind(),
attr.source.kind(),
"rule layer_kind must agree with source kind",
);
}
}
#[test]
fn format_provenance_formats_is_fiber_of_format_provenance() {
for f in Format::ALL.iter().copied() {
for p in FormatProvenance::ALL.iter().copied() {
assert_eq!(
p.formats().contains(&f),
f.provenance() == p,
"fiber law: f.provenance() == p iff p.formats() contains f, on ({f:?}, {p:?})",
);
}
}
}
#[test]
fn format_provenance_formats_partition_format_all_disjointly() {
use std::collections::HashSet;
let mut union: HashSet<Format> = HashSet::new();
let mut total_count = 0_usize;
for p in FormatProvenance::ALL.iter().copied() {
for f in p.formats().iter().copied() {
assert!(
union.insert(f),
"format {f:?} appears in more than one fiber (provenance {p:?})",
);
total_count += 1;
}
}
assert_eq!(
total_count,
Format::ALL.len(),
"the disjoint union of fibers must cover Format::ALL with no duplicates",
);
let expected: HashSet<Format> = Format::ALL.iter().copied().collect();
assert_eq!(
union, expected,
"the disjoint union of fibers must equal Format::ALL as a set",
);
}
#[test]
fn format_provenance_formats_cardinalities_sum_to_format_all() {
let sum: usize = FormatProvenance::ALL
.iter()
.copied()
.map(|p| p.formats().len())
.sum();
assert_eq!(
sum,
Format::ALL.len(),
"the fiber cardinalities must sum to Format::ALL.len()",
);
}
#[test]
fn format_provenance_formats_respects_format_all_declaration_order() {
for p in FormatProvenance::ALL.iter().copied() {
let fiber = p.formats();
let mut last_position: Option<usize> = None;
for f in fiber.iter().copied() {
let position = Format::ALL
.iter()
.position(|all_f| *all_f == f)
.unwrap_or_else(|| panic!("fiber format {f:?} missing from Format::ALL"));
if let Some(prev) = last_position {
assert!(
position > prev,
"fiber for {p:?} must list formats in Format::ALL declaration order; \
{f:?} at Format::ALL position {position} appears after position {prev}",
);
}
last_position = Some(position);
}
}
}
#[test]
fn format_provenance_formats_today_match_recognized_partition() {
assert_eq!(
FormatProvenance::FigmentBuiltin.formats(),
&[Format::Yaml, Format::Toml],
"FigmentBuiltin fiber must equal [Yaml, Toml] today",
);
assert_eq!(
FormatProvenance::ShikumiBuilt.formats(),
&[Format::Lisp, Format::Nix],
"ShikumiBuilt fiber must equal [Lisp, Nix] today",
);
}
#[test]
fn format_provenance_formats_agrees_with_has_shikumi_provider() {
for f in FormatProvenance::ShikumiBuilt.formats().iter().copied() {
assert!(
f.has_shikumi_provider(),
"ShikumiBuilt fiber {f:?} must have has_shikumi_provider() == true",
);
}
for f in FormatProvenance::FigmentBuiltin.formats().iter().copied() {
assert!(
!f.has_shikumi_provider(),
"FigmentBuiltin fiber {f:?} must have has_shikumi_provider() == false",
);
}
}
#[test]
fn format_provenance_formats_image_equals_realizable_format_axis() {
use std::collections::HashSet;
for p in FormatProvenance::ALL.iter().copied() {
let fiber: HashSet<Format> = p.formats().iter().copied().collect();
let from_cube: HashSet<Format> = FormatCoordinates::ALL
.iter()
.copied()
.filter(|cell| cell.provenance == p && cell.is_realizable())
.map(|cell| cell.format)
.collect();
assert_eq!(
fiber, from_cube,
"fiber for {p:?} must equal the realizable-cube format projection \
on the provenance == {p:?} plane",
);
}
}
#[test]
fn format_provenance_strip_metadata_name_routes_through_shikumi_built_fiber() {
let path = Path::new("/etc/app/app.cfg");
for f in FormatProvenance::ShikumiBuilt.formats().iter().copied() {
let name = f.metadata_name(path);
let (recovered, rest) = Format::strip_metadata_name(&name).unwrap_or_else(|| {
panic!("ShikumiBuilt fiber {f:?} must round-trip strip_metadata_name")
});
assert_eq!(
recovered, f,
"strip_metadata_name must recover the ShikumiBuilt fiber format {f:?}",
);
assert_eq!(
rest,
path.to_str().unwrap(),
"strip_metadata_name must return the trailing path verbatim",
);
}
for f in FormatProvenance::FigmentBuiltin.formats().iter().copied() {
let name = f.metadata_name(path);
assert!(
Format::strip_metadata_name(&name).is_none(),
"strip_metadata_name must not recognize FigmentBuiltin fiber {f:?} \
(the resolver routes figment-builtin file attribution through Source::File, \
not metadata.name)",
);
}
}
#[test]
fn format_provenance_as_str_yields_canonical_kebab_case_names() {
assert_eq!(FormatProvenance::FigmentBuiltin.as_str(), "figment-builtin",);
assert_eq!(FormatProvenance::ShikumiBuilt.as_str(), "shikumi-built");
}
#[test]
fn format_provenance_from_canonical_str_round_trips_through_trait() {
use crate::ClosedAxisLabel;
for p in FormatProvenance::ALL.iter().copied() {
assert_eq!(
<FormatProvenance as ClosedAxisLabel>::from_canonical_str(p.as_str()),
Some(p),
"trait from_canonical_str must round-trip for {p:?}",
);
}
assert_eq!(
<FormatProvenance as ClosedAxisLabel>::from_canonical_str("Figment-Builtin"),
Some(FormatProvenance::FigmentBuiltin),
);
assert_eq!(
<FormatProvenance as ClosedAxisLabel>::from_canonical_str("SHIKUMI-BUILT"),
Some(FormatProvenance::ShikumiBuilt),
);
assert_eq!(
<FormatProvenance as ClosedAxisLabel>::from_canonical_str("custom"),
None,
);
}
#[test]
fn format_coordinates_classifies_each_variant() {
for f in Format::ALL.iter().copied() {
assert_eq!(
f.format_coordinates(),
FormatCoordinates {
format: f,
provenance: f.provenance(),
},
"format_coordinates must equal (format, format.provenance()) on {f:?}",
);
}
}
#[test]
fn format_coordinates_round_trip() {
for f in Format::ALL.iter().copied() {
assert_eq!(
f.format_coordinates().format_or_none(),
Some(f),
"format_coordinates -> format_or_none round-trip must recover {f:?}",
);
}
}
#[test]
fn format_coordinates_format_or_none_returns_none_for_unrecognized_cells() {
for cell in FormatCoordinates::ALL.iter().copied() {
let recognized = cell.format.provenance() == cell.provenance;
assert_eq!(
cell.format_or_none().is_some(),
recognized,
"format_or_none must be Some iff cell.provenance matches \
cell.format.provenance() on {cell:?}",
);
}
}
#[test]
fn format_coordinates_all_has_no_duplicates() {
use std::collections::HashSet;
let unique: HashSet<FormatCoordinates> = FormatCoordinates::ALL.iter().copied().collect();
assert_eq!(
unique.len(),
FormatCoordinates::ALL.len(),
"FormatCoordinates::ALL must contain no duplicates; got: {:?}",
FormatCoordinates::ALL,
);
}
#[test]
fn format_coordinates_all_cardinality_matches_product_of_axes() {
assert_eq!(
FormatCoordinates::ALL.len(),
Format::ALL.len() * FormatProvenance::ALL.len(),
"FormatCoordinates::ALL cardinality must equal \
Format::ALL.len() * FormatProvenance::ALL.len()",
);
assert_eq!(
FormatCoordinates::ALL.len(),
8,
"FormatCoordinates::ALL cardinality must be 8 today; \
update both this literal and the cells if axes grow",
);
}
#[test]
fn format_coordinates_all_equals_axes_cartesian_product() {
use std::collections::HashSet;
let declared: HashSet<FormatCoordinates> = FormatCoordinates::ALL.iter().copied().collect();
let mut product: HashSet<FormatCoordinates> = HashSet::new();
for format in Format::ALL.iter().copied() {
for provenance in FormatProvenance::ALL.iter().copied() {
product.insert(FormatCoordinates { format, provenance });
}
}
assert_eq!(
declared, product,
"FormatCoordinates::ALL must equal the cartesian product \
Format::ALL × FormatProvenance::ALL exactly (no extras, no omissions)",
);
}
#[test]
fn format_coordinates_all_iterates_in_lexicographic_order() {
let mut expected: Vec<FormatCoordinates> = Vec::new();
for format in Format::ALL.iter().copied() {
for provenance in FormatProvenance::ALL.iter().copied() {
expected.push(FormatCoordinates { format, provenance });
}
}
assert_eq!(
FormatCoordinates::ALL.to_vec(),
expected,
"FormatCoordinates::ALL must list cells in lexicographic \
order (format outer, provenance inner)",
);
}
#[test]
fn format_coordinates_all_partitions_into_recognized_and_unrecognized() {
let recognized = FormatCoordinates::ALL
.iter()
.copied()
.filter(|c| c.format_or_none().is_some())
.count();
let unrecognized = FormatCoordinates::ALL
.iter()
.copied()
.filter(|c| c.format_or_none().is_none())
.count();
assert_eq!(
recognized,
Format::ALL.len(),
"recognized cell count must equal Format::ALL.len()",
);
assert_eq!(
unrecognized,
FormatCoordinates::ALL.len() - Format::ALL.len(),
"unrecognized cell count must equal the cube complement",
);
assert_eq!(
recognized + unrecognized,
FormatCoordinates::ALL.len(),
"the partition must cover the cube exactly",
);
}
#[test]
fn format_coordinates_all_recognized_image_equals_format_coordinates() {
use std::collections::HashSet;
let image: HashSet<FormatCoordinates> = Format::ALL
.iter()
.copied()
.map(Format::format_coordinates)
.collect();
let recognized: HashSet<FormatCoordinates> = FormatCoordinates::ALL
.iter()
.copied()
.filter(|c| c.format_or_none().is_some())
.collect();
assert_eq!(
image, recognized,
"the recognized image of FormatCoordinates::ALL must equal \
the image of Format::format_coordinates over Format::ALL",
);
}
#[test]
fn format_coordinates_all_round_trips_through_format_or_none_on_recognized_cells() {
for cell in FormatCoordinates::ALL.iter().copied() {
if let Some(format) = cell.format_or_none() {
assert_eq!(
format.format_coordinates(),
cell,
"format_or_none -> format_coordinates round-trip \
must recover the recognized cell {cell:?}",
);
}
}
}
#[test]
fn format_coordinates_is_copy_and_hashable() {
use std::collections::HashSet;
let c = FormatCoordinates {
format: Format::Yaml,
provenance: FormatProvenance::FigmentBuiltin,
};
let c2 = c;
let c3 = c;
assert_eq!(c, c2);
assert_eq!(c2, c3);
let set: HashSet<FormatCoordinates> = FormatCoordinates::ALL.iter().copied().collect();
assert_eq!(set.len(), FormatCoordinates::ALL.len());
}
#[test]
fn format_coordinates_is_realizable_agrees_with_format_or_none_some() {
for cell in FormatCoordinates::ALL.iter().copied() {
let expected = cell.format_or_none().is_some();
assert_eq!(
cell.is_realizable(),
expected,
"cell {cell:?}: is_realizable must equal format_or_none().is_some()",
);
}
}
#[test]
fn format_coordinates_realizable_partitions_into_4_realizable_and_4_unrealizable() {
let realizable = FormatCoordinates::ALL
.iter()
.filter(|c| c.is_realizable())
.count();
let unrealizable = FormatCoordinates::ALL
.iter()
.filter(|c| !c.is_realizable())
.count();
assert_eq!(
realizable,
Format::ALL.len(),
"realizable cells must equal Format::ALL cardinality",
);
assert_eq!(
unrealizable,
FormatCoordinates::ALL.len() - Format::ALL.len(),
"unrealizable cells must equal cube cardinality minus format cardinality",
);
assert_eq!(
realizable + unrealizable,
FormatCoordinates::ALL.len(),
"realizable + unrealizable must cover ALL exactly once",
);
assert_eq!(realizable, 4);
assert_eq!(unrealizable, 4);
}
#[test]
fn format_coordinates_is_realizable_image_equals_format_image() {
use std::collections::HashSet;
let observed: HashSet<FormatCoordinates> = Format::ALL
.iter()
.copied()
.map(Format::format_coordinates)
.collect();
let realizable: HashSet<FormatCoordinates> = FormatCoordinates::ALL
.iter()
.copied()
.filter(|c| c.is_realizable())
.collect();
assert_eq!(
observed, realizable,
"observed image over Format::ALL must equal the realizable cells",
);
}
#[test]
fn format_format_coordinates_always_lies_on_realizable_cell() {
for format in Format::ALL.iter().copied() {
assert!(
format.format_coordinates().is_realizable(),
"format {format:?}: format_coordinates() must produce a realizable cell",
);
}
}
#[test]
fn format_coordinates_unrealizable_cells_have_no_inverse() {
for cell in FormatCoordinates::ALL.iter().copied() {
if !cell.is_realizable() {
assert!(
cell.format_or_none().is_none(),
"unrealizable cell {cell:?}: format_or_none must be None",
);
}
}
}
}