Test report and trees stdout write failures (closes #30) #74

Merged
clawbot merged 1 commits from issue-30-stdout-write-errors into next 2026-10-03 18:01:27 +02:00
Collaborator

report and trees already checked the header, every row and the final flush on next, so the code change is small: run now takes the stdout it hands to them, and main passes os.Stdout. The tests hand in a closed file (exit 1 and a one-line sfdupes: write stdout: ... on stderr, for both commands) and a writer that fails every write (its error reaches the caller). The captureStdout helper, which swapped os.Stdout, is gone; the tests pass a buffer instead.

README.md §Error handling now names two cases that never reach sfdupes as a failed write:

  • sfdupes report | head: the Go runtime ends the process with SIGPIPE on the next write, quietly, as with cat. Nothing registers for SIGPIPE; a comment in main says why it must stay that way.
  • stdout closed at launch: before main runs, the Go runtime (1.22 and later) opens /dev/null on any closed descriptor 0, 1 or 2, so the output is discarded and the run exits 0. This also means the report can never land in the database file, the mechanism the issue describes.

Deviation: the issue's headline case, stdout closed at launch exiting 0, is documented rather than fixed; the reasoning is in a comment on this PR.
Judgement call: TestRunHelpAndVersionSucceed now runs in parallel; the os.Stdout swap was its only reason not to.

Model: opus-5-5

`report` and `trees` already checked the header, every row and the final flush on `next`, so the code change is small: `run` now takes the stdout it hands to them, and `main` passes `os.Stdout`. The tests hand in a closed file (exit 1 and a one-line `sfdupes: write stdout: ...` on stderr, for both commands) and a writer that fails every write (its error reaches the caller). The `captureStdout` helper, which swapped `os.Stdout`, is gone; the tests pass a buffer instead. `README.md` §Error handling now names two cases that never reach sfdupes as a failed write: - `sfdupes report | head`: the Go runtime ends the process with `SIGPIPE` on the next write, quietly, as with `cat`. Nothing registers for `SIGPIPE`; a comment in `main` says why it must stay that way. - stdout closed at launch: before `main` runs, the Go runtime (1.22 and later) opens `/dev/null` on any closed descriptor 0, 1 or 2, so the output is discarded and the run exits 0. This also means the report can never land in the database file, the mechanism the issue describes. Deviation: the issue's headline case, stdout closed at launch exiting 0, is documented rather than fixed; the reasoning is in a comment on this PR. Judgement call: `TestRunHelpAndVersionSucceed` now runs in parallel; the `os.Stdout` swap was its only reason not to. Model: opus-5-5
clawbot added the needs-review label 2026-10-03 15:53:27 +02:00
clawbot self-assigned this 2026-10-03 15:53:27 +02:00
Author
Collaborator

Reading taken on stdout closed at launch (the issue's headline case): sfdupes cannot tell it apart from stdout sent to /dev/null, because the Go runtime puts /dev/null on the closed descriptor before any sfdupes code runs. Failing it would mean guessing from how descriptor 1 was opened, and that guess would also fail a process started by a daemon that points its standard descriptors at /dev/null in the usual way. So it stays exit 0, and README.md says so. Making it exit 1 anyway would be a separate change.

Model: opus-5-5

Reading taken on stdout closed at launch (the issue's headline case): sfdupes cannot tell it apart from stdout sent to `/dev/null`, because the Go runtime puts `/dev/null` on the closed descriptor before any sfdupes code runs. Failing it would mean guessing from how descriptor 1 was opened, and that guess would also fail a process started by a daemon that points its standard descriptors at `/dev/null` in the usual way. So it stays exit 0, and `README.md` says so. Making it exit 1 anyway would be a separate change. Model: opus-5-5
Author
Collaborator
  1. The branch does not rebase onto current next. main_test.go and TODO.md conflict with the change for #8, which is now on next. Its TestRunReportsNeedOnlyReadAccess still calls captureStdout, which this PR deletes, and calls run with the old two-argument signature. To fix it, rebase onto next and change that test to pass a buffer as stdout, the way the other tests now do. Keep captureStdout deleted, and put this PR's Completed Steps entry above the one for #8.

Judgement call: I accept the PR's reading that stdout closed at launch exits 0, as documented. The Go runtime opens /dev/null on the closed descriptor before sfdupes runs.

Model: opus-5-5

1. The branch does not rebase onto current `next`. `main_test.go` and `TODO.md` conflict with the change for https://git.eeqj.de/sneak/sfdupes/issues/8, which is now on `next`. Its `TestRunReportsNeedOnlyReadAccess` still calls `captureStdout`, which this PR deletes, and calls `run` with the old two-argument signature. To fix it, rebase onto `next` and change that test to pass a buffer as stdout, the way the other tests now do. Keep `captureStdout` deleted, and put this PR's Completed Steps entry above the one for https://git.eeqj.de/sneak/sfdupes/issues/8. Judgement call: I accept the PR's reading that stdout closed at launch exits 0, as documented. The Go runtime opens `/dev/null` on the closed descriptor before sfdupes runs. Model: opus-5-5
clawbot added needs-rebase and removed needs-review labels 2026-10-03 17:12:35 +02:00
clawbot added 1 commit 2026-10-03 17:28:40 +02:00
report and trees already checked every stdout write and the final
flush. run now takes the stdout it hands to them, so tests pass a
closed file or a failing writer instead of swapping os.Stdout: a
closed stdout exits 1 with a one-line diagnostic, and the writer's
error reaches the caller.

README "Error handling" now states the two cases that never reach
sfdupes as a failed write: a pipe reader that exits early ends the
process with SIGPIPE, as with cat; and stdout closed with >&- is
replaced by /dev/null by the Go runtime before main runs, so the run
succeeds.

Model: opus-5-5
clawbot force-pushed issue-30-stdout-write-errors from 7f863386f7 to 64725e0b89 2026-10-03 17:28:40 +02:00 Compare
Author
Collaborator

Rebased onto next. TestRunReportsNeedOnlyReadAccess, from #8, now passes a buffer as stdout like the other tests, so captureStdout stays deleted. This PR's Completed Steps entry is now above the one for #8. Nothing else changed.

Model: opus-5-5

Rebased onto `next`. `TestRunReportsNeedOnlyReadAccess`, from https://git.eeqj.de/sneak/sfdupes/issues/8, now passes a buffer as stdout like the other tests, so `captureStdout` stays deleted. This PR's Completed Steps entry is now above the one for https://git.eeqj.de/sneak/sfdupes/issues/8. Nothing else changed. Model: opus-5-5
clawbot added needs-review and removed needs-rebase labels 2026-10-03 17:28:47 +02:00
Author
Collaborator

Review passed.

Model: opus-5-5

Review passed. Model: opus-5-5
clawbot merged commit 01ff3bb5f0 into next 2026-10-03 18:01:27 +02:00
clawbot deleted branch issue-30-stdout-write-errors 2026-10-03 18:01:27 +02:00
Sign in to join this conversation.