agjendem opened a new issue, #3544: URL: https://github.com/apache/maven-surefire/issues/3544
### Affected version 3.6.0 (maven-surefire-plugin and maven-failsafe-plugin). The code involved is the same on master and on the `surefire-3.6.1` tag. ### Bug description With `runOrder=random` on the JUnit Platform and the default `forkCount=1`, the test classes are shuffled twice with the same seed. The second shuffle applies the same permutation again, so the class order is σ² instead of σ. Some orders can never occur: two test classes always run in alphabetical order, whatever the seed. **Reproducer** Three test classes `demo.ATest`, `demo.BTest` and `demo.CTest`, each with one test that prints its class name, and this `pom.xml`: <details><summary>pom.xml and ATest.java</summary> ```xml <project xmlns="http://maven.apache.org/POM/4.0.0"> <modelVersion>4.0.0</modelVersion> <groupId>demo</groupId> <artifactId>surefire-random-order</artifactId> <version>1.0</version> <properties> <maven.compiler.release>17</maven.compiler.release> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.junit</groupId> <artifactId>junit-bom</artifactId> <version>5.13.4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>3.6.0</version> </plugin> </plugins> </build> </project> ``` ```java package demo; import org.junit.jupiter.api.Test; class ATest { @Test void run() { System.out.println("ORDER A"); } } ``` </details> For each of 20 large seeds: ``` mvn surefire:test -Dtest=ATest,BTest -Dsurefire.runOrder=random -Dsurefire.runOrder.random.seed=<seed> ``` As a control, the same seeds with only JUnit's random class orderer (default `runOrder`): ``` mvn surefire:test -Dtest=ATest,BTest "-DargLine=-Djunit.jupiter.testclass.order.default=org.junit.jupiter.api.ClassOrderer\$Random -Djunit.jupiter.execution.order.random.seed=<seed>" ``` | Classes | `runOrder=random` | Only JUnit `ClassOrderer$Random` | |---|---|---| | A, B | AB ×20 | AB ×9, BA ×11 | | A, B, C | ABC ×17, CAB ×3 | ACB ×6, BCA ×6, BAC ×4, CAB ×3, CBA ×1 | The seeds must be large. For small seeds such as 1–20, `java.util.Random` returns the same first `nextInt(2)`, so the control gives BA every time. **Cause** (links to the `surefire-3.6.0` tag) 1. `DefaultRunOrderCalculator` creates `new Random(seed)` ([L52-L53](https://github.com/apache/maven-surefire/blob/0ff622b160253f362511a72d29a1f8067631f9d3/surefire-api/src/main/java/org/apache/maven/surefire/api/util/DefaultRunOrderCalculator.java#L52-L53)), sorts the classes and shuffles them ([L70-L72](https://github.com/apache/maven-surefire/blob/0ff622b160253f362511a72d29a1f8067631f9d3/surefire-api/src/main/java/org/apache/maven/surefire/api/util/DefaultRunOrderCalculator.java#L70-L72)). 2. `JUnitPlatformProvider.setupRunOrder()` sets `ClassOrderer$Random` and passes the same seed as `junit.jupiter.execution.order.random.seed` ([L168-L174](https://github.com/apache/maven-surefire/blob/0ff622b160253f362511a72d29a1f8067631f9d3/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java#L168-L174)). 3. With `forkCount=1` and `reuseForks=true`, the defaults ([`ForkStarter.isForkOnce()`](https://github.com/apache/maven-surefire/blob/0ff622b160253f362511a72d29a1f8067631f9d3/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/booterclient/ForkStarter.java#L338-L340)), the provider sends all classes to JUnit in one discovery request, in the shuffled order ([L278-L283](https://github.com/apache/maven-surefire/blob/0ff622b160253f362511a72d29a1f8067631f9d3/surefire-providers/surefire-junit-platform/src/main/java/org/apache/maven/surefire/junitplatform/JUnitPlatformProvider.java#L278-L283)). 4. Jupiter's `ClassOrderer.Random` calls `Collections.shuffle(classDescriptors, new Random(seed))`. `Collections.shuffle` with a fresh `Random(seed)` applies the same permutation σ to every list of the same size. Step 4 applies σ to the result of step 1, so the final order is σ∘σ. A simulation of `java.util.Random` and `Collections.shuffle` with σ∘σ gives exactly the 40 orders in the `runOrder=random` column. With two classes, σ² is always the identity. In general only squares of permutations can occur, and the alphabetical order comes up with probability (number of involutions)/n!: 1 for n=2, 2/3 for n=3 and 5/12 for n=4. This came with #3421 (SUREFIRE-859), which both sorts the classes before the shuffle and forwards the seed to Jupiter. Each change is right on its own, but together they shuffle twice with one seed. **Why it matters** `runOrder=random` is how you look for order-dependent tests. Because of σ², a module with two test classes never runs them in the other order, and with more classes many orders never occur. We found this while we looked for order-dependent tests in a large multi-module build: the class orders we saw matched σ², not σ. **Possible fixes** - When all classes go to JUnit in one request with `ClassOrderer$Random`, keep the alphabetical sort but skip Surefire's own shuffle, so that Jupiter shuffles once. The path with one class per request (`LazyTestsToRun`) still needs Surefire's shuffle. - Or give Jupiter a seed that is derived from the Surefire seed, for example mixed with a constant, so that one seed still reproduces the whole order. ### AI assistance I used generative AI assistance while working on this report. I reviewed and verified the analysis and the results, and remain responsible for the report. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
