Test/coverage improvements#19
Merged
Merged
Conversation
Adds 4 tests calling branch.invokeSuspend(input) directly inside
runBlocking { } so the suspend path is exercised through unambiguous
bytecode, not via the runBlocking-wrapped sync invoke().
Coverage outcome (PIT before → after):
- Mutations tracked on Branch.invokeSuspend: ~6 NO_COVERAGE → 11 KILLED
+ 3 TIMED_OUT + 3 SURVIVED + 12 NO_COVERAGE
- Net: 11 mutations now killed where previously nothing was tracked,
and 3 actionable survivors surfaced (worth a follow-up to tighten).
Caveat called out in the test file's header comment: PIT's coverage
tracking through Kotlin's coroutine state-machine bytecode is partial.
12 NO_COVERAGE remain on the suspend function despite the code being
executed by these tests. That's a PIT-vs-coroutines limitation, not a
real coverage gap. The null-branch (lines 32-38) is also genuinely
defensive-dead-code — agent<IN, OUT : Any> requires non-null OUT, so
no public-API usage can produce null at the type level. Documented in
the test file rather than chased.
Tests cover:
- TypeRoute: result matches a registered type → that route fires.
- Registration order: more-specific-first wins.
- ElseRoute: result doesn't match any TypeRoute, falls through to else.
- Missing-branch error: result has no matching route AND no onElse,
asserts the error message names the result type or onElse.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ed types 14 new tests targeting the NO_COVERAGE clusters from #885: coerceToInt — Float input (was lines 340-345 untouched): - in-range whole-number Float → coerce successfully - fractional Float → reject (return null) - out-of-range Float (1e10) → reject - NaN, +Infinity, -Infinity → reject - Short and Byte inputs → coerce (line 334 fallthrough) coerceToLong — Float input (was lines 366-371 untouched): - same six cases as above for Long with appropriate bounds - Short/Byte fallthrough (line 360) promptTypeName — List<T> recursion + nested-Generable simpleName: - `List<String>` field renders type as "List<String>" (line 215-217) - nested @generable field renders as its simpleName (line 219) PIT delta on agents_engine.generation.GenerableSupportKt: - NO_COVERAGE: 24 → 14 (10 mutants newly covered) - promptTypeName: fully covered (6 → 0 NO_COVERAGE) - coerceToInt: 5 → 2 NO_COVERAGE - coerceToLong: 4 → 2 NO_COVERAGE Remaining 14 NO_COVERAGE are in sealed-class path (sealedJsonSchema, sealedPromptFragment, dataClassPromptFragment) and coerceValue wrapper — out of scope for this ticket; tracked under #889 catch-all. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Spawns real child processes (cat, sh) to exercise the full forProcess
factory body and its three lambdas (stderr drain, onClose destroy,
default stderrSink). @EnabledOnOs(LINUX, MAC) so Windows runners skip
cleanly — CI on ubuntu-latest covers the path.
5 tests covering:
- Round-trip JSON-RPC over a `cat` child (proves stdin/stdout wiring).
- Env map applied to child (sh -c reads $TEST_VAR, prints as result).
- workingDir applied to child (sh -c prints $(pwd)).
- stderrSink receives lines from the child's stderr.
- close() destroys a long-running child within the 2-second waitFor
window (asserts <3s wall-clock).
PIT delta on StdioMcpTransport$Companion:
- NO_COVERAGE: 12 → 1
- The lone remaining mutant is the default-lambda for
`stderrSink: (String) -> Unit = {}` parameter (line 34) — equivalent
mutant on a Kotlin-synthetic empty lambda.
- 5 mutations now KILLED + 10 SURVIVED (actionable signal where there
was nothing before).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 new tests targeting the NO_COVERAGE clusters from #887: recoverInvalidArguments — Retry path (lines 317-336): - Retry where currentRaw is actually parseable → executor runs once. - Retry where currentRaw stays unparseable → post-loop throw fires. executeToolWithExecutionRecovery — Unrecoverable + null arms: - Unrecoverable wraps the original exception with "unrecoverable" message and preserves cause. (line 388) - handler returning null re-throws original exception. (line 391) parseOutput — typed (non-String) OUT branch (line 417): - Agent with @generable OUT type; model returns JSON; framework parses via fromLlmOutput. PIT delta on agents_engine.model.AgenticLoopKt: - NO_COVERAGE: 9 → 2 (7 mutants newly covered) - KILLED: now 116 - SURVIVED: 14 (actionable) Remaining 2 NO_COVERAGE are structural: - L343 recoverInvalidArguments null-arm — defensive-dead-code; the OnErrorBuilder substitutes Unrecoverable for null so the user can't actually return null from the invalidArgs lambda. - L196 selectSkillByLlm — Kotlin coroutine state-machine internal. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tests exercising the previously-fully-untested castForumReturn helper by emitting forum_return tool calls with each shape of value: - L84 outType String + non-String value → toString cast - L85 raw is instance of OUT (Int) → pass-through - L87-89 raw is Map matching @generable → constructFromMap success - L89 raw is Map but wrong shape → "could not be parsed" error - L91-93 raw is JSON String matching @generable → fromLlmOutput success - L93 raw is invalid String → "could not be parsed" error - L95 raw is incompatible (Int into ForumVerdict OUT) → catch-all error PIT delta on agents_engine.composition.forum.Forum: - NO_COVERAGE: 7 → 0 (clean win) - KILLED: 16 / SURVIVED: 4 — fully covered Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
6 tests exercising the 400/JSON-RPC-error branches that PIT marked NO_COVERAGE in the catch-all #889 ticket: - L86: malformed JSON body → HTTP 400 - L87: JSON missing "method" field → HTTP 400 - L133: tools/call without "name" param → JSON-RPC code -32602 - L135: tools/call with unknown tool → JSON-RPC code -32601 - L148: skill execute throws → response with isError:true - bonus: unknown top-level method → -32601 "Method not found" PIT delta on agents_engine.mcp.McpServer: - NO_COVERAGE on handle/handleToolCall: 6 → 2 (4 newly covered) - KILLED jumped to 56; SURVIVED 17 (actionable signal) Remaining 2 NO_COVERAGE are out of reach via this test surface: - L81 payload-too-large: already covered by McpServerBodySizeLimitTest from #851; PIT line-tracking through the early-return doesn't credit it consistently. - L107 outer-catch 500: defensive wrapping around the entire dispatcher, fires only on unexpected runtime errors that the inner branches don't surface — engineered triggers would be contrived. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.