mirror of
https://github.com/imjasonh/testscript-rs
synced 2026-07-22 15:42:35 +00:00
Implement WorkdirRoot configuration feature
Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com>
This commit is contained in:
parent
a74df564e0
commit
6fd802beb2
6 changed files with 309 additions and 2 deletions
37
examples/test_workdir_root.rs
Normal file
37
examples/test_workdir_root.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
use testscript_rs::testscript;
|
||||||
|
use std::fs;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
println!("Testing workdir_root functionality...");
|
||||||
|
|
||||||
|
// Create a custom root directory
|
||||||
|
let custom_root = "/tmp/test_workdir_root";
|
||||||
|
fs::create_dir_all(custom_root)?;
|
||||||
|
|
||||||
|
// Create test data
|
||||||
|
let testdata = TempDir::new()?;
|
||||||
|
let testdata_path = testdata.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path)?;
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "Testing workdir_root feature"
|
||||||
|
stdout "Testing workdir_root feature"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("test.txt"), test_content)?;
|
||||||
|
|
||||||
|
// Test with custom workdir root
|
||||||
|
println!("Running test with custom workdir root: {}", custom_root);
|
||||||
|
match testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(custom_root)
|
||||||
|
.execute() {
|
||||||
|
Ok(()) => println!("✓ Test passed with custom workdir root!"),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("✗ Test failed: {:?}", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!("All tests passed! The workdir_root feature is working correctly.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
47
src/lib.rs
47
src/lib.rs
|
|
@ -120,6 +120,18 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
||||||
/// true
|
/// true
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ### With Custom Work Directory Root
|
||||||
|
/// ```no_run
|
||||||
|
/// use testscript_rs::testscript;
|
||||||
|
///
|
||||||
|
/// // Use a custom location for test working directories
|
||||||
|
/// testscript::run("testdata")
|
||||||
|
/// .workdir_root("/tmp/my-app-tests")
|
||||||
|
/// .preserve_work_on_failure(true) // Combine features
|
||||||
|
/// .execute()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
dir: String,
|
dir: String,
|
||||||
params: RunParams,
|
params: RunParams,
|
||||||
|
|
@ -229,6 +241,41 @@ impl Builder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the root directory for test working directories
|
||||||
|
///
|
||||||
|
/// When specified, test directories will be created inside this root directory
|
||||||
|
/// instead of the system default temporary directory. This is useful for:
|
||||||
|
/// - **Debugging**: Use a known location for test directories
|
||||||
|
/// - **Performance**: Use faster storage (e.g., tmpfs on Linux)
|
||||||
|
/// - **CI environments**: Use specific temp locations
|
||||||
|
/// - **Security**: Isolate test directories to specific paths
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `root` - The directory path where test working directories should be created
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// use testscript_rs::testscript;
|
||||||
|
///
|
||||||
|
/// // Use custom location for test directories
|
||||||
|
/// testscript::run("testdata")
|
||||||
|
/// .workdir_root("/tmp/my-app-tests")
|
||||||
|
/// .preserve_work_on_failure(true) // Combine with other features
|
||||||
|
/// .execute()
|
||||||
|
/// .unwrap();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// This creates test directories like `/tmp/my-app-tests/testscript-abc123/`.
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
/// - The root directory must exist and be writable
|
||||||
|
/// - If not specified, uses the system default temporary directory
|
||||||
|
/// - Each test still gets its own isolated subdirectory
|
||||||
|
pub fn workdir_root<P: Into<std::path::PathBuf>>(mut self, root: P) -> Self {
|
||||||
|
self.params = self.params.workdir_root(root);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Execute all test scripts in the configured directory
|
/// Execute all test scripts in the configured directory
|
||||||
///
|
///
|
||||||
/// This will discover all `.txt` files in the directory and run them as test scripts.
|
/// This will discover all `.txt` files in the directory and run them as test scripts.
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,37 @@ pub struct TestEnvironment {
|
||||||
impl TestEnvironment {
|
impl TestEnvironment {
|
||||||
/// Create a new test environment with a temporary directory
|
/// Create a new test environment with a temporary directory
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
let temp_dir = tempfile::tempdir()?;
|
Self::new_with_root(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new test environment with a temporary directory in the specified root
|
||||||
|
pub fn new_with_root(workdir_root: Option<&std::path::Path>) -> Result<Self> {
|
||||||
|
let temp_dir = match workdir_root {
|
||||||
|
Some(root) => {
|
||||||
|
// Validate that the root directory exists and is writable
|
||||||
|
if !root.exists() {
|
||||||
|
return Err(Error::Generic(format!(
|
||||||
|
"Workdir root directory does not exist: {}",
|
||||||
|
root.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if !root.is_dir() {
|
||||||
|
return Err(Error::Generic(format!(
|
||||||
|
"Workdir root path is not a directory: {}",
|
||||||
|
root.display()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
// Test if the directory is writable by creating a temp directory
|
||||||
|
tempfile::TempDir::new_in(root).map_err(|e| {
|
||||||
|
Error::Generic(format!(
|
||||||
|
"Cannot create temporary directory in workdir root {}: {}",
|
||||||
|
root.display(),
|
||||||
|
e
|
||||||
|
))
|
||||||
|
})?
|
||||||
|
}
|
||||||
|
None => tempfile::tempdir()?,
|
||||||
|
};
|
||||||
let work_dir = temp_dir.path().to_path_buf();
|
let work_dir = temp_dir.path().to_path_buf();
|
||||||
|
|
||||||
Ok(TestEnvironment {
|
Ok(TestEnvironment {
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ pub fn run_script_impl(script_path: &Path, params: &RunParams) -> Result<()> {
|
||||||
let script_file = script_path.to_string_lossy().to_string();
|
let script_file = script_path.to_string_lossy().to_string();
|
||||||
|
|
||||||
// Create test environment
|
// Create test environment
|
||||||
let mut env = TestEnvironment::new()?;
|
let mut env = TestEnvironment::new_with_root(params.workdir_root.as_deref())?;
|
||||||
|
|
||||||
// Set up files from the script
|
// Set up files from the script
|
||||||
env.setup_files(&script.files)?;
|
env.setup_files(&script.files)?;
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,8 @@ pub struct RunParams {
|
||||||
pub update_scripts: bool,
|
pub update_scripts: bool,
|
||||||
/// Whether to preserve working directories when tests fail
|
/// Whether to preserve working directories when tests fail
|
||||||
pub preserve_work_on_failure: bool,
|
pub preserve_work_on_failure: bool,
|
||||||
|
/// Optional root directory for test working directories
|
||||||
|
pub workdir_root: Option<std::path::PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RunParams {
|
impl RunParams {
|
||||||
|
|
@ -55,6 +57,7 @@ impl RunParams {
|
||||||
conditions,
|
conditions,
|
||||||
update_scripts,
|
update_scripts,
|
||||||
preserve_work_on_failure: false,
|
preserve_work_on_failure: false,
|
||||||
|
workdir_root: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,6 +94,15 @@ impl RunParams {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Set the root directory for test working directories
|
||||||
|
///
|
||||||
|
/// When specified, test directories will be created inside this root directory
|
||||||
|
/// instead of the system default temporary directory.
|
||||||
|
pub fn workdir_root<P: Into<std::path::PathBuf>>(mut self, root: P) -> Self {
|
||||||
|
self.workdir_root = Some(root.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Check if a program exists in PATH (cross-platform)
|
/// Check if a program exists in PATH (cross-platform)
|
||||||
pub fn program_exists(program: &str) -> bool {
|
pub fn program_exists(program: &str) -> bool {
|
||||||
// TODO: Consider caching results for performance if needed
|
// TODO: Consider caching results for performance if needed
|
||||||
|
|
|
||||||
181
tests/workdir_root.rs
Normal file
181
tests/workdir_root.rs
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
//! Tests for workdir root configuration functionality
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use testscript_rs::testscript;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_basic_functionality() {
|
||||||
|
// Create a temporary directory to use as our custom root
|
||||||
|
let custom_root = TempDir::new().unwrap();
|
||||||
|
let custom_root_path = custom_root.path();
|
||||||
|
|
||||||
|
// Create a test directory structure
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "hello from custom root"
|
||||||
|
stdout "hello from custom root"
|
||||||
|
|
||||||
|
-- test_file.txt --
|
||||||
|
Test content in custom root
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("custom_root_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
// Run test with custom workdir root
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(custom_root_path)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "Test with custom workdir root failed: {:?}", result);
|
||||||
|
|
||||||
|
// Verify that temporary directories were created in our custom root
|
||||||
|
// (Note: the actual temp directories are cleaned up after the test runs,
|
||||||
|
// but we can verify the API works without errors)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_nonexistent_directory() {
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "test"
|
||||||
|
stdout "test"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("simple_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
// Try to use a non-existent directory as workdir root
|
||||||
|
let nonexistent_path = "/this/path/does/not/exist";
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(nonexistent_path)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_err(), "Expected error for non-existent workdir root");
|
||||||
|
let error_msg = format!("{:?}", result.unwrap_err());
|
||||||
|
assert!(error_msg.contains("does not exist"), "Error should mention directory doesn't exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_not_a_directory() {
|
||||||
|
let temp_dir = TempDir::new().unwrap();
|
||||||
|
|
||||||
|
// Create a file (not directory) to test the validation
|
||||||
|
let file_path = temp_dir.path().join("not_a_directory.txt");
|
||||||
|
fs::write(&file_path, "this is a file").unwrap();
|
||||||
|
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "test"
|
||||||
|
stdout "test"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("simple_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
// Try to use a file as workdir root
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(&file_path)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_err(), "Expected error for file used as workdir root");
|
||||||
|
let error_msg = format!("{:?}", result.unwrap_err());
|
||||||
|
assert!(error_msg.contains("not a directory"), "Error should mention path is not a directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_readonly_directory() {
|
||||||
|
// This test is tricky because creating a truly read-only directory
|
||||||
|
// that we can't write to requires platform-specific code and may fail
|
||||||
|
// in test environments. We'll skip this for now and rely on the validation
|
||||||
|
// in the actual tempfile::TempDir::new_in() call.
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_with_preserve_work() {
|
||||||
|
// Create a temporary directory to use as our custom root
|
||||||
|
let custom_root = TempDir::new().unwrap();
|
||||||
|
let custom_root_path = custom_root.path();
|
||||||
|
|
||||||
|
// Create a test directory structure
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "combined features test"
|
||||||
|
stdout "combined features test"
|
||||||
|
|
||||||
|
-- config.txt --
|
||||||
|
Configuration file for testing
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("combined_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
// Test that workdir_root can be combined with other features
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(custom_root_path)
|
||||||
|
.preserve_work_on_failure(true)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "Combined features test failed: {:?}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_with_custom_command() {
|
||||||
|
// Create a temporary directory to use as our custom root
|
||||||
|
let custom_root = TempDir::new().unwrap();
|
||||||
|
let custom_root_path = custom_root.path();
|
||||||
|
|
||||||
|
// Create a test directory structure
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"custom-echo hello world
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("custom_cmd_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
// Test that workdir_root works with custom commands
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(custom_root_path)
|
||||||
|
.command("custom-echo", |_env, args| {
|
||||||
|
// Simple custom command that just validates args exist
|
||||||
|
if args.is_empty() {
|
||||||
|
return Err(testscript_rs::Error::Generic(
|
||||||
|
"custom-echo requires arguments".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "Custom command with workdir root failed: {:?}", result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_workdir_root_api_chaining() {
|
||||||
|
// Test that the API method exists and can be chained
|
||||||
|
let custom_root = TempDir::new().unwrap();
|
||||||
|
let testdata_dir = TempDir::new().unwrap();
|
||||||
|
let testdata_path = testdata_dir.path().join("testdata");
|
||||||
|
fs::create_dir(&testdata_path).unwrap();
|
||||||
|
|
||||||
|
let test_content = r#"exec echo "chaining test"
|
||||||
|
stdout "chaining test"
|
||||||
|
"#;
|
||||||
|
|
||||||
|
fs::write(testdata_path.join("chain_test.txt"), test_content).unwrap();
|
||||||
|
|
||||||
|
let result = testscript::run(testdata_path.to_string_lossy())
|
||||||
|
.workdir_root(custom_root.path())
|
||||||
|
.workdir_root(custom_root.path()) // Can be called multiple times
|
||||||
|
.preserve_work_on_failure(false)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assert!(result.is_ok(), "API chaining test failed: {:?}", result);
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue