Skip to content

Commit b3a030c

Browse files
fix: add RAII env var cleanup guards to render tests
Prevents cross-test interference by restoring environment variables on drop, even if a test panics. Closes #42 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ad92758 commit b3a030c

2 files changed

Lines changed: 36 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2020

2121
### Fixed
2222

23+
- Render tests now use an RAII `EnvGuard` to restore environment variables on drop, preventing cross-test interference when tests run in parallel.
2324
- Auto Tag workflow now uses `RELEASE_TOKEN` instead of `GITHUB_TOKEN` so the pushed tag triggers the Release workflow. Tags pushed by the default `GITHUB_TOKEN` do not trigger other workflows (GitHub Actions security feature).
2425

2526
## [1.3.0] - 2026-03-12

src/render.rs

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,35 @@ pub fn template_render(input: &str) -> Result<String, String> {
7676
#[cfg(test)]
7777
mod tests {
7878
use super::*;
79+
80+
struct EnvGuard {
81+
name: String,
82+
previous: Option<String>,
83+
}
84+
85+
impl EnvGuard {
86+
fn set(name: &str, value: &str) -> Self {
87+
let previous = env::var(name).ok();
88+
env::set_var(name, value);
89+
Self {
90+
name: name.to_string(),
91+
previous,
92+
}
93+
}
94+
}
95+
96+
impl Drop for EnvGuard {
97+
fn drop(&mut self) {
98+
match &self.previous {
99+
Some(val) => env::set_var(&self.name, val),
100+
None => env::remove_var(&self.name),
101+
}
102+
}
103+
}
104+
79105
#[test]
80106
fn test_envsubst_basic() {
81-
env::set_var("TEST_RENDER_VAR", "hello");
107+
let _g = EnvGuard::set("TEST_RENDER_VAR", "hello");
82108
assert_eq!(envsubst("say ${TEST_RENDER_VAR}"), "say hello");
83109
assert_eq!(envsubst("say $TEST_RENDER_VAR"), "say hello");
84110
}
@@ -97,31 +123,31 @@ mod tests {
97123
}
98124
#[test]
99125
fn test_envsubst_empty_value() {
100-
env::set_var("TEST_EMPTY_VAR", "");
126+
let _g = EnvGuard::set("TEST_EMPTY_VAR", "");
101127
assert_eq!(envsubst("${TEST_EMPTY_VAR}"), "");
102128
}
103129
#[test]
104130
fn test_envsubst_special_chars() {
105-
env::set_var("TEST_SPECIAL", "a=b&c");
131+
let _g = EnvGuard::set("TEST_SPECIAL", "a=b&c");
106132
assert_eq!(envsubst("${TEST_SPECIAL}"), "a=b&c");
107133
}
108134
#[test]
109135
fn test_envsubst_multiline() {
110-
env::set_var("TEST_ML", "val");
136+
let _g = EnvGuard::set("TEST_ML", "val");
111137
let input = "line1 ${TEST_ML}\nline2 $TEST_ML";
112138
let output = envsubst(input);
113139
assert!(output.contains("line1 val"));
114140
assert!(output.contains("line2 val"));
115141
}
116142
#[test]
117143
fn test_envsubst_adjacent() {
118-
env::set_var("TEST_A", "X");
119-
env::set_var("TEST_B", "Y");
144+
let _g1 = EnvGuard::set("TEST_A", "X");
145+
let _g2 = EnvGuard::set("TEST_B", "Y");
120146
assert_eq!(envsubst("${TEST_A}${TEST_B}"), "XY");
121147
}
122148
#[test]
123149
fn test_template_basic() {
124-
env::set_var("TEST_TPL_VAR", "world");
150+
let _g = EnvGuard::set("TEST_TPL_VAR", "world");
125151
let result = template_render("hello {{ env.TEST_TPL_VAR }}").unwrap();
126152
assert_eq!(result, "hello world");
127153
}
@@ -136,7 +162,7 @@ mod tests {
136162
}
137163
#[test]
138164
fn test_template_urlencode() {
139-
env::set_var("TEST_URLENCODE_VAR", "p@ss%word");
165+
let _g = EnvGuard::set("TEST_URLENCODE_VAR", "p@ss%word");
140166
let result = template_render("{{ env.TEST_URLENCODE_VAR | urlencode }}").unwrap();
141167
assert_eq!(result, "p%40ss%25word");
142168
}
@@ -147,7 +173,7 @@ mod tests {
147173
}
148174
#[test]
149175
fn test_template_conditional() {
150-
env::set_var("TEST_COND", "yes");
176+
let _g = EnvGuard::set("TEST_COND", "yes");
151177
let result = template_render("{% if env.TEST_COND %}ok{% endif %}").unwrap();
152178
assert_eq!(result, "ok");
153179
}

0 commit comments

Comments
 (0)