mirror of
https://github.com/imjasonh/testscript-rs
synced 2026-07-18 14:46:25 +00:00
Allow setting environment variables in setup hook (#44)
## Plan: Support passing env vars into test environment in `setup`
- [x] Update `SetupFn` type alias in `params.rs` to accept `&mut
TestEnvironment` instead of `&TestEnvironment`
- [x] Update `setup` method signature in `params.rs` to accept `Fn(&mut
TestEnvironment)` instead of `Fn(&TestEnvironment)`
- [x] Update `setup` method signature in `lib.rs` Builder to accept
`Fn(&mut TestEnvironment)` instead of `Fn(&TestEnvironment)`
- [x] Update execution in `execution.rs` to pass `&mut env` to setup
function
- [x] Update existing test in `setup_hook.rs` to test the new
functionality (make it pass)
- [x] Add a new focused test to validate setting environment variables
in setup hook
- [x] Run tests to validate changes - all tests pass
- [x] Update documentation examples to show the new capability
- [x] Address code review feedback - fix trailing newline inconsistency
- [x] Add comprehensive test for environment variable presence/absence
to ensure proper isolation
- [x] Enhance tests to demonstrate env vars work without sh -c wrapper
(using printenv/set)
- [x] Format assert statement to single line per style guidelines
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
>
> ----
>
> *This section details on the original issue you should resolve*
>
> <issue_title>support passing env vars into the test environment in
`setup`</issue_title>
> <issue_description>```
> .setup(|env| => {
> env.set("FOO", "bar")
> }
> ```
>
> (or similar)
>
> this should make `FOO` have the value of `bar` in the test
environment</issue_description>
>
> ## Comments on the Issue (you are @copilot in this section)
>
> <comments>
> </comments>
>
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
- Fixes imjasonh/testscript-rs#43
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/imjasonh/testscript-rs/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com>
This commit is contained in:
parent
57965e8c91
commit
a69a295cbc
5 changed files with 164 additions and 27 deletions
|
|
@ -44,6 +44,9 @@ use testscript_rs::testscript;
|
||||||
fn test_my_cli() {
|
fn test_my_cli() {
|
||||||
testscript::run("testdata")
|
testscript::run("testdata")
|
||||||
.setup(|env| {
|
.setup(|env| {
|
||||||
|
// Set environment variables for tests
|
||||||
|
env.set_env_var("MY_APP_CONFIG", "/path/to/config");
|
||||||
|
|
||||||
// Compile your CLI tool
|
// Compile your CLI tool
|
||||||
std::process::Command::new("cargo")
|
std::process::Command::new("cargo")
|
||||||
.args(["build", "--bin", "my-cli"])
|
.args(["build", "--bin", "my-cli"])
|
||||||
|
|
|
||||||
29
src/lib.rs
29
src/lib.rs
|
|
@ -154,6 +154,10 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
||||||
///
|
///
|
||||||
/// testscript::run("testdata")
|
/// testscript::run("testdata")
|
||||||
/// .setup(|env| {
|
/// .setup(|env| {
|
||||||
|
/// // Set environment variables for your CLI
|
||||||
|
/// env.set_env_var("MY_APP_MODE", "testing");
|
||||||
|
/// env.set_env_var("LOG_LEVEL", "debug");
|
||||||
|
///
|
||||||
/// // Compile your CLI tool
|
/// // Compile your CLI tool
|
||||||
/// std::process::Command::new("cargo")
|
/// std::process::Command::new("cargo")
|
||||||
/// .args(["build", "--bin", "my-cli"])
|
/// .args(["build", "--bin", "my-cli"])
|
||||||
|
|
@ -203,11 +207,30 @@ impl Builder {
|
||||||
|
|
||||||
/// Add a setup function that runs before each test script
|
/// Add a setup function that runs before each test script
|
||||||
///
|
///
|
||||||
/// The setup function receives a reference to the test environment and can
|
/// The setup function receives a mutable reference to the test environment and can
|
||||||
/// perform actions like compiling binaries or setting up test data.
|
/// perform actions like compiling binaries, setting up test data, or setting environment variables.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// use testscript_rs::testscript;
|
||||||
|
///
|
||||||
|
/// testscript::run("testdata")
|
||||||
|
/// .setup(|env| {
|
||||||
|
/// // Set an environment variable
|
||||||
|
/// env.set_env_var("FOO", "bar");
|
||||||
|
///
|
||||||
|
/// // Compile a binary
|
||||||
|
/// std::process::Command::new("cargo")
|
||||||
|
/// .args(["build", "--bin", "my-cli"])
|
||||||
|
/// .status()?;
|
||||||
|
/// Ok(())
|
||||||
|
/// })
|
||||||
|
/// .execute()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
pub fn setup<F>(mut self, func: F) -> Self
|
pub fn setup<F>(mut self, func: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&TestEnvironment) -> Result<()> + 'static,
|
F: Fn(&mut TestEnvironment) -> Result<()> + 'static,
|
||||||
{
|
{
|
||||||
self.params = self.params.setup(func);
|
self.params = self.params.setup(func);
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ pub fn run_script_impl(script_path: &Path, params: &RunParams) -> Result<()> {
|
||||||
|
|
||||||
// Run setup hook if provided
|
// Run setup hook if provided
|
||||||
if let Some(setup) = ¶ms.setup {
|
if let Some(setup) = ¶ms.setup {
|
||||||
setup(&env)?;
|
setup(&mut env)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Track script updates if we're in update mode
|
// Track script updates if we're in update mode
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ use std::collections::HashMap;
|
||||||
pub type CommandFn = fn(&mut TestEnvironment, &[String]) -> Result<()>;
|
pub type CommandFn = fn(&mut TestEnvironment, &[String]) -> Result<()>;
|
||||||
|
|
||||||
/// Type alias for a setup function
|
/// Type alias for a setup function
|
||||||
pub type SetupFn = Box<dyn Fn(&TestEnvironment) -> Result<()>>;
|
pub type SetupFn = Box<dyn Fn(&mut TestEnvironment) -> Result<()>>;
|
||||||
|
|
||||||
/// Configuration parameters for running tests
|
/// Configuration parameters for running tests
|
||||||
pub struct RunParams {
|
pub struct RunParams {
|
||||||
|
|
@ -73,7 +73,7 @@ impl RunParams {
|
||||||
/// Set a setup function to run before each script
|
/// Set a setup function to run before each script
|
||||||
pub fn setup<F>(mut self, func: F) -> Self
|
pub fn setup<F>(mut self, func: F) -> Self
|
||||||
where
|
where
|
||||||
F: Fn(&TestEnvironment) -> Result<()> + 'static,
|
F: Fn(&mut TestEnvironment) -> Result<()> + 'static,
|
||||||
{
|
{
|
||||||
self.setup = Some(Box::new(func));
|
self.setup = Some(Box::new(func));
|
||||||
self
|
self
|
||||||
|
|
|
||||||
|
|
@ -87,37 +87,66 @@ fn test_setup_hook_environment() {
|
||||||
let temp_dir = TempDir::new().unwrap();
|
let temp_dir = TempDir::new().unwrap();
|
||||||
let script_path = temp_dir.path().join("env_test.txt");
|
let script_path = temp_dir.path().join("env_test.txt");
|
||||||
|
|
||||||
let script_content = r#"# Test setup hook can modify environment
|
// Use sh -c to allow shell variable expansion
|
||||||
env TEST_FROM_SETUP=should_be_set
|
let script_content = if cfg!(windows) {
|
||||||
exec echo "Value: $TEST_FROM_SETUP"
|
r#"# Test setup hook can modify environment (Windows)
|
||||||
stdout "Value: hello_from_setup""#;
|
exec cmd /c "echo Value: %TEST_FROM_SETUP%"
|
||||||
|
stdout "Value: hello_from_setup""#
|
||||||
|
} else {
|
||||||
|
r#"# Test setup hook can modify environment (Unix)
|
||||||
|
exec sh -c "echo Value: $TEST_FROM_SETUP"
|
||||||
|
stdout "Value: hello_from_setup"#
|
||||||
|
};
|
||||||
|
|
||||||
fs::write(&script_path, script_content).unwrap();
|
fs::write(&script_path, script_content).unwrap();
|
||||||
|
|
||||||
// Create RunParams with setup hook that sets an environment variable
|
// Create RunParams with setup hook that sets an environment variable
|
||||||
let params = RunParams::new().setup(|env| {
|
let params = RunParams::new().setup(|env| {
|
||||||
// The setup hook has access to the TestEnvironment but can't modify it
|
// Now we can modify the environment directly in the setup hook
|
||||||
// This demonstrates the current API limitation - we can access but not modify
|
env.set_env_var("TEST_FROM_SETUP", "hello_from_setup");
|
||||||
println!("Setup running in: {}", env.work_dir.display());
|
|
||||||
|
|
||||||
// We could write files that contain environment setup
|
|
||||||
let env_script = env.work_dir.join("setup_env.sh");
|
|
||||||
std::fs::write(&env_script, "export TEST_FROM_SETUP=hello_from_setup")?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut params = params;
|
let result = run_script(&script_path, ¶ms);
|
||||||
// Manually set the environment variable for this test
|
assert!(
|
||||||
params = params.condition("TEST_FROM_SETUP", true);
|
result.is_ok(),
|
||||||
|
"Environment setup test should pass: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_hook_multiple_env_vars() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let script_path = temp_dir.path().join("multi_env_test.txt");
|
||||||
|
|
||||||
|
// Use sh -c to allow shell variable expansion
|
||||||
|
let script_content = if cfg!(windows) {
|
||||||
|
r#"# Test setup hook can set multiple environment variables (Windows)
|
||||||
|
exec cmd /c "echo FOO=%FOO% BAR=%BAR% BAZ=%BAZ%"
|
||||||
|
stdout "FOO=value1 BAR=value2 BAZ=value3""#
|
||||||
|
} else {
|
||||||
|
r#"# Test setup hook can set multiple environment variables (Unix)
|
||||||
|
exec sh -c "echo FOO=$FOO BAR=$BAR BAZ=$BAZ"
|
||||||
|
stdout "FOO=value1 BAR=value2 BAZ=value3"#
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::write(&script_path, script_content).unwrap();
|
||||||
|
|
||||||
|
// Create RunParams with setup hook that sets multiple environment variables
|
||||||
|
let params = RunParams::new().setup(|env| {
|
||||||
|
env.set_env_var("FOO", "value1");
|
||||||
|
env.set_env_var("BAR", "value2");
|
||||||
|
env.set_env_var("BAZ", "value3");
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
let result = run_script(&script_path, ¶ms);
|
let result = run_script(&script_path, ¶ms);
|
||||||
// This test demonstrates the current limitation - setup can't modify the running environment
|
assert!(
|
||||||
if result.is_err() {
|
result.is_ok(),
|
||||||
println!(
|
"Multiple environment variables test should pass: {:?}",
|
||||||
"Environment setup test failed (expected with current API): {:?}",
|
result
|
||||||
result
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
@ -144,3 +173,85 @@ exec echo "Should not execute""#;
|
||||||
.to_string()
|
.to_string()
|
||||||
.contains("Setup deliberately failed"));
|
.contains("Setup deliberately failed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_setup_env_vars_presence_and_absence() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let script_path = temp_dir.path().join("env_presence_test.txt");
|
||||||
|
|
||||||
|
// Test that verifies:
|
||||||
|
// 1. Variables set in setup ARE available in the test
|
||||||
|
// 2. Variables NOT set in setup are NOT available in the test
|
||||||
|
let script_content = if cfg!(windows) {
|
||||||
|
r#"# Test environment variable presence and absence (Windows)
|
||||||
|
# Check that SET_VAR is available using printenv (no shell needed)
|
||||||
|
exec cmd /c set SET_VAR
|
||||||
|
stdout "SET_VAR=value_from_setup"
|
||||||
|
|
||||||
|
# Also verify with shell expansion
|
||||||
|
exec cmd /c "echo SET_VAR=%SET_VAR%"
|
||||||
|
stdout "SET_VAR=value_from_setup"
|
||||||
|
|
||||||
|
# Check that UNSET_VAR is not available
|
||||||
|
! exec cmd /c set UNSET_VAR"#
|
||||||
|
} else {
|
||||||
|
r#"# Test environment variable presence and absence (Unix)
|
||||||
|
# Check that SET_VAR is available using printenv (no shell needed)
|
||||||
|
exec printenv SET_VAR
|
||||||
|
stdout "value_from_setup"
|
||||||
|
|
||||||
|
# Also verify with shell expansion
|
||||||
|
exec sh -c "echo SET_VAR=$SET_VAR"
|
||||||
|
stdout "SET_VAR=value_from_setup"
|
||||||
|
|
||||||
|
# Check that UNSET_VAR is not available
|
||||||
|
! exec printenv UNSET_VAR"#
|
||||||
|
};
|
||||||
|
|
||||||
|
fs::write(&script_path, script_content).unwrap();
|
||||||
|
|
||||||
|
// Create RunParams with setup hook that only sets SET_VAR, not UNSET_VAR
|
||||||
|
let params = RunParams::new().setup(|env| {
|
||||||
|
env.set_env_var("SET_VAR", "value_from_setup");
|
||||||
|
// Deliberately NOT setting UNSET_VAR to verify it's not available
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = run_script(&script_path, ¶ms);
|
||||||
|
assert!(
|
||||||
|
result.is_ok(),
|
||||||
|
"Environment presence/absence test should pass: {:?}",
|
||||||
|
result
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_env_command_prints_all_vars() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
let script_path = temp_dir.path().join("env_print_test.txt");
|
||||||
|
|
||||||
|
// Test that 'env' without args prints all environment variables
|
||||||
|
let script_content = r#"# Test env command prints all variables
|
||||||
|
env VAR1=value1
|
||||||
|
env VAR2=value2
|
||||||
|
|
||||||
|
# The env command should be callable and set variables
|
||||||
|
# (printing is done to stdout which we can't easily test here,
|
||||||
|
# but we verify the vars are set by using them)
|
||||||
|
exec sh -c "echo $VAR1"
|
||||||
|
stdout "value1"
|
||||||
|
|
||||||
|
exec sh -c "echo $VAR2"
|
||||||
|
stdout "value2"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(&script_path, script_content).unwrap();
|
||||||
|
|
||||||
|
let params = RunParams::new().setup(|env| {
|
||||||
|
env.set_env_var("SETUP_VAR", "from_setup");
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
|
||||||
|
let result = run_script(&script_path, ¶ms);
|
||||||
|
assert!(result.is_ok(), "Env command test should pass: {:?}", result);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue