Race Condition in JaCoCo merge when generating a report?

279 Views Asked by At

So this is something, that a colleague called a "Schroedingers Bug" - it worked fine, until something was pointed out. Something was changed and now the bug is there. And changing back didn't help - the bug is still there :-/

In our Maven project we use JaCoCo for our code coverage (maven-jacoco-plugin version 0.8.7). The surefire plugin (version 2.22.2) is making the unit tests, the failsave (version 3.0.0-M5) is doing the integration tests.

This is our POM:

<plugin>
                <groupId>org.jacoco</groupId>
                <artifactId>jacoco-maven-plugin</artifactId>
                <version>${maven-jacoco-plugin.version}</version>
                <executions>
                    <execution>
                        <id>before-unit-test-execution</id>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <destFile>${project.build.directory}/jacoco-output/jacoco-unit-tests.exec</destFile>
                            <propertyName>surefire.jacoco.args</propertyName>
                        </configuration>
                    </execution>
                    <execution>
                        <id>after-unit-test-execution</id>
                        <phase>test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco-output/jacoco-unit-tests.exec</dataFile>
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-unit-test-coverage-report
                            </outputDirectory>
                        </configuration>
                    </execution>
                    <execution>
                        <id>before-integration-test-execution</id>
                        <phase>pre-integration-test</phase>
                        <goals>
                            <goal>prepare-agent</goal>
                        </goals>
                        <configuration>
                            <destFile>${project.build.directory}/jacoco-output/jacoco-integration-tests.exec</destFile>
                            <propertyName>failsafe.jacoco.args</propertyName>
                        </configuration>
                    </execution>
                    <execution>
                        <id>after-integration-test-execution</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco-output/jacoco-integration-tests.exec</dataFile>
                            <outputDirectory>
                                ${project.reporting.outputDirectory}/jacoco-integration-test-coverage-report
                            </outputDirectory>
                        </configuration>
                    </execution>
                    <execution>
                        <id>merge-unit-and-integration</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>merge</goal>
                        </goals>
                        <configuration>
                            <fileSets>
                                <fileSet>
                                    <directory>${project.build.directory}/jacoco-output/</directory>
                                    <includes>
                                        <include>jacoco-integration-tests.exec</include>
                                        <include>jacoco-unit-tests.exec</include>
<!--                                        <include>*.exec</include>-->
                                    </includes>
                                </fileSet>
                            </fileSets>
                            <destFile>${project.build.directory}/jacoco-output/merged.exec</destFile>
                        </configuration>
                    </execution>
                    <execution>
                        <id>create-merged-report</id>
                        <phase>post-integration-test</phase>
                        <goals>
                            <goal>report</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco-output/merged.exec</dataFile>
                            <outputDirectory>${project.reporting.outputDirectory}/jacoco-merged-test-coverage-report
                            </outputDirectory>
                        </configuration>
                    </execution>
                    <execution>
                        <id>check</id>
                        <goals>
                            <!-- check is bound to the verify phase by default -->
                            <goal>check</goal>
                        </goals>
                        <configuration>
                            <dataFile>${project.build.directory}/jacoco-output/merged.exec</dataFile>
                            <rules>
                                <rule>
                                    <element>CLASS</element>
                                    <excludes>
                                        <exclude>*Test</exclude>
                                        <exclude>configuration/*</exclude>
                                    </excludes>
                                    <limits>
                                        <limit>
                                            <counter>LINE</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>${jacoco.min.line.coverage}</minimum>
                                        </limit>
                                        <limit>
                                            <counter>BRANCH</counter>
                                            <value>COVEREDRATIO</value>
                                            <minimum>${jacoco.min.branch.coverage}</minimum>
                                        </limit>
                                    </limits>
                                </rule>
                            </rules>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

In between you see the commented part - that is the one that changed.

Now this will sometimes throw an EOFException (only when run locally - not when it is exectued on the Gitlab CI/CD pipeline). The file in question is the "jacoco-integration-tests.exec". Looking up that file shows, that it has not been finished writing. It is much smaller than the other file in the folder ("jacoco-unit-tests.exec").

As this bug is not reliably produced (just now it worked fine after I deleted the target folder) we suspect some race condition going on. As if JaCoCo is still writing that integration test file and then the next step already wants to access it and that crashes. But I don't really know and information about this is hard to come by. Less so, how to fix it.

Does anyone know where this bug stems from and how to fix it? Thank you very much.

1

There are 1 best solutions below

0
Bob Fields On
Caused by: java.io.EOFException
at java.io.DataInputStream.readByte (DataInputStream.java:272)
at org.jacoco.core.internal.data.CompactDataInput.readBooleanArray (CompactDataInput.java:64)
at org.jacoco.core.data.ExecutionDataReader.readExecutionData (ExecutionDataReader.java:150)
at org.jacoco.core.data.ExecutionDataReader.readBlock (ExecutionDataReader.java:116)
at org.jacoco.core.data.ExecutionDataReader.read (ExecutionDataReader.java:93)
at org.jacoco.core.tools.ExecFileLoader.load (ExecFileLoader.java:60)
at org.jacoco.core.tools.ExecFileLoader.load (ExecFileLoader.java:74)
at org.jacoco.maven.ReportSupport.loadExecutionData (ReportSupport.java:83)
at org.jacoco.maven.ReportMojo.loadExecutionData (ReportMojo.java:61)
at org.jacoco.maven.AbstractReportMojo.executeReport (AbstractReportMojo.java:191)
at org.jacoco.maven.AbstractReportMojo.execute (AbstractReportMojo.java:180)
at org.apache.maven.plugin.DefaultBuildPluginManager.executeMojo (DefaultBuildPluginManager.java:137)

I hope this is enough to go on. jacoco 0.8.7, Windows 10, JDK 11.0.11. It produces the jacoco.exec file but fails when creating the html report.