From 1dfd9db40dab39ae9b7480e40de9b6a445ed7b35 Mon Sep 17 00:00:00 2001 From: Clay McLeod Date: Mon, 16 Mar 2026 11:36:45 -0500 Subject: [PATCH 1/2] feat: adds `--output-file` flag to `spectool test` When set, spectool reads task/workflow outputs from the specified file path instead of the default `outputs.json` in the working directory. The path supports `~{target}` substitution, making it compatible with engines that write outputs to indexed locations (e.g., Sprocket's `--index-on` flag). --- src/command/test.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/command/test.rs b/src/command/test.rs index 79fa73a..ea24d3d 100644 --- a/src/command/test.rs +++ b/src/command/test.rs @@ -140,6 +140,17 @@ pub struct Args { #[arg(long, default_value_t = false)] redirect_stdout: bool, + /// Path to read outputs from after the command executes. + /// + /// Supports `~{target}` substitution for the workflow or task name. When + /// set, spectool reads outputs from this file instead of from the default + /// `outputs.json` in the working directory. + /// + /// For example, `--output-file "/tmp/out/index/result/outputs.json"` + /// reads from a stable path created by an engine's index feature. + #[arg(long, value_name = "PATH")] + output_file: Option, + /// Only run tests matching these patterns (comma-separated). /// /// Patterns are matched as substrings of test names. @@ -506,6 +517,11 @@ fn process_test( tracing::debug!("executing command `{}`", command); + // Resolve the output file path if provided + let output_file = args.output_file.as_ref().map(|path| { + PathBuf::from(path.replace("~{target}", target.name())) + }); + // Execute the test and evaluate the result let start_time = std::time::Instant::now(); let result = execute_and_evaluate_test( @@ -514,6 +530,7 @@ fn process_test( &root_dir, &workdir, args.redirect_stdout, + output_file.as_deref(), args.output_selector.as_deref(), ); let elapsed = start_time.elapsed(); @@ -595,6 +612,7 @@ fn execute_and_evaluate_test( root_dir: &Path, workdir: &Path, redirect_stdout: bool, + output_file: Option<&Path>, output_selector: Option<&str>, ) -> TestResult { // Execute the command @@ -656,7 +674,9 @@ fn execute_and_evaluate_test( // If we have expected output, validate it if let Some(expected_output) = test.output() { - let outputs_path = workdir.join("outputs.json"); + let outputs_path = output_file + .map(|p| p.to_path_buf()) + .unwrap_or_else(|| workdir.join("outputs.json")); let actual_output = match std::fs::read_to_string(&outputs_path) { Ok(content) => content, From a24340c97904da2525fee1d647dd9a80625ef116 Mon Sep 17 00:00:00 2001 From: Clay McLeod Date: Mon, 16 Mar 2026 11:42:55 -0500 Subject: [PATCH 2/2] chore: `cargo +nightly fmt` --- src/command/test.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/command/test.rs b/src/command/test.rs index ea24d3d..5e1b99c 100644 --- a/src/command/test.rs +++ b/src/command/test.rs @@ -518,9 +518,10 @@ fn process_test( tracing::debug!("executing command `{}`", command); // Resolve the output file path if provided - let output_file = args.output_file.as_ref().map(|path| { - PathBuf::from(path.replace("~{target}", target.name())) - }); + let output_file = args + .output_file + .as_ref() + .map(|path| PathBuf::from(path.replace("~{target}", target.name()))); // Execute the test and evaluate the result let start_time = std::time::Instant::now();