use std::path::PathBuf;
use std::sync::Mutex;
use tempfile::tempdir;
static CWD_LOCK: Mutex<()> = Mutex::new(());
fn testdata(name: &str) -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("tests/testdata")
.join(name)
}
#[test]
fn parse_file_simple() {
let config = hocon::parse_file(testdata("base.conf")).unwrap();
assert_eq!(config.get_string("host").unwrap(), "localhost");
assert_eq!(config.get_i64("port").unwrap(), 8080);
}
#[test]
fn include_merges_into_current() {
let config = hocon::parse_file(testdata("with_include.conf")).unwrap();
assert_eq!(config.get_string("host").unwrap(), "localhost");
assert_eq!(config.get_i64("port").unwrap(), 8080);
assert!(config.get_bool("debug").unwrap());
}
#[test]
fn include_override_by_later_key() {
let config = hocon::parse_file(testdata("override_include.conf")).unwrap();
assert_eq!(config.get_string("host").unwrap(), "localhost");
assert_eq!(config.get_i64("port").unwrap(), 9090);
}
#[test]
fn include_nested_directory() {
let config = hocon::parse_file(testdata("with_nested_include.conf")).unwrap();
assert_eq!(config.get_string("db_host").unwrap(), "db.local");
assert_eq!(config.get_string("app").unwrap(), "myapp");
}
#[test]
fn include_circular_detection() {
let result = hocon::parse_file(testdata("circular_a.conf"));
assert!(result.is_err());
}
#[test]
fn include_extension_probing() {
let config = hocon::parse_file(testdata("ext_probe.conf")).unwrap();
assert_eq!(config.get_string("found").unwrap(), "yes");
assert!(config.get_bool("extra").unwrap());
}
#[test]
fn include_probe_order_conf_wins() {
let config = hocon::parse_file(testdata("probe-order-wrapper.conf")).unwrap();
assert!(config.get_bool("from_json").unwrap());
assert!(config.get_bool("from_conf").unwrap());
assert_eq!(config.get_string("shared").unwrap(), "conf");
}
#[test]
fn include_missing_silently_ignored() {
let config = hocon::parse(
r#"include "nonexistent.conf"
fallback = true"#,
)
.unwrap();
assert!(config.get_bool("fallback").unwrap());
}
#[test]
fn include_relativize_quoted_key_with_dots() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("child.conf"), "x = 1\ny = ${x}").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(r#""a.b" {{ include "{}/child.conf" }}"#, dir_str);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_i64(r#""a.b".x"#).unwrap(), 1);
assert_eq!(config.get_i64(r#""a.b".y"#).unwrap(), 1);
}
#[test]
fn include_env_fallback_quoted_key_prefix() {
struct EnvGuard {
key: &'static str,
}
impl Drop for EnvGuard {
fn drop(&mut self) {
std::env::remove_var(self.key);
}
}
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("child.conf"), "val = ${MY_TEST_VAR_QK}").unwrap();
std::env::set_var("MY_TEST_VAR_QK", "ok");
let _guard = EnvGuard {
key: "MY_TEST_VAR_QK",
};
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(r#""a.b" {{ include "{}/child.conf" }}"#, dir_str);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_string(r#""a.b".val"#).unwrap(), "ok");
}
#[test]
fn file_include_resolves_from_cwd_not_including_dir() {
let _lock = CWD_LOCK.lock().unwrap();
let prev_cwd = std::env::current_dir().unwrap();
let dir = tempdir().unwrap();
let subdir = dir.path().join("sub");
std::fs::create_dir(&subdir).unwrap();
std::fs::write(dir.path().join("child.conf"), "cwd_key = 99").unwrap();
std::fs::write(subdir.join("child.conf"), "child_key = 1").unwrap();
let abs_child = subdir
.join("child.conf")
.display()
.to_string()
.replace('\\', "/");
let parent_content = format!(
concat!(
"bare_ok = true\n",
"include \"child.conf\"\n",
"include file(\"child.conf\")\n",
"include file(\"{}\")\n",
),
abs_child
);
std::fs::write(subdir.join("parent.conf"), &parent_content).unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let config = hocon::parse_file(subdir.join("parent.conf")).unwrap();
std::env::set_current_dir(&prev_cwd).unwrap();
assert_eq!(config.get_i64("child_key").unwrap(), 1);
assert_eq!(config.get_i64("cwd_key").unwrap(), 99);
assert!(config.get_bool("bare_ok").unwrap());
}
#[test]
fn s14c_2_ancestor_scope_var_fallback_after_relativization() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("ref.conf"), "ref = ${y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(
r#"y = "root-y"
bar {{ include "{}/ref.conf" }}
"#,
dir_str
);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_string("bar.ref").unwrap(), "root-y");
}
#[test]
fn s14c_2_relativized_path_still_wins_when_both_exist() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("ref.conf"), "y = \"local-y\"\nref = ${y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(
r#"y = "root-y"
bar {{ include "{}/ref.conf" }}
"#,
dir_str
);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_string("bar.ref").unwrap(), "local-y");
assert_eq!(config.get_string("y").unwrap(), "root-y");
}
#[test]
fn s14c_2_optional_substitution_falls_back_to_original() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("ref.conf"), "ref = ${?y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(
r#"y = "from-root"
bar {{ include "{}/ref.conf" }}
"#,
dir_str
);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_string("bar.ref").unwrap(), "from-root");
}
#[test]
fn s14c_2_delayed_merge_preserved_via_fallback() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("ref.conf"), "ref = ${y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(
r#"y = {{ a = 1 }}
y = ${{z}}
z = {{ b = 2 }}
bar {{ include "{}/ref.conf" }}
"#,
dir_str
);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_i64("y.a").unwrap(), 1);
assert_eq!(config.get_i64("y.b").unwrap(), 2);
assert_eq!(config.get_i64("bar.ref.a").unwrap(), 1);
assert_eq!(config.get_i64("bar.ref.b").unwrap(), 2);
}
#[test]
fn s14c_2_multi_level_include_relativization_chain() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("inner.conf"), "ref = ${y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
std::fs::write(
dir.path().join("outer.conf"),
format!("inner {{ include \"{}/inner.conf\" }}\n", dir_str),
)
.unwrap();
let input = format!(
r#"y = "from-root"
outer {{ include "{}/outer.conf" }}
"#,
dir_str
);
let config = hocon::parse(&input).unwrap();
assert_eq!(config.get_string("outer.inner.ref").unwrap(), "from-root");
}
#[test]
fn s14c_2_neither_path_resolves_still_errors() {
let dir = tempdir().unwrap();
std::fs::write(dir.path().join("ref.conf"), "ref = ${y}\n").unwrap();
let dir_str = dir.path().display().to_string().replace('\\', "/");
let input = format!(r#"bar {{ include "{}/ref.conf" }}"#, dir_str);
let err = hocon::parse(&input).expect_err("expected resolve error");
assert!(
err.to_string().contains("y") || err.to_string().contains("resolve"),
"error should mention the missing key 'y' or resolution failure, got: {}",
err
);
}