Working with Distributed Applications

Introduction

In some cases the application you wish to test has many components running on separate nodes in a network, or even on disconnected machines. You can use Clover to test such applications, although some additional configuration is required. This page describes how to configure Clover in order to get a per-test code coverage for distributed business logic.

 

(warning) Please note that having an application deployed on multiple machines does not necessarily mean that the application logic is truly distributed. For instance, an application might run on multiple machines for the sake of load balancing, but do not have communication between nodes. We recommend to read the Using Clover in various environment configurations tutorial and especially to have a look at the "Decision matrix" which can help you to decide which approach would best fit your needs.

(warning) When deploying your application in container environments, you should also check to ensure that Clover has sufficient permissions to function.

(warning) In case you use Maven, ensure that you're deploying an instrumented version of an application archive. If you use "clover2:setup" then the WAR/EAR is named as usual, but if you use "clover2:instrument" then file has a "-clover" suffix and is usually located in the target/clover directory.

 

On this page:

Overview

In Clover Distributed Coverage we have two main machine roles:

Clover Server - is a JVM in which Clover sends "test start" and "test end" events in order to inform Clover Clients about test boundaries; Clover opens a port on which it waits for Clover Clients to connect

Clover Client - is a JVM in which Clover will connect to Clover Server

(lightbulb) Please note that Clover's Server/Client designation is actually unrelated with your application structure.

(lightbulb) Both Clover Server and Clover Client write coverage files to disk.

 

Clover Server is the place where you will typically execute unit tests (or integration tests). These tests will call application logic on Clover Client #N machines. 

If unit tests (or integration tests) are executed by a build script, then the Build Server actually performs a role of the Clover Server.

Report Server is the place where Clover reports are generated - it can be the same physical machine as Build Server, of course.

 

 

The diagram above shows a simplified configuration used in the WebApp example. There are only two JVMs used:

  • 1st one is a Maven build, which instantiates a container using Cargo Maven Plugin, deploys compiled application and clover.jar, executes unit tests with "-Dclover.server=true" option (Clover records coverage of test classes) and finally creates a report
  • 2nd one is a Tomcat container, where instrumented application is running, Clover listens to "test start/end" evens to record per-test coverage (in addition to the global coverage)

 

Collecting overall coverage from distributed builds

The first step in setting up coverage from distributed builds is to configure Clover for overall coverage reporting.

Step 1: Understanding the Clover 'initstring'

At build time, Clover constructs a registry of your source code, and writes it to a file at the location specified in the Clover initialization string (initstring). When Clover-instrumented code is executed (e.g. by running a suite of unit tests), Clover looks in the same location for this registry file to initialize itself. Clover then records coverage data and writes coverage recording files next to the registry file during execution. See Clover Database Structure for more information.

Step 2: Choosing a location for the Clover registry

If you are deploying and running your Clover-instrumented code on different machines, you must provide a way for Clover to find the registry file, and provide a place for Clover to write coverage recording files; otherwise no coverage will be recorded.

Clover provides three different ways to achieve this:

  1. Specify aninitstringthat is a globally accessible file path
    The compile-time initstring should be an absolute path to the same filesystem location, and be accessible and writable from the build machine and all execution machines. This could be a path on a shared drive or filesystem.
    OR:
  2. Specify aninitstringthat is a relative path, resolved at runtime
    The compile-time initstring represents a relative path (relative to the current working directory of each execution context). To do this you need to specify relative="yes" on the <clover-setup> task.
    OR:
  3. Specify aninitstringat runtime via system properties
    You can override the Clover initstring at runtime via system properties. Two (three?) system properties are supported:

    To set one of these properties, you need to pass it on the command line when Java is launched, using the -Dparameter:

    clover.initstring
    If not null, the value of this property is treated as an absolute file path to the Clover registry file. 

    clover.initstring.basedir 
    If not null (and the clover.initstring systyem property is not set), the value of this property is used as the base directory for the file specified at compile-time in the initstring to resolve the full path to the Clover registry.

    clover.initstring.prefix 
    If not null (and the clover.initstring or clover.initstring.basedir system properties are not set), the value of this property is prepended to the string value of compile-time specified initstring to resolve the full path to the Clover registry.

    java -Dclover.initstring=... myapplication.Server
    

    For application servers, this may involve adding the property to a startup script or batch file.

    For methods two and three in the sequence above, you will need to copy the Clover registry file from the location on the build machine to the appropriate directory on each of the execution machines (as part of the test deployment process).
    This needs to occur:
    a. after the Clover build is complete, and
    b. before you run your tests.

    Once test execution is complete, you will need to copy the coverage recording files from each remote machine to the initstring path on the build machine in order to generate coverage reports.

 

Step 3: Set your classpath correctly

You must put clover.jar (or the appropriate Clover plugin jar) in the classpath for any JVM that will load classes that have been instrumented by Clover. How you go about this depends on the nature of the application you are testing and the environment you are deploying to.

In some cases, the clover.jar must be on the classpath of the actual webserver, not just on the classpath of the webapp that is instrumented. This is to ensure Clover can properly flush its coverage data when the JVM of the webserver is shutdown.

Collecting per-test coverage from distributed builds

(info) The steps below require you to have carried out the previous steps on this page (related to 'Collecting Overall Coverage from Distributed Builds').

Enabling or disabling distributed coverage at runtime

Clover's distributed coverage feature is enabled at runtime by making use of command-line options.

This can be done without the need for re-instrumentation or compilation of source files.

Enabling distributed coverage
Distributed coverage can be enabled via setting this System property:

 -Dclover.distributed.coverage=on

This will enable distributed coverage with default settings (host=localhost, port=1198, timeout=5000ms, numClients=0, retryPeriod=1000ms, name=clover.tcp.server).

 

In case when you cannot use default settings, you can pass specific value for any of attributes using the "key=value" syntax passed as clover.distributed.coverage value:

  • host - host name of the "Clover Server"
  • port - port on which the Clover will listen
  • numClients - number of "Clover Clients" to connect until server starts test execution
  • timeout - connection timeout in milliseconds
  • retryPeriod - inverval between connection retries in milliseconds
  • name - name of the Clover server service (URL is host:port/name)

Example:

 -Dclover.distributed.coverage=host=myhost;port=7777;numClients=2

 


Clover also needs to know which JVM is hosting your unit tests ("Clover Server"), by providing the following system property:

 -Dclover.server=true 


Disabling distributed coverage
Distributed coverage can be disabled by setting this system property on either the Test or the Application JVM:

-Dclover.distributed.coverage=off 

This will turn off distributed coverage for the JVM in which this is set, regardless of what was instrumented.

For more configuration options and how to do this in Ant and Maven, see the Clover-for-Ant and Clover-for-Maven 2 documentation.

 

Configuration complete
Distributed Per-Test Coverage in Clover will now operate when running distributed builds. Detailed reports will now be available.

 

Troubleshooting

Server does not wait for clients, despite having numClients != 0 in build configuration

Do not use -Dclover.distributed.coverage=on runtime option if numClients!=0 was set in instrumentation. The clover.distributed.coverage provided at runtime will override numClients setting from instrumentation, setting it to 0.

As a consequence, your tests on server will start immediately, without waiting for clients to connect. It can result in lower or zero coverage.

Instead of this:

  • enable clover.distributed.coverage option in build file or
  • use -Dclover.distributed.coverage=numClients=N (where N is a number >= 0) at runtime

 

Execution of tests hangs when numClients != 0

Server-client dependency loop

It can happen that your "Server" will wait for "Clients" to connect, while Clients will wait until Server starts unit test execution - it depends on how tests are written.

This is a typical case for web applications running in container (like Tomcat, JBoss), when your unit test calls a servlet class (e.g. via HTTP request). The issue is as follows:

  • unit tests on <<server>> are waiting until all clients are connected (numClients != 0) but
  • none of the clients will connect until a servlet class is loaded in the container, which happens only when the first HTTP request comes (and it will not come, due to the point above)

In order to avoid this circular dependency you have to:

  • create a servlet context listener (and instrument it by Clover)
    • the class can do virtually nothing
    • the listener class will be automatically loaded by container at application deployment (without waiting for any web request)
    • as soon as class is loaded by classloader, it will automatically connect Clover recorder instance to the "Clover server"; Clover server will start its execution


Sample code

package com.my.webapp;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class MyServletContextListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent servletContextEvent) {
        System.out.println("Web App Initialized");
    }
    @Override
    public void contextDestroyed(ServletContextEvent servletContextEvent) {
        System.out.println("Web App Destroyed");
    }
}
<web-app>
    <!-- ... --> 
    <listener>
        <listener-class>com.my.webapp.MyServletContextListener</listener-class>
    </listener>
    <!-- ... -->
</web-app>

 

Full code example is available on Bitbucket: https://bitbucket.org/atlassian/maven-clover2-plugin (src/it/webapp).

 

Last modified on Jun 30, 2017

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.