When an Allure adapter leaves `historyId` unset on a `TestResult`, how does the `allure-java` writer compute a value before the result file is written, and what makes that computed value change?
answer
- only filled in when it is missing
- starts from case identity
- parameters are sorted, then appended
- one flag keeps a parameter out
- the output is an opaque digest
basics
~20 sWhen historyId is missing but testCaseId is set, the writer concatenates testCaseId with the run's parameter name and value pairs, sorted and minus any marked excluded, then hashes the result. A changed parameter value changes the key.
solid answer
~40 sAt stop time the writer checks whether `historyId` is still null and `testCaseId` is set; only then does it derive a value, so an explicitly assigned `historyId` always wins and a result with no `testCaseId` gets no series key at all. The derivation builds one string: `testCaseId`, then each parameter's name and value appended, with null entries and any parameter flagged as excluded from the key dropped, and the survivors sorted by name then value. That string is hashed and stored. Sorting means the order the adapter happened to record parameters in cannot fork the series; the exclusion flag means a value can be displayed on the result without becoming part of its identity. Change a parameter's value, add one, or remove one, and the key changes.
go deeper
Recall that the key is built from case identity plus the parameters, not from the test's title, and that it is a hash rather than anything you can read.
Walk through the derivation in order - the null check, the parameter filter, the sort, the concatenation, the hash - and say what each step is there to prevent.
Show you know which changes reset a series and which do not, and that adding one innocuous parameter to a suite resets every trend in it.
Take a position on what belongs in the parameter list at all, given that recording a value there is an identity decision the whole estate then inherits.
## The rule the writer applies `allure-java` fills in the series key only when it is missing. As a test is stopped and before its `*-result.json` is written, the writer checks two things: is `historyId` still null, and is `testCaseId` set? Only if both hold does it compute a value. That single condition carries three consequences worth stating plainly: - **An explicitly set `historyId` always wins.** If an adapter, a listener or the test itself has already assigned one, the writer leaves it exactly as it is. This is the supported way to pin a series to something that survives refactoring. - **No `testCaseId` means no `historyId`.** If case identity was never assigned, nothing is derived, the field stays null, and the result belongs to no series at all. It will render, and it will never appear on a trend. - **The computation happens at stop time, not at start time.** Parameters recorded during the test are therefore included; anything added after the result is written is not. ## How the value is built The writer assembles one string and hashes it. In order: 1. Start with `testCaseId`. 2. Take the result's `parameters`, dropping null entries. 3. Drop every parameter flagged as excluded from the key. This is the deliberate escape hatch: such a parameter still displays on the report, it just does not participate in identity. 4. Sort the survivors by name, then by value. 5. Append each survivor's name followed by its value onto the string. 6. Hash the whole string and store the digest as `historyId`. Two design decisions in that list do real work. **Sorting** means the order in which the adapter happened to record the parameters cannot change the key -- a suite that records parameters from a map, or that adds one from a listener midway, still lands on the same series. **The exclusion filter** means identity and presentation are separable: you can show a value to a human without letting it fork the trend. ## What makes a computed key change, and what does not | change | new `historyId`? | why | |---|---|---| | the test method's body is edited | no | neither `testCaseId` nor the parameters moved | | the display `name` is rewritten | no | `name` is not an input | | a parameter's value changes | yes | the value is concatenated into the source string | | a parameter is added or removed | yes | the source string gains or loses a pair | | the parameters are recorded in a different order | no | they are sorted first | | a parameter is flagged as excluded | yes, once | that pair leaves the source string | | the class or method is renamed | yes | `testCaseId` is derived from the code coordinates | That fourth row is the one that surprises people: adding a harmless-looking diagnostic parameter to an existing test resets every one of its series. The key is a hash, so there is no partial match and no near miss -- one different byte in the source string gives an unrelated digest. ## Why it is a hash at all The digest makes the key fixed-length and safe to use as a map key and as part of a file name, regardless of how long the parameter values are. The cost is that the value is **opaque**: you cannot look at a `historyId` and read back which case and which parameters produced it. Debugging a broken trend therefore means comparing the *inputs* -- `testCaseId` and the `parameters` array -- between two runs, not staring at the digests. ## Practical consequences - **Do not treat `historyId` as documentation.** It is a lookup key. If you need a human-readable identity in the report, that is what `fullName`, `name` and labels are for. - **Assign `historyId` yourself when the code coordinates are not the identity you want.** A suite whose tests are generated, or one that must keep a series across a planned package move, is better served by an explicitly assigned key derived from something stable, such as an external case identifier. - **Treat the parameter list as part of the contract.** Anything you record as a parameter is, by default, part of the key. Deciding what belongs in `parameters` is therefore an identity decision, not just a display decision. - **Check `testCaseId` first when a result has no trend.** A null `historyId` in the written file is not a report bug; it means case identity was never set upstream.
- What happens to the series key if `testCaseId` was never assigned on that result?Nothing is derived. The writer only computes a key when `historyId` is null and `testCaseId` is non-null, so with case identity missing the field simply stays null. The result renders in the report but joins no trend, and it is not a report bug - the omission is upstream, in whatever was supposed to assign case identity.
- You add a diagnostic parameter to an existing suite. What does that do to the trends?It resets every series it touches. The new name and value join the string the key is hashed from, so each affected test gets an unrelated digest and starts a fresh one-point series. Flagging the parameter as excluded from the key avoids this while still showing the value on the result.
saying these in an interview costs you the question
- Thinks the writer always recomputes and overwrites historyId
- Believes the display name feeds the key
- Assumes parameter order in the file changes the key
- Expects a partial match when one parameter differs
- Thinks a result with no case identity still gets a series