Repository navigation
Say how many replies a run could not check, and why - #408
Merged
Merged
Conversation
A run now counts, check by check, the replies it could not judge and the reason each check gave: the shape of a body the document gives no shape for, a body that is not JSON, one cut short, a shape RESTest cannot use, a status code or content type the document leaves undeclared on an operation read only in part. The summary says so after the verdict, and report.json lists it under notChecked with the operations the replies came from. Nothing any check reports changes, and neither does the exit code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r neither Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nished Co-Authored-By: Claude Opus 5.5 <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.
Related: None. ADR-0014's M1.8 amendment named this gap: a run that cannot check a reply says nothing about it.
What you can do now that you could not before
A run that finds nothing wrong now says what it could not check. When a check cannot judge a reply — the document gives no shape for its body, the body is not JSON, the shape points at something the document never defines — the summary counts those replies after the verdict, by check and reason, and
report.jsonlists them undernotChecked, with the operations they came from. A minute against restcountries, whose document describes every reply only in words, used to end with its 4,872 faults and nothing else; it now also says that not one of its 287,676 replies had its body compared with a shape, and why.Try it yourself
With only Java and git. The "API" is a stand-in of a few lines on the HTTP server that comes with Java: it answers every request
200with a small JSON body. Its document declares two operations:/healthdescribes its reply only in words, and/versionnamesapplication/jsonand gives no shape for it.Real output, from a fresh clone of this branch; the run ends with
0:The three lines after
no faults foundare new.restest-out/report.json(one line; laid out here for reading) says the same, operation by operation:The number of requests depends on the machine. The same command on
master(git switch --detach afa51e5d, then./mvnw -q install -DskipTestsagain) also ends with0, and says nothing about what it could not check:How it works
Every reply goes through eight checks (the faults). Five of them always reach a verdict: whether a reply is a 500, a 501 or another 5xx, whether a request broken on purpose was accepted, whether a request got no reply. Three sometimes cannot. The check of a reply's shape (
F200) has nothing to compare a body with when the document gives no shape. The checks of a status code (F920) and a content type (F921) have nothing to hold a reply to when the document declares no reply, or no type, or could only be read in part. Until now each of the three said nothing in that case, so a run that could check nothing looked exactly like one that checked everything and found it fine.Now each of the three says why, from a closed list of nine reasons:
noShapeDeclared,notJson,cutShort,notUtf8,shapeUnusableandkeyHiddenfor the shape;noReplyDeclaredfor the status code;noContentTypeDeclaredfor the content type; andreadInPartfor either of those two. Each check gives a reply at most one reason, the first that applies in its own order (the faults give each order). What a check reports as a fault does not change for any reply. A reply with nothing to judge, such as one with no body, is not counted, and neither is one whose status code or content type the document leaves out:F920andF921report those.The part of RESTest that runs the checks announces, once for each reply some check could not judge, which checks and why. Both reports count those announcements in one shared place: the screen prints one line per check and reason, most replies first, and
report.jsonlists every operation. The exit code does not change:0means no fault was found in what could be checked, and the line above says what could not.What changed in the code
restest-coreoracle/WhyNotJudged, a closed enum of the nine reasons, each with the wordreport.jsonwrites and the phrase the screen prints.oracle/CouldNotJudge: the kind of fault a check reports, what it looks at (the shape,the status code,the content type), and why it could not.RunEvent.ReplyNotJudged, carrying the reply and a non-empty list ofCouldNotJudge.Oracleinterface is unchanged.restest-oraclesSaysWhyNot, which declares the rule'skind()andwhat()once and gives aVerdict: the faults found, or the reason it could not judge. Not-judged verdicts are made once per reason.ResponseSchemaOracle,UndeclaredStatusOracleandUndeclaredContentTypeOracleimplement it. Theirjudgeisverdict(...).findings(), so the two cannot disagree. Each follows the order its Javadoc anddocs/faults.mdgive.SpecificationDocument.pointerToSchemareturns aShape: the trail to it,NONE_GIVENorOUT_OF_REACH.ResponseSchemaOraclekeeps each declared reply'sShapebeside the checkers it already kept, since it now looks it up for replies that are not JSON too.HeldToTheDocumentno longer leaves out an operation read only in part. Each rule decides at its own step: what it read is judged, and only what it could not find isreadInPart.OracleListenerasks the rules that can say why, announces their faults as before, and sends oneReplyNotJudgedafter all rules. It allocates its list only when a rule could not judge. A rule that throws is still RESTest's own failure (exit4), never "could not judge".restest-reportNotCheckedcounts, by check and reason, replies and the operations they came from, in one place for both reports, asServerErrorsandPhasesalready do.ConsoleReportprints its lines after the verdict, before the operations that could not be tested.JsonReportwritesnotChecked[]afterskippedOperations[].Phasespasses the event over.docs/report.md: the new summary line;notChecked[]with a real example; its operations; and the nine reasons.docs/faults.md: each check's order and what it counts; a0covers what could be checked.docs/command-line.mdand manual chapter 9: the0row.docs/known-limitations.md.0and says what it could not check.DocumentedReportTestholds the reasons listed indocs/report.mdto the enum, and requires a real run to writenotChecked.run.sqlite: judging a stored run again would give them again.Evidence
Tests: 27 new test methods, and the existing tests of the three rules' replies left alone now also say whether, and why, each was not judged.
./mvnw verifyis green on all nine modules, 4,594 tests. CI: added below once it has run.Architecture tests: one added (
DocumentedReportTest: the reasons on the page are exactly RESTest's), none weakened.Mutation score,
restest-oracles: 97% (294 of 304), 98% line coverage;master98% (249 of 255), 96%. The four new survivors are equivalent: anullreturned insideOptional.mapbecomes the same empty result. The other two were there before.Per-request overhead: measured on swagger-petstore, restcountries and rest-scs, three one-minute runs of
masterand of this branch, alternated, with the same seed for each pair, on battery power.masterSmoke run: the two pinned images, run as the smoke job runs them (10 s, seed 20260914), three runs of
masterand of this branch, alternated.master36,996–56,162 faults; this branch 35,795–57,323. This branch addsthe shape of 46,529–72,080 replies, on 19 operations: the document gives no shapeand the content type of 12,238–18,925, on 18.master54,464–63,084 faults; this branch 61,852–65,395. This branch adds the shape of 92,790–98,140 replies and the content type of 89,322–94,476, both on 29 operations.1. One round of the activity catalogue was run again, on both builds, after its container stopped answering mid-campaign.Idle time: 4.4–5.4% in the smoke runs on both builds; 0.5–0.8% in the one-minute runs.
Artefact: the fifteen Web Fuzzing Dataset APIs measured for the earlier rules of fault, run again locally, a minute each, seed 42.
F920reports, or no body at all.noShapeDeclaredon nine, andnotJsonon rest-scs's text replies.1, as onmaster.Decisions taken
0. Nearly every document leaves some reply without a shape, so a code for "not everything checked" would almost never be0. Whether a run may be asked to fail on it is left open. Schemathesis, for comparison, shows what it could not validate as a warning that fails a run only when asked.F920andF921report those replies, and counting them again would say the same thing twice. The cost is that with either check switched off nothing says those replies went unchecked, and the known limitations say so.restest-oracles. A check written elsewhere stays one method to write.kind(),what()andverdict(). The last returns a type only that module can read. The Javadoc says so.F200counts. It judges any reply whose status code the document declares, 5xx included, so a 500 with no shape declared counts undernoShapeDeclared. All 1,517 of restcountries' 500s are in that row.F920andF921leave server errors to the server-error checks.readInPart. Which faults are reported does not change.whatis "the shape". Both read the same for one reply as for thousands.docs/faults.mdhold those.Specifications.pets()now models/noreplyas declaring no content, as a model read from that document would.docs/report.md's "The summary" come from runs before this change, and a run of the same pet shop now prints the new lines. They are re-taken, with the rest of the documentation's output, by the pull request that prepares the next release.Open questions
None.
masterAGENTS.mdholddocs/DESIGN.mdwithout explicit approval🤖 Generated with Claude Code