One `<testcase>` element in a Maven Surefire JUnit-XML file carries a `<failure>` and two `<rerunFailure>` children. How many times did that case actually run, and why is that count not a field in the file?
answer
- one element per case, not per attempt
- count the children, not the cases
- the two shapes need different arithmetic
- one shape leaves its last attempt unwritten
basics
~20 sThree times: one <failure> plus two <rerunFailure> children is three failed attempts. No field records it - the file keeps one <testcase> per case, so the attempt count is arithmetic over that element's outcome children.
solid answer
~40 sThree. The file holds exactly one `<testcase>` element per case however many times it ran, and the attempts are recorded as that element's outcome children. A case that never passed is written as one `<failure>` or `<error>` plus one repetition element per later attempt, so a `<failure>` and two `<rerunFailure>` children mean three attempts, all failed. A case that recovered has no plain `<failure>` at all: it carries one `<flakyFailure>` or `<flakyError>` per failed attempt plus an unrecorded final pass, so *M* flaky children mean *M+1* attempts. There is no attempt-count attribute to read - `@flakes` on `<testsuite>` counts recovered *cases*, not attempts - and `@time` holds one run rather than a total, so a consumer that wants attempts has to count children and know which of the two shapes it is looking at.
code
xml · 12 lines<testcase name="chargesCard" classname="com.example.PayTest" time="2.10">
<failure message="expected 200 but was 503" type="java.lang.AssertionError">java.lang.AssertionError: expected 200 but was 503
at com.example.PayTest.chargesCard(PayTest.java:58)</failure>
<rerunFailure message="expected 200 but was 503" type="java.lang.AssertionError">
<stackTrace>java.lang.AssertionError: expected 200 but was 503
at com.example.PayTest.chargesCard(PayTest.java:58)</stackTrace>
</rerunFailure>
<rerunFailure message="expected 200 but was 503" type="java.lang.AssertionError">
<stackTrace>java.lang.AssertionError: expected 200 but was 503
at com.example.PayTest.chargesCard(PayTest.java:58)</stackTrace>
</rerunFailure>
</testcase>go deeper
Know that a re-run case still produces a single <testcase> element, and that each failed attempt after the first is written as its own child element inside it.
Do the arithmetic out loud: a <failure> plus N rerun elements is N+1 failed attempts, while M flaky elements are M failed attempts followed by a pass that leaves no element behind.
Explain what this breaks downstream - inflated failure counts, wall-clock totals that miss re-run time - and how you would test a consumer against a fixture holding both shapes.
Decide whether your organisation stores raw attempts or a per-case verdict in its result store, and defend the query cost and the reporting honesty each choice buys.
Three. And the reason the question needs asking at all is that nothing in the file states it: the JUnit-XML result format has no attempt-count field anywhere, so the number has to be derived by counting elements and knowing which of two shapes you are looking at. ## One element per case, however many runs Whatever happens, Surefire writes **one `<testcase>` element per test case**. Re-running a case does not add a second `<testcase>`; it adds children to the one that already exists. That is the first thing to get right, because it decides what every count over the file means: - Counting `<testcase>` elements gives you the number of **cases**. - Counting outcome children gives you the number of **failed attempts**. - Nothing gives you the number of runs directly. ## The arithmetic, per shape There are two shapes, and they need different sums. | shape | children | attempts | |---|---|---| | never passed | one `<failure>` or `<error>`, then N `<rerunFailure>` / `<rerunError>` | N + 1, all failed | | recovered | M `<flakyFailure>` / `<flakyError>`, no plain `<failure>` | M + 1, the last one a pass | | passed first time | none | 1 | So a `<testcase>` with a `<failure>` and two `<rerunFailure>` children ran three times and failed all three. A `<testcase>` with three `<flakyFailure>` children ran four times: the fourth run passed and, because a pass writes no outcome child, it left nothing behind. That asymmetry - one shape records every attempt, the other records every attempt *except the decisive one* - is the part people miss. ## Why there is no field for it The format was designed around a single run per case, and re-runs were bolted on by adding element names rather than by adding a counter. The suite-level `@flakes` attribute is the closest thing to a count, and it counts something else: **cases that recovered**, not attempts. A suite where one case needed four goes and nine cases passed first time reports `flakes="1"`, and the three failed attempts are visible only inside that one `<testcase>`. `@time` is no help either. It holds **one run, not a total**, and it does not even describe the same run across the two shapes: for a case that never passed it is the first, failing run, and for a recovered case it is the last, successful one. Summing `@time` across a suite that re-ran cases therefore under-reports the wall clock those re-runs actually consumed, silently, and by an amount that grows with the number of re-runs. ## What this breaks downstream 1. **Inflated failure counts.** A dashboard that sums outcome children and calls the total "failures" is counting attempts. One case re-run twice contributes three, and a recovered case contributes to that sum while contributing nothing to `@failures` on `<testsuite>`. The number of failed *cases* and the number of failed *attempts* are different questions, and the file answers them in different places. 2. **Duration reports that do not add up.** Job wall clock exceeds the sum of `@time` and nobody can account for the gap, because the re-run attempts are not in the sum at all. 3. **Naive merges across shards.** A merge tool that concatenates `<testcase>` elements from several files can end up with two `<testcase>` elements for the same case - one per shard - and there is no attempt field to reconcile them with. Reconciliation has to happen on case identity plus the outcome shape. ## Getting it right If you have to answer "how many times did this run" from these files, do it once, at parse time, and store the result rather than re-deriving it: - Branch on whether the `<testcase>` holds a plain `<failure>` or `<error>`. If it does, the case never passed, and attempts equal one plus the count of `rerun`-prefixed children. - Otherwise, count the `flaky`-prefixed children; attempts equal that count plus one, and the case ended green. - Record the verdict and the attempt count as explicit fields, so nothing downstream has to re-read element names to work out what happened. Then test the parser against a fixture holding all three shapes - a plain pass, a case with `<failure>` plus rerun elements, and a case with only flaky elements. A fixture built from a build that never re-ran anything will agree with almost any wrong implementation.
- Does `@time` on that `<testcase>` tell you how long the three attempts took together?No. `@time` holds one run, not the sum. For a case that never passed it is the first, failing run; for a case written with `<flakyFailure>` it is the last, successful one. Summing `@time` across a suite that re-ran cases therefore under-reports the wall clock those re-runs consumed.
- A dashboard sums the outcome children across a suite to get a failure count. What does it report for a build with re-runs?Too many. It is counting attempts, so one case re-run twice contributes three failures instead of one. The count of failed *cases* is `@failures` on `<testsuite>`; the outcome children count attempts, and a recovered case contributes to that sum while contributing nothing to `@failures`.
saying these in an interview costs you the question
- Thinks each attempt gets its own <testcase> element
- Counts outcome children and calls the total a failure count
- Assumes @time is the sum of all attempts of a case
- Forgets the final passing attempt has no element of its own
- Reads @flakes as a count of attempts rather than of cases