# Recording Test Coverage for Java with Docker

This how-to will show you how to set up test coverage recording for a dockerized Java application or any other language running on the Java Virtual Machine (JVM), so you can use the Test Gap analysis. The following graphic gives a quick overview over the final setup:

Overview Over the Setup

Coverage is recorded using the Teamscale JaCoCo Agent, which is a Java agent that can attach to any JVM to record the lines of code executed during any test (manual or automated). The agent also handles the automatic upload of the recorded test coverage to Teamscale on shutdown of the JVM or in configurable time intervals.

# Prerequisite: Generate git.properties

The agent needs to be able to find a git.properties file, which contains the Git SHA1 of the commit that was used to build your Docker image. You can easily generate such a file and include it in your Jar/War/Ear/... files by using the corresponding Maven or Gradle build plugin.

# Prerequisite: Adjust Your Dockerfile

The agent sends the coverage it collected when the JVM inside your Docker container is shut down. This requires that the JVM receives the SIGTERM signal sent by your orchestration tooling. Thus, you'll need to ensure that the JVM is the main process inside the docker image. This can be achieved by using exec to replace the shell process with your JVM process:

# this will not work:
CMD java -jar /your.jar

# use this instead:
CMD exec java -jar /your.jar

The same applies if you're using ENTRYPOINT instead of CMD:

# this will not work:
ENTRYPOINT java -jar /your.jar

# use this instead:
ENTRYPOINT exec java -jar /your.jar

If your application is wrapped inside a start script, you'll likewise need to ensure that you use exec to replace the shell process with the JVM process.

Testing if Your JVM Is the Main Process

To list all processes inside a running Docker container (e.g. named determined_dijkstra), run

docker container top determined_dijkstra

Identify the PID of your java process. Then run the following for your container to obtain the main PID of the container:

docker inspect -f '{{.State.Pid}}' determined_dijkstra

Your java process's PID must be reported as the main PID by this command.

# Deploy the Agent

You'll need to deploy the cqse/teamscale-jacoco-agent image next to your own Docker images. This image will do nothing by itself, it simply provides the agent's Jar file to your containers so you don't have to include the agent in your images.

To make the agent available to your JVM, you'll need to mount the /agent volume of the cqse/teamscale-jacoco-agent container into every Docker container for which you'd like to record coverage. This volume contains the agent's jar file.

# Configure the Agent

The agent expects a configuration file as its input. You'll have to include it in your Docker image. Teamscale has a wizard to generate this config file for you. Include this file in your Docker images in a well-known path, e.g. /jacocoagent.properties.

Don't Store the Configuration File Under /agent

It would then be overwritten by the volume mount you configured above.

Advanced Agent Configuration

For a list of all agent configuration options, please refer to the agent's documentation.

# Activate the Agent

In order to put all parts together and activate the agent for your application, you'll need to set a JVM argument. This is most often done via the environment variable JAVA_TOOL_OPTIONS, which is automatically picked up by the JVM itself. However, if your setup overrides JAVA_TOOL_OPTIONS, e.g. in a start script, you may need to use a different environment variable that is picked up by your start scripts (e.g. JAVA_OPTS is used by many frameworks). The agent's documentation contains detailled instructions for many web application servers.

JAVA_TOOL_OPTIONS="-javaagent:/agent/teamscale-jacoco-agent.jar=config-file=/jacocoagent.properties"

You'll need to set this environment variable in your Docker orchestration tooling (e.g. Docker Compose, Helm, ECS, Kubernetes).

Now you can restart your container and the agent will record coverage and upload it to Teamscale in regular intervals.

To Quickly Test Whether the Setup Worked

Simply restart your container and check the external uploads page of your project in the projects perspective. A new upload should be shown there from your test environment.

Debugging Setup Problems

The agent by default logs to /logs/teamscale-jacoco-agent.log inside your Docker container. If the setup does not work as expected, please have a look at the problems reported in this log file, e.g. by running:

docker exec -it YOUR_CONTAINER_ID cat /logs/teamscale-jacoco-agent.log | less

# Example: Docker Compose 3

version: '3'
services:
  yourapp:
    image: your/image
    # activate the agent by setting an appropriate environment variable
    environment:
      JAVA_TOOL_OPTIONS: "-javaagent:/agent/teamscale-jacoco-agent.jar=config-file=/jacocoagent.properties"
    # use the agent's volume
    volumes:
      - agent-jar:/agent:ro
  agent:
    image: cqse/teamscale-jacoco-agent:16.0.1
    # make the agent's volume available
    volumes:
      - agent-jar:/agent:ro

# declares the agent's volume
volumes:
  - agent-jar: