Skip to content

Answers: test the poll in answer (rejected, empty, no answer, read error) - #167

Merged
nofuturekid merged 2 commits into
rebuildfrom
fix/151-test-rejected-answer
Nov 13, 2026
Merged

nofuturekid merged 2 commits into
rebuildfrom
fix/151-test-rejected-answer

Conversation

@nofuturekid

Copy link
Copy Markdown
Collaborator

Fixes #151

What changed

Camera::answer read selector 2 straight from the device, so no test reached its arms. The poll body moved into poll_answer(command, seq, gap, read) in crates/obsbot-device/src/family/exchange.rs; read is a closure returning one selector-2 read, so a test can script the replies. Camera::answer calls it with REPLY_GAP (50 ms) and || self.dev.xu_get(2): same sleep before every read, same 20 attempts, same read, nothing sent. Production code otherwise unchanged.

The seam is deliberately small (one function, one closure) so that #152 (stacked on this branch) can use it.

New tests (module exchange::tests):

  • a rejected answer returns Error::Rejected(command, code) (code -3) after exactly one read
  • an empty answer (type 0x09 echoing the command) returns an empty list after one read
  • stale replies (other sequence) run through all 20 reads and end in Error::NoAnswer(command)
  • two failed reads, then a value: the poll goes on and returns the value (3 reads)

How it was verified

Gate, on this branch: cargo fmt --check, cargo clippy --all-targets --locked -- -D warnings, cargo test --locked: all green. tests/byte_guard.rs and tests/cli_surface.rs pass with their fixtures unchanged.

Mutations by hand in poll_answer (each reverted afterwards; the file is identical to the committed one):

Mutation Result
Some(Answer::Rejected(code)) => return Err(...) replaced by Some(Answer::Rejected(_)) => {} (the issue's mutant) a_rejected_answer_returns_the_cameras_code_at_the_first_poll FAILED
rejected error built with code 0 same test FAILED
Answer::Empty arm replaced by {} an_empty_answer_is_an_empty_list_not_a_missing_one FAILED
failed read ends the poll (read()?) a_failed_read_is_polled_again_and_a_later_value_is_returned FAILED
REPLY_ATTEMPTS 20 to 19 without_a_matching_answer_the_poll_gives_up_after_all_attempts FAILED

With the current code all four tests pass. No CHANGELOG line: tests only, nothing a user can see. No camera needed; the tests open no device.

🤖 Generated with Claude Code

Camera::answer read the device directly, so no test reached its arms. The poll is now
poll_answer, which takes the selector-2 read as a closure and the gap as an argument;
Camera::answer passes the device read and the same 50 ms gap, so what is sent and read
on the camera is unchanged. Tests script the replies: a rejected answer returns
Error::Rejected(command, code) at the first read, an empty answer returns an empty list,
a stale answer runs through all 20 reads to NoAnswer, and failed reads are polled again
until a value arrives.

Fixes #151

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The test module already gets XU_SIZE through use super::*.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@nofuturekid
nofuturekid merged commit a3adac6 into rebuild Nov 13, 2026
6 checks passed
@nofuturekid
nofuturekid deleted the fix/151-test-rejected-answer branch November 13, 2026 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant