1
0
Fork 0
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:
Copilot 2025-09-27 19:03:10 +00:00 committed by GitHub
parent 8c3127afea
commit bfa39f9ab9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 264 additions and 5 deletions

View file

@ -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.

View file

@ -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)]

View file

@ -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(())

View file

@ -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