diff --git a/.github/workflows/ci-shimforge-config.yml b/.github/workflows/ci-shimforge-config.yml new file mode 100644 index 00000000..3a036917 --- /dev/null +++ b/.github/workflows/ci-shimforge-config.yml @@ -0,0 +1,29 @@ +name: Fork shimforge config tests +on: + push: + branches: ['shimforge-config-tests'] + workflow_dispatch: +permissions: + contents: read +jobs: + verify: + if: github.repository == 'tanglearncode/zoxide' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@stable + with: + toolchain: 1.88.0 + - name: Build the config tests at zoxide's MSRV, with the lockfile verified + run: cargo test --locked --bins config -- --list + - name: Run the config tests 20 times at 8 threads, nothing serialized + run: | + for attempt in $(seq 1 20); do + cargo test --locked --bins config -- --test-threads=8 + done + echo 'config tests: 20 runs x 8 threads, no env mutation.' >> "$GITHUB_STEP_SUMMARY" + - name: Run the full test suite with the mocks present + run: cargo test --locked diff --git a/Cargo.lock b/Cargo.lock index 16edf6e8..47d40d12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -808,6 +808,27 @@ dependencies = [ "zmij", ] +[[package]] +name = "shimforge" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ebf7ede656bc1cbb829e0ce8e9abf91fa3d1862b7862be358bc08a5b3064b63" +dependencies = [ + "libc", + "shimforge-macros", +] + +[[package]] +name = "shimforge-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30e52f9314e5203ce9e4d956d789be7457d07c0afc65448ec5e0e1d9c92cf614" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1171,6 +1192,7 @@ dependencies = [ "rstest", "rstest_reuse", "serde", + "shimforge", "tempfile", "time", "which", diff --git a/Cargo.toml b/Cargo.toml index bfcf4086..d93aac27 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,7 @@ color-print = "0.3.4" [dev-dependencies] assert_cmd = "2.0.0" +shimforge = "=0.1.1" rstest = { version = "0.26.0", default-features = false } rstest_reuse = "0.7.0" tempfile = "3.15.0" diff --git a/src/config.rs b/src/config.rs index 0aeda5c5..caf6e185 100644 --- a/src/config.rs +++ b/src/config.rs @@ -60,3 +60,58 @@ pub fn maxage() -> Result { pub fn resolve_symlinks() -> bool { env::var_os("_ZO_RESOLVE_SYMLINKS").is_some_and(|var| var == "1") } + +#[cfg(test)] +mod tests { + use std::ffi::OsString; + + use shimforge::{Session, mock}; + + use super::*; + + // Each test gets its own value for one variable, on its own thread. The + // process environment is never written, so these run in parallel with + // each other and with every other test in the suite. + fn env_var(session: &mut Session, name: &'static str, value: Option<&str>) { + let var_os = mock!(session, env::var_os::<&str>, fn(&str) -> Option); + let value = value.map(OsString::from); + var_os.expect().with(move |var| *var == name).returns(value); + } + + #[test] + fn maxage_defaults_when_unset() { + let mut session = Session::new(); + env_var(&mut session, "_ZO_MAXAGE", None); + assert_eq!(maxage().unwrap(), 10_000.0); + } + + #[test] + fn maxage_reads_the_variable() { + let mut session = Session::new(); + env_var(&mut session, "_ZO_MAXAGE", Some("500")); + assert_eq!(maxage().unwrap(), 500.0); + } + + #[test] + fn maxage_rejects_a_value_that_is_not_a_number() { + let mut session = Session::new(); + env_var(&mut session, "_ZO_MAXAGE", Some("soon")); + let error = maxage().unwrap_err().to_string(); + assert!(error.contains("unable to parse _ZO_MAXAGE"), "{error}"); + } + + #[test] + fn a_relative_data_dir_is_rejected() { + let mut session = Session::new(); + env_var(&mut session, "_ZO_DATA_DIR", Some("relative/zoxide")); + let error = data_dir().unwrap_err().to_string(); + assert!(error.contains("must be an absolute path"), "{error}"); + } + + #[test] + fn resolve_symlinks_is_opt_in() { + let mut session = Session::new(); + env_var(&mut session, "_ZO_RESOLVE_SYMLINKS", Some("1")); + assert!(resolve_symlinks()); + } +}