diff --git a/.circleci/config.yml b/.circleci/config.yml index 42ccd67f4..ad7717c1e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -143,8 +143,16 @@ commands: ./codecov --version - # Upload the coverage report. - ./codecov upload-coverage $COMMON_ARGS + # Prefer the aggregated multi-module report. + JACOCO_AGG="sqrl-testing/sqrl-coverage/target/site/jacoco-aggregate/jacoco.xml" + if [ -f "$JACOCO_AGG" ]; then + echo "=== Using aggregate JaCoCo report: $JACOCO_AGG ===" + ls -lh "$JACOCO_AGG" + ./codecov upload-coverage $COMMON_ARGS --file "$JACOCO_AGG" + else + echo "=== No aggregate JaCoCo report found; skipping coverage upload ===" + # Avoid uploading empty/partial reports (they can drag down overall coverage). + fi # Upload the test reports. ./codecov do-upload --report-type test_results $COMMON_ARGS @@ -289,15 +297,20 @@ jobs: name: Auth ghcr command: echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin - run: - name: Build with Docker images + name: Build Docker images command: mvn -U clean -T1C -B install -DonlyImages -Ddocker.image.tag=local-${CIRCLE_SHA1} - run: name: Run container tests using TestContainers command: | mvn -B install -DonlyContainerE2E \ -pl :sqrl-testing-container \ + -Dsqrl.container.coverage=true \ -Ddocker.image.tag=local-${CIRCLE_SHA1} \ -Dmcp.inspector.version=$MCP_TAG + - run: + name: Generate aggregate JaCoCo report + command: mvn -B -pl :sqrl-coverage -am -DskipTests -DskipITs verify + - save-test-results deploy: docker: @@ -316,7 +329,7 @@ jobs: echo "pinentry-mode loopback" >> ~/.gnupg/gpg.conf - run: name: Build shaded JAR - command: mvn -U clean -T1C -B deploy -DonlyJars ${CIRCLE_TAG:+-Prelease} -Deasyjacoco.skip=true + command: mvn -U clean -T1C -B deploy -DonlyJars ${CIRCLE_TAG:+-Prelease} -Djacoco.skip=true build-images: # use a full VM so we can run Docker / Buildx diff --git a/.mvn/extensions.xml b/.mvn/extensions.xml deleted file mode 100644 index d477ef5cf..000000000 --- a/.mvn/extensions.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - - - com.marvinformatics.jacoco - easy-jacoco-maven-plugin - 0.1.4 - - diff --git a/CLAUDE.md b/CLAUDE.md index 3d4d64644..178e9f805 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -47,11 +47,11 @@ mvn verify # Coverage report mvn jacoco:report -# Test specific module (ALWAYS include -Deasyjacoco.skip when using -pl) -mvn test -pl sqrl-planner -Deasyjacoco.skip +# Test specific module +mvn test -pl sqrl-planner # Test specific test method in module -mvn test -pl sqrl-tools/sqrl-config -Dtest=TestClassName#testMethodName -Deasyjacoco.skip +mvn test -pl sqrl-tools/sqrl-config -Dtest=TestClassName#testMethodName # Container tests (requires Docker images to be built) mvn -B install -DonlyContainerE2E -pl :sqrl-testing-container -Dit.test=TestClassName diff --git a/pom.xml b/pom.xml index 7cc657dd0..eac20212f 100644 --- a/pom.xml +++ b/pom.xml @@ -172,7 +172,6 @@ 1.4.13 - 0.1.4 5.4 5.0.0 3.2.8 @@ -855,28 +854,56 @@ org/** - - - - com.marvinformatics.jacoco - easy-jacoco-maven-plugin - 0.1.4 - - - - - - INSTRUCTION - COVEREDRATIO - 0.70 - - - - - - true - - + + + prepare-agent + + prepare-agent + + + + prepare-agent-integration + + prepare-agent-integration + + + + report + + report + + verify + + + report-integration + + report-integration + + verify + + + check + + check + + verify + + false + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + 0.70 + + + + + + + diff --git a/sqrl-cli/pom.xml b/sqrl-cli/pom.xml index 0708a5406..da0656388 100644 --- a/sqrl-cli/pom.xml +++ b/sqrl-cli/pom.xml @@ -442,36 +442,33 @@ instrument - - - org.jacoco - org.jacoco.agent - ${jacoco.version} - runtime - - - - com.marvinformatics.jacoco - easy-jacoco-maven-plugin - ${easy-jacoco-maven-plugin.version} - - - instrument-uber-jar - - instrument-jar - - - ${project.build.directory}/sqrl-cli.jar - ${project.build.directory}/sqrl-cli.jar - - com/datasqrl/* - - - - + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + instrument-uber-jar + + instrument + + package + + + com/datasqrl/** + + + + + restore-instrumented-classes + + restore-instrumented-classes + + verify + + diff --git a/sqrl-server/sqrl-server-vertx/pom.xml b/sqrl-server/sqrl-server-vertx/pom.xml index 6ce823190..86ab6acda 100644 --- a/sqrl-server/sqrl-server-vertx/pom.xml +++ b/sqrl-server/sqrl-server-vertx/pom.xml @@ -91,23 +91,29 @@ - com.marvinformatics.jacoco - easy-jacoco-maven-plugin - ${easy-jacoco-maven-plugin.version} + org.jacoco + jacoco-maven-plugin + ${jacoco.version} instrument-uber-jar - instrument-jar + instrument + package - ${project.build.directory}/vertx-server.jar - ${project.build.directory}/vertx-server.jar - com/datasqrl/* + com/datasqrl/** + + restore-instrumented-classes + + restore-instrumented-classes + + verify + diff --git a/sqrl-testing/pom.xml b/sqrl-testing/pom.xml index 6e8e43eaf..77ea55961 100644 --- a/sqrl-testing/pom.xml +++ b/sqrl-testing/pom.xml @@ -30,6 +30,7 @@ sqrl-testing-integration + sqrl-coverage diff --git a/sqrl-testing/sqrl-coverage/pom.xml b/sqrl-testing/sqrl-coverage/pom.xml new file mode 100644 index 000000000..b5a80d3a8 --- /dev/null +++ b/sqrl-testing/sqrl-coverage/pom.xml @@ -0,0 +1,108 @@ + + + + 4.0.0 + + + com.datasqrl + sqrl-testing + 1.0-SNAPSHOT + + + sqrl-coverage + SQRL :: Coverage + pom + + + + + com.datasqrl + sqrl-cli + ${project.version} + + + com.datasqrl + sqrl-planner + ${project.version} + + + com.datasqrl + sqrl-discovery + ${project.version} + + + com.datasqrl + sqrl-server-vertx-base + ${project.version} + + + + + + + org.jacoco + jacoco-maven-plugin + + + report-aggregate + + report-aggregate + + verify + + + **/target/jacoco.exec + **/target/jacoco-it.exec + **/target/jacoco/jacoco-container.exec + + + + + + + + + + + + include-container-testing + + + com.datasqrl + sqrl-testing-container + ${project.version} + + + + + + dev + + + com.datasqrl + sqrl-testing-container + ${project.version} + + + + + diff --git a/sqrl-testing/sqrl-testing-container/src/test/java/com/datasqrl/container/testing/SqrlContainerTestBase.java b/sqrl-testing/sqrl-testing-container/src/test/java/com/datasqrl/container/testing/SqrlContainerTestBase.java index d792d215f..f4147b5f9 100644 --- a/sqrl-testing/sqrl-testing-container/src/test/java/com/datasqrl/container/testing/SqrlContainerTestBase.java +++ b/sqrl-testing/sqrl-testing-container/src/test/java/com/datasqrl/container/testing/SqrlContainerTestBase.java @@ -71,6 +71,8 @@ protected void commonTearDown() { protected static final String SQRL_CMD_IMAGE = "datasqrl/cmd"; protected static final String SQRL_SERVER_IMAGE = "datasqrl/sqrl-server"; protected static final String BUILD_DIR = "/build"; + protected static final String JACOCO_OUT_DIR = "/jacoco"; + protected static final String JACOCO_AGENT_PATH = "/jacoco-agent.jar"; protected static final int HTTP_SERVER_PORT = 8888; protected static Network sharedNetwork; @@ -115,6 +117,8 @@ protected GenericContainer createCmdContainer(Path workingDir, boolean debug) .withFileSystemBind(workingDir.toString(), BUILD_DIR, BindMode.READ_WRITE) .withEnv("TZ", "America/Los_Angeles"); + container = configureJacocoCoverage(container, "cmd"); + if (debug) { container = container.withEnv("SQRL_DEBUG", "1"); } @@ -127,14 +131,19 @@ protected GenericContainer createServerContainer(Path workingDir) { var deployPlanPath = workingDir.resolve("build/deploy/plan"); assertThat(deployPlanPath).exists().isDirectory(); - return new GenericContainer<>(DockerImageName.parse(SQRL_SERVER_IMAGE + ":" + getImageTag())) - .withNetwork(sharedNetwork) - .withExposedPorts(HTTP_SERVER_PORT) - .withFileSystemBind(deployPlanPath.toString(), "/opt/sqrl/config", BindMode.READ_ONLY) - .withEnv("SQRL_DEBUG", "1") - .waitingFor( - Wait.forLogMessage(".*HTTP server listening on port 8888.*", 1) - .withStartupTimeout(Duration.ofSeconds(30))); + var container = + new GenericContainer<>(DockerImageName.parse(SQRL_SERVER_IMAGE + ":" + getImageTag())) + .withNetwork(sharedNetwork) + .withExposedPorts(HTTP_SERVER_PORT) + .withFileSystemBind(deployPlanPath.toString(), "/opt/sqrl/config", BindMode.READ_ONLY) + .withEnv("SQRL_DEBUG", "1") + .waitingFor( + Wait.forLogMessage(".*HTTP server listening on port 8888.*", 1) + .withStartupTimeout(Duration.ofSeconds(30))); + + container = configureJacocoCoverage(container, "server"); + + return container; } protected void compileSqrlProject(Path workingDir) { @@ -290,6 +299,75 @@ private String getImageTag() { return System.getProperty("docker.image.tag", "local"); } + @SneakyThrows + private GenericContainer configureJacocoCoverage(GenericContainer container, String component) { + if (!Boolean.parseBoolean(System.getProperty("sqrl.container.coverage", "false"))) { + return container; + } + + var jacocoAgentJar = findJacocoAgentJar(); + if (jacocoAgentJar == null) { + log.warn("Container coverage enabled but JaCoCo agent jar not found in ~/.m2; skipping"); + return container; + } + + var jacocoOutDir = Paths.get("target", "jacoco").toAbsolutePath(); + var hostDestFile = jacocoOutDir.resolve("jacoco-container.exec"); + Files.createDirectories(jacocoOutDir); + + var containerDestFile = JACOCO_OUT_DIR + "/jacoco-container.exec"; + var agentArg = + "-javaagent:" + + JACOCO_AGENT_PATH + + "=destfile=" + + containerDestFile + + ",append=true,excludes=org/**"; + + var toolOptions = agentArg; + + log.info( + "Enabling container JaCoCo for {}: agent={} destfile={}", + component, + jacocoAgentJar, + hostDestFile); + + return container + .withFileSystemBind(jacocoOutDir.toString(), JACOCO_OUT_DIR, BindMode.READ_WRITE) + .withFileSystemBind(jacocoAgentJar.toString(), JACOCO_AGENT_PATH, BindMode.READ_ONLY) + .withEnv("JAVA_TOOL_OPTIONS", toolOptions); + } + + @Nullable + private Path findJacocoAgentJar() { + var m2 = Paths.get(System.getProperty("user.home"), ".m2", "repository"); + var jacocoDir = m2.resolve(Paths.get("org", "jacoco", "org.jacoco.agent")); + if (!Files.exists(jacocoDir)) { + return null; + } + + try (var stream = Files.list(jacocoDir)) { + var versions = + stream + .filter(Files::isDirectory) + .map(p -> p.getFileName().toString()) + .sorted() + .toList(); + for (int i = versions.size() - 1; i >= 0; i--) { + var version = versions.get(i); + var candidate = + jacocoDir.resolve( + Paths.get(version, "org.jacoco.agent-" + version + "-runtime.jar")); + if (Files.exists(candidate)) { + return candidate; + } + } + return null; + } catch (Exception e) { + log.warn("Failed to locate JaCoCo agent jar under {}", jacocoDir, e); + return null; + } + } + protected String getDockerRunCommand(GenericContainer container, Path workingDir) { var sb = new StringBuilder(); sb.append("docker run -it --rm"); diff --git a/sqrl-testing/sqrl-testing-integration/pom.xml b/sqrl-testing/sqrl-testing-integration/pom.xml index 5bd33ceae..6c7d9f20d 100644 --- a/sqrl-testing/sqrl-testing-integration/pom.xml +++ b/sqrl-testing/sqrl-testing-integration/pom.xml @@ -142,4 +142,5 @@ test +