fix: capture stderr when tests fail#3995
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughAdded PIPE to subprocess imports in a unit-test Jinja2 template, passed Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Suggested reviewers
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/snakemake/unit_tests/templates/ruletest.py.jinja2 (1)
10-10: Consider updating the snakemake invocation to also capture stderr.For consistency with the changes in
common.py.jinja2, thischeck_outputcall could also be updated torun(..., check=True, capture_output=True). This would ensure stderr is captured when the snakemake test run fails, aiding in debugging.This is optional and could be addressed in a follow-up if desired.
♻️ Suggested change
-from subprocess import check_output +from subprocess import run# Run the test job. - check_output( + run( [ "python", "-m", "snakemake", ... ] - + conda_prefix + + conda_prefix, + check=True, + capture_output=True, )Also applies to: 30-65
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/snakemake/unit_tests/templates/ruletest.py.jinja2` at line 10, The test currently imports and uses subprocess.check_output which only captures stdout; update the import and invocation to use subprocess.run and call run(..., check=True, capture_output=True) instead of check_output so stderr is captured on failure. Replace usages of check_output(...) in the ruletest template with run(..., check=True, capture_output=True) and adjust any places reading the output to use the CompletedProcess attributes (stdout/stderr) as needed; ensure the import line in src/snakemake/unit_tests/templates/ruletest.py.jinja2 brings in run (or import subprocess and prefix calls) so the new call resolves.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@src/snakemake/unit_tests/templates/ruletest.py.jinja2`:
- Line 10: The test currently imports and uses subprocess.check_output which
only captures stdout; update the import and invocation to use subprocess.run and
call run(..., check=True, capture_output=True) instead of check_output so stderr
is captured on failure. Replace usages of check_output(...) in the ruletest
template with run(..., check=True, capture_output=True) and adjust any places
reading the output to use the CompletedProcess attributes (stdout/stderr) as
needed; ensure the import line in
src/snakemake/unit_tests/templates/ruletest.py.jinja2 brings in run (or import
subprocess and prefix calls) so the new call resolves.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 72e54b6a-44bc-402a-9a59-8a188ed73ce3
📒 Files selected for processing (2)
src/snakemake/unit_tests/templates/common.py.jinja2src/snakemake/unit_tests/templates/ruletest.py.jinja2
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/snakemake/unit_tests/templates/common.py.jinja2`:
- Around line 55-59: The current call to check_output(...) passes stderr=PIPE
but doesn't surface it when cmp fails; wrap the check_output invocation in a
try/except that catches CalledProcessError (from subprocess) and re-raises a
clearer error that includes e.stderr (decoded if needed) so ruletest.py.jinja2's
direct .check() will show cmp diagnostics; modify the block around check_output
(the call using cmp_cmds.get(expected_file.suffix, ["cmp"]) + [expected_file,
generated_file] and PIPE) to catch CalledProcessError and raise a RuntimeError
or enrich the existing exception with the captured stderr.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0908fe90-a64c-4267-9d1a-d8104a120f9a
📒 Files selected for processing (1)
src/snakemake/unit_tests/templates/common.py.jinja2
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/snakemake/unit_tests/templates/common.py.jinja2 (1)
7-7:⚠️ Potential issue | 🟠 MajorCapturing the subprocess streams here still won't surface the failure details.
check_output()already stores stdout onCalledProcessError, and Line 59 now stores stderr too, but the default exception text stays generic. A failingcmpwill therefore still bubble up without the captured diagnostics unless you catchCalledProcessErrorhere and re-raise with the decoded stream contents.🛠️ Proposed fix
-from subprocess import check_output, PIPE +from subprocess import CalledProcessError, PIPE, check_output ... def compare_files(self, expected_file, generated_file, cmp_cmds): - check_output( - cmp_cmds.get(expected_file.suffix, ["cmp"]) - + [expected_file, generated_file], - stderr=PIPE, - ) + try: + check_output( + cmp_cmds.get(expected_file.suffix, ["cmp"]) + + [expected_file, generated_file], + stderr=PIPE, + ) + except CalledProcessError as e: + details = "\n".join( + part + for part in [ + e.output.decode(errors="replace").strip() if e.output else "", + e.stderr.decode(errors="replace").strip() if e.stderr else "", + ] + if part + ) + raise AssertionError( + f"File comparison failed for {generated_file} against {expected_file}" + + (f":\n{details}" if details else "") + ) from eRun this read-only check to confirm that
str(e)does not include the captured streams:#!/bin/bash python - <<'PY' import subprocess, sys try: subprocess.check_output( [ sys.executable, "-c", "import sys; sys.stdout.write('stdout detail\\n'); sys.stderr.write('stderr detail\\n'); sys.exit(1)", ], stderr=subprocess.PIPE, ) except subprocess.CalledProcessError as e: print("str(e):", str(e)) print("output:", e.output.decode().strip()) print("stderr:", e.stderr.decode().strip()) PYExpected:
str(e)stays generic, whileoutputandstderrcontain the useful diagnostics.Also applies to: 55-60
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/snakemake/unit_tests/templates/common.py.jinja2` at line 7, The call to check_output() must be wrapped in a try/except to catch subprocess.CalledProcessError and re-raise a clearer error including the decoded e.output and e.stderr so failure diagnostics aren't lost; locate the check_output invocation(s) (and the block around line ~59) and replace the bare call with a try: ... except subprocess.CalledProcessError as e: raise RuntimeError(f"cmp failed: stdout={e.output.decode().strip()}, stderr={e.stderr.decode().strip()}") from e (or similar message) ensuring you reference e.output and e.stderr and decode them before including in the raised exception.
🧹 Nitpick comments (1)
src/snakemake/unit_tests/templates/common.py.jinja2 (1)
30-45: Drop the temporary DEBUG prints before merge.Lines 30, 38, and 45 unconditionally dump full file sets on every successful test run, which will make CI logs noisy and can bury the comparison failure you actually want to inspect. Please gate these behind an opt-in verbose flag or remove them.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/snakemake/unit_tests/templates/common.py.jinja2` around lines 30 - 45, Remove the unconditional debug prints that dump input_files, workdir_files, and expected_files; either delete the print(...) calls around those variables or gate them behind an opt-in verbose/debug flag (e.g., check self.verbose or use a logger.debug) so normal test runs don’t flood CI logs—update the code around the variables input_files, workdir_files, and expected_files to only emit these details when verbose/debug is enabled.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/snakemake/unit_tests/templates/common.py.jinja2`:
- Around line 31-37: The comment beside the workdir_files computation is
misleading about the substring patterns; update the comment near workdir_files
(the set comprehension that iterates os.walk(self.workdir)) to state that the
code uses "/.snakemake" and "/config" (no trailing slash) which correctly
excludes top-level and nested Unix paths, and clarify that the observed mismatch
is a Windows-path issue (e.g., backslashes) if Windows support is required;
optionally suggest replacing the substring checks with explicit top-level
directory filtering using Path.parts to handle Windows paths consistently.
---
Duplicate comments:
In `@src/snakemake/unit_tests/templates/common.py.jinja2`:
- Line 7: The call to check_output() must be wrapped in a try/except to catch
subprocess.CalledProcessError and re-raise a clearer error including the decoded
e.output and e.stderr so failure diagnostics aren't lost; locate the
check_output invocation(s) (and the block around line ~59) and replace the bare
call with a try: ... except subprocess.CalledProcessError as e: raise
RuntimeError(f"cmp failed: stdout={e.output.decode().strip()},
stderr={e.stderr.decode().strip()}") from e (or similar message) ensuring you
reference e.output and e.stderr and decode them before including in the raised
exception.
---
Nitpick comments:
In `@src/snakemake/unit_tests/templates/common.py.jinja2`:
- Around line 30-45: Remove the unconditional debug prints that dump
input_files, workdir_files, and expected_files; either delete the print(...)
calls around those variables or gate them behind an opt-in verbose/debug flag
(e.g., check self.verbose or use a logger.debug) so normal test runs don’t flood
CI logs—update the code around the variables input_files, workdir_files, and
expected_files to only emit these details when verbose/debug is enabled.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 96fddaf8-ebfd-4447-9466-6bf80e09241e
📒 Files selected for processing (1)
src/snakemake/unit_tests/templates/common.py.jinja2
🤖 I have created a release *beep* *boop* --- ## [9.17.0](v9.16.3...v9.17.0) (2026-03-13) ### Features * Allow storing snakemake metadata in files or databases ([#4012](#4012)) ([dd75f31](dd75f31)) * Allow to specify comparison command per-unit test ([#3956](#3956)) ([b88171c](b88171c)) * job table orderd topological when run is started ([#4018](#4018)) ([75cf506](75cf506)) * lambda functions for priority in rules ([#3253](#3253)) ([d2aa226](d2aa226)) * Make on... directive of modules accessible ([#4050](#4050)) ([e9f2e1c](e9f2e1c)) ### Bug Fixes * adjust conda tests to not fail on apple silicon; fix [#4040](#4040) ([#4049](#4049)) ([f5b0142](f5b0142)) * allow "--containerize apptainer" to output apptainer format instead of dockerfile ([#4030](#4030)) ([f5cac30](f5cac30)) * apptainer command not recognized when singularity is absent ([#4010](#4010)) ([b8162e2](b8162e2)) * capture stderr when tests fail ([#3995](#3995)) ([97d74ba](97d74ba)) * **docs:** make Data-dependent conditional execution a complete example ([#4043](#4043)) ([3a1d7f2](3a1d7f2)) * don't build the DAG when running unlock. Fixes [#4000](#4000) and [#198](#198) ([#4007](#4007)) ([acf79fd](acf79fd)) * Ensure pixi tasks may be run as advertised ([#4046](#4046)) ([88253c2](88253c2)) * fix checkpoint handling corner cases ([#3870](#3870) and [#3559](#3559)) ([#4015](#4015)) ([63f4257](63f4257)) * issue 3642 ([#4054](#4054)) ([76e6fc2](76e6fc2)) * issue 3815 ([#4026](#4026)) ([b0eec96](b0eec96)) * logging None in shellcmd context causes error ([#4064](#4064)) ([d0652cd](d0652cd)) * lookup function returns default value for empty DataFrame queries ([#4056](#4056)) ([f71de97](f71de97)) * make `cache: omit-software` a rule specific property ([#4085](#4085)) ([034a9e7](034a9e7)) * reduce number of tests leaving temporary files behind ([#4033](#4033)) ([a3a1c97](a3a1c97)) * regression in dynamic resource handling ([#4038](#4038)) ([f2c554a](f2c554a)) * somewhat shorter announce message ([#4080](#4080)) ([57efc71](57efc71)) ### Performance Improvements * switch reretry with tenacity; decouple container classes (with Python 3.7 compat for old scripts) from rest of the codebase (enabling moving to newer python versions) ([#4032](#4032)) ([ffb19e7](ffb19e7)) ### Documentation * Add AI-assisted contributions policy to contributing guidelines ([#4051](#4051)) ([dd70526](dd70526)) * **codebase:** Update & simplify plugin architecture section ([#4052](#4052)) ([176cf63](176cf63)) * Correct workflow.source_path() description in documentation ([#4036](#4036)) ([45883c5](45883c5)) * fixed wrong code example for collect() function ([#4037](#4037)) ([5c85ed8](5c85ed8)) * Minor docs improvements ([#4089](#4089)) ([29ea226](29ea226)) * switch to sphinx_design for tabs ([#3976](#3976)) ([9674614](9674614)) * typo in the migration table breaking a pip install command ([#4024](#4024)) ([66f9dda](66f9dda)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
QC
docs/) is updated to reflect the changes or this is not necessary (e.g. if the change does neither modify the language nor the behavior or functionalities of Snakemake).Summary by CodeRabbit
Refactor
Style