I am packaging my Spring Boot App with the Spring Boot gradle plugin.

tasks.bootBuildImage {
    builder.set("paketobuildpacks/builder-jammy-full:latest")
    imageName.set(devImageName)
    val systemParams = listOf(
        "-Dfile.encoding=UTF-8",
        "-Djava.awt.headless=true",
        "-Djava.net.preferIPv4Stack=true",
        "-XX:-OmitStackTraceInFastThrow",
        "-XX:+HeapDumpOnOutOfMemoryError"
    )
    val gcParams = listOf(
        "-XX:+UnlockExperimentalVMOptions",
        "-XX:+UseZGC",
        "-XX:+UseLargePages",
        "-XX:+DisableExplicitGC"
    )
    val allParams = (systemParams + gcParams).joinToString(" ")
    environment.putAll(
        mapOf(
            "BPE_DELIM_JAVA_TOOL_OPTIONS" to " ",
            "BPE_APPEND_JAVA_TOOL_OPTIONS" to allParams,
        )
    )
}

After building this image I run a lot of End-to-End Tests with Cypress and Playwright. this works fine. The App itself is started as a docker container together with all other needed components like Database, Loadbalancer and so on. so I can relay test the final setup. And Cypress/Playwright itself run as a docker container.

But now I want to measure code coverage.

How can I add the Jacoco Lib to the Spring Boot Image and let it only activate when a given property is set?

I found the Tanzu Jacoco Build Pack. Is this the right way?

4 Replies 4

I found the Tanzu Jacoco Build Pack. Is this the right way?

If you're a Tanzu/ Broadcom customer, maybe, otherwise it's not an OSS component, so no.

And it seems like Paketo Buildpacks do not have a Jacoco buildpack, so...let's rather consider a workaround.

Running JaCoCo in "production" is just a matter of attaching the JaCoCo agent to the running JVM, something along the lines of:

$ java -javaagent:jacocoagent.jar=address=localhost,includes="*",port=36320,destfile=jacoco.exec,output=tcpserver -jar build/libs/my-app-0.0.1-SNAPSHOT.jar

You already know how to pass options to the JVM for the runtime, so now you need to figure out how to ship the agent and retrieve the jacoco.exec file.

The easiest way would be to mount a volume (or several) to your Spring Boot app at runtime, a read/write volume where your jacocoagent.jarwould sit and where you jacoco.exec would be written to.

Hope that makes sense, if you're having issues, come and ask the Paketo Community on Slack

Wonderful. That helped me very much.

I have now a separate "playwright" module with this build.gradle.kts:

plugins {
    base
    java
    jacoco
}

repositories {
    mavenCentral()
}
dependencies {
    runtimeOnly("org.jacoco:org.jacoco.agent:0.8.14:runtime")
}

val jacocoDir = layout.buildDirectory.dir("logs/jacoco")

tasks.named("check") {
    dependsOn("playwright")
    // report is always generated after tests run
    finalizedBy(tasks.jacocoTestReport)
}

tasks.register<Exec>("playwright") {
    dependsOn(":backend:bootBuildImage")
    dependsOn("copyJacocoLib")
    inputs.dir("./")
    commandLine("./run-docker.sh")
}

tasks.register<Copy>("copyJacocoLib") {
    from(configurations.runtimeClasspath)
    into(jacocoDir)
    include("org.jacoco.agent-*-runtime.jar")
}

jacoco {
    toolVersion = "0.8.14"
    reportsDirectory = jacocoDir
}

tasks.jacocoTestReport {
    executionData = files("${jacocoDir.get()}/jacoco.exec")
    sourceSets(project(":kicktipp-backend").sourceSets.main.get())
    reports {
        html.required = true
        csv.required = true
        xml.required = true
    }
}

And a compose file similar to this:

services:
  app:
    image: backend:dev
    volumes:
      - .${JACOCO_DIR}:${JACOCO_DIR}
    environment:
      - JAVA_TOOL_OPTIONS=-javaagent:${JACOCO_LIB}=destfile=${JACOCO_REPORT}
    healthcheck:
      test: [ "CMD", "curl", "-f", "http://localhost:8080/" ]
      start_period: 10s
      start_interval: 1s
      retries: 10

  playwright:
    image: mcr.microsoft.com/playwright:v1.52.0-noble
    # https://playwright.dev/docs/docker#recommended-docker-configuration
    init: true
    ipc: host
    user: ubuntu:ubuntu
    volumes:
      - ../:/app
    working_dir: /app/kicktipp-playwright
    command: bash -c "npm ci && npx playwright test"
    depends_on:
      app:
        condition: service_healthy

an .env file:

JACOCO_DIR=/build/logs/jacoco
JACOCO_VERSION=0.8.14
JACOCO_LIB=${JACOCO_DIR}/org.jacoco.agent-${JACOCO_VERSION}-runtime.jar
JACOCO_REPORT=${JACOCO_DIR}/jacoco.exec

And my docker-run.sh

#!/bin/bash

cd $(dirname $0)
set -e
LOG_DIR="build/logs"
LOG_FILE="$LOG_DIR/docker.log"
CONTAINER="playwright-playwright-1"
mkdir -p $LOG_DIR $LOG_DIR/playwright
trap '{
  docker logs --tail 100 $CONTAINER
}' ERR
docker compose --ansi never up --build --exit-code-from playwright >$LOG_FILE 2>&1

And of course a usual playwright configuration.

Not perfect yet, but it works and shows the coverage in gitlab:

build:
  stage: build
  script:
    - ./gradlew build
    - awk -F"," '{ instructions += $4 + $5; covered += $5 } END { print covered, "/", instructions, " instructions covered"; print 100*covered/instructions, "% covered" }' playwright/build/logs/jacoco/test/jacocoTestReport.csv
  coverage: /([0-9]{1,3}.[0-9]*).%.covered/

Better ways to do it but just for further reference if someone needs a code example to get it running

Hey Janning Vygen , I'm a Product Manager at Stack Overflow. We're gathering feedback on the new question type you used to ask this. If you have a moment, we'd love to hear how your experience was or what you would improve.

Havn't thought about it really. But I guess I like the question/anwser model more maybe because i am used to it.

But anwsering on your quesion was quite difficult, had to scroll to the top and click "Join the conersation".

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.