mirror of
https://github.com/imjasonh/testscript-rs
synced 2026-07-12 19:08:36 +00:00
Add TestWork functionality to preserve working directories on failure (#24)
- [x] Analyze current codebase and understand test execution flow - [x] Add preserve_work_on_failure field to RunParams struct - [x] Add preserve_work_on_failure() method to Builder - [x] Modify TestEnvironment to support preserving work directories - [x] Update run_script_impl to handle work directory preservation on failure - [x] Add comprehensive tests to validate the new functionality - [x] Update documentation with usage examples - [x] Update README with TestWork section showing debugging capabilities - [x] Fix clippy warnings for code quality - [x] Validate functionality works correctly in both debug and release modes - [x] Fix formatting issues that caused CI failure - [x] Replace std::mem::forget() with idiomatic TempDir::keep() method ## Improved Code Quality ✅ Replaced the non-idiomatic use of `std::mem::forget()` with the proper `TempDir::keep()` method for preserving temporary directories. This approach: - Is more explicit about the intent to preserve the directory - Is the recommended way according to tempfile crate documentation - Removes the need for manual memory management workarounds - Makes the code more readable and maintainable All tests continue to pass and functionality remains unchanged. <!-- START COPILOT CODING AGENT SUFFIX --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Add TestWork functionality to preserve working directories</issue_title> > <issue_description>## Feature Request: TestWork Functionality > > Go's testscript supports a `TestWork` parameter that preserves working directories when tests fail, making debugging much easier. > > ## Proposed API > > ```rust > testscript::run("testdata") > .preserve_work_on_failure(true) > .execute() > .unwrap(); > ``` > > ## Implementation > > - When a test fails and this option is enabled, print the work directory path > - Don't clean up the temporary directory on test failure > - Could also support preserving on success for debugging > > ## Benefits > > - **Easier debugging**: Inspect files created during failed tests > - **Development workflow**: Understand what went wrong > - **Compatibility**: Matches Go testscript behavior > > ## Example Output > > ``` > Test failed. Work directory preserved at: /tmp/testscript-work-abc123 > You can inspect the test environment: > cd /tmp/testscript-work-abc123 > ls -la > ```</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> Fixes imjasonh/testscript-rs#6 <!-- START COPILOT CODING AGENT TIPS --> --- 💬 Share your feedback on Copilot coding agent for the chance to win a $200 gift card! Click [here](https://survey3.medallia.com/?EAHeSx-AP01bZqG0Ld9QLQ) to start the survey. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: imjasonh <210737+imjasonh@users.noreply.github.com> Co-authored-by: Jason Hall <jason@chainguard.dev>
This commit is contained in:
parent
8c3127afea
commit
bfa39f9ab9
7 changed files with 264 additions and 5 deletions
31
src/lib.rs
31
src/lib.rs
|
|
@ -93,7 +93,7 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
|||
/// testscript::run("testdata").execute().unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// ### With Custom Setup
|
||||
/// ### With Custom Setup and Work Directory Preservation
|
||||
/// ```no_run
|
||||
/// use testscript_rs::testscript;
|
||||
///
|
||||
|
|
@ -111,6 +111,7 @@ fn run(params: &mut RunParams, test_data_glob: &str) -> Result<()> {
|
|||
/// Ok(())
|
||||
/// })
|
||||
/// .condition("custom", my_custom_check())
|
||||
/// .preserve_work_on_failure(true) // Preserve work directory on test failure
|
||||
/// .execute()
|
||||
/// .unwrap();
|
||||
///
|
||||
|
|
@ -200,6 +201,34 @@ impl Builder {
|
|||
self
|
||||
}
|
||||
|
||||
/// Enable or disable preserving working directories when tests fail
|
||||
///
|
||||
/// When enabled, if a test fails, the working directory will be preserved
|
||||
/// and its path will be printed to stderr for debugging purposes.
|
||||
/// This matches the behavior of Go's testscript TestWork functionality.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use testscript_rs::testscript;
|
||||
///
|
||||
/// testscript::run("testdata")
|
||||
/// .preserve_work_on_failure(true)
|
||||
/// .execute()
|
||||
/// .unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// When a test fails, you'll see output like:
|
||||
/// ```text
|
||||
/// Test failed. Work directory preserved at: /tmp/testscript-work-abc123
|
||||
/// You can inspect the test environment:
|
||||
/// cd /tmp/testscript-work-abc123
|
||||
/// ls -la
|
||||
/// ```
|
||||
pub fn preserve_work_on_failure(mut self, preserve: bool) -> Self {
|
||||
self.params = self.params.preserve_work_on_failure(preserve);
|
||||
self
|
||||
}
|
||||
|
||||
/// Execute all test scripts in the configured directory
|
||||
///
|
||||
/// This will discover all `.txt` files in the directory and run them as test scripts.
|
||||
|
|
|
|||
|
|
@ -301,6 +301,13 @@ impl TestEnvironment {
|
|||
|
||||
result
|
||||
}
|
||||
|
||||
/// Preserve the work directory by preventing TempDir cleanup
|
||||
/// Returns the path to the preserved directory
|
||||
pub fn preserve_work_dir(self) -> std::path::PathBuf {
|
||||
// Use the idiomatic way to preserve a TempDir
|
||||
self._temp_dir.keep()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -79,6 +79,18 @@ pub fn run_script_impl(script_path: &Path, params: &RunParams) -> Result<()> {
|
|||
}
|
||||
}
|
||||
|
||||
// Handle work directory preservation on failure
|
||||
if params.preserve_work_on_failure {
|
||||
let preserved_path = env.preserve_work_dir();
|
||||
eprintln!(
|
||||
"Test failed. Work directory preserved at: {}",
|
||||
preserved_path.display()
|
||||
);
|
||||
eprintln!("You can inspect the test environment:");
|
||||
eprintln!(" cd {}", preserved_path.display());
|
||||
eprintln!(" ls -la");
|
||||
}
|
||||
|
||||
// Wrap error with script context for non-update cases or non-output errors
|
||||
return Err(Error::script_error(
|
||||
&script_file,
|
||||
|
|
@ -90,6 +102,17 @@ pub fn run_script_impl(script_path: &Path, params: &RunParams) -> Result<()> {
|
|||
|
||||
// Check for early termination
|
||||
if env.should_skip {
|
||||
// Handle work directory preservation for skipped tests if configured
|
||||
if params.preserve_work_on_failure {
|
||||
let preserved_path = env.preserve_work_dir();
|
||||
eprintln!(
|
||||
"Test skipped. Work directory preserved at: {}",
|
||||
preserved_path.display()
|
||||
);
|
||||
eprintln!("You can inspect the test environment:");
|
||||
eprintln!(" cd {}", preserved_path.display());
|
||||
eprintln!(" ls -la");
|
||||
}
|
||||
return Err(Error::Generic("Test skipped".to_string()));
|
||||
}
|
||||
if env.should_stop {
|
||||
|
|
@ -102,10 +125,19 @@ pub fn run_script_impl(script_path: &Path, params: &RunParams) -> Result<()> {
|
|||
apply_script_updates(script_path, &content, &updates)?;
|
||||
}
|
||||
|
||||
// Wait for any remaining background processes
|
||||
// Wait for any remaining background processes - handle failures here too
|
||||
let background_names: Vec<String> = env.background_processes.keys().cloned().collect();
|
||||
for name in background_names {
|
||||
env.wait_for_background(&name)?;
|
||||
if let Err(e) = env.wait_for_background(&name) {
|
||||
if params.preserve_work_on_failure {
|
||||
let preserved_path = env.preserve_work_dir();
|
||||
eprintln!("Test failed during background process cleanup. Work directory preserved at: {}", preserved_path.display());
|
||||
eprintln!("You can inspect the test environment:");
|
||||
eprintln!(" cd {}", preserved_path.display());
|
||||
eprintln!(" ls -la");
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ pub struct RunParams {
|
|||
pub conditions: HashMap<String, bool>,
|
||||
/// Whether to update test scripts with actual output
|
||||
pub update_scripts: bool,
|
||||
/// Whether to preserve working directories when tests fail
|
||||
pub preserve_work_on_failure: bool,
|
||||
}
|
||||
|
||||
impl RunParams {
|
||||
|
|
@ -52,6 +54,7 @@ impl RunParams {
|
|||
setup: None,
|
||||
conditions,
|
||||
update_scripts,
|
||||
preserve_work_on_failure: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -82,6 +85,12 @@ impl RunParams {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set whether to preserve working directories when tests fail
|
||||
pub fn preserve_work_on_failure(mut self, preserve: bool) -> Self {
|
||||
self.preserve_work_on_failure = preserve;
|
||||
self
|
||||
}
|
||||
|
||||
/// Check if a program exists in PATH (cross-platform)
|
||||
pub fn program_exists(program: &str) -> bool {
|
||||
// TODO: Consider caching results for performance if needed
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue