Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I take advantage of CircleCI parallelism in my Java / Maven / Surefire project?

I have a Java project that uses Maven and the maven-surefire-plugin to run JUnit 4 tests. I'm building with CircleCI. How can I enable parallelism so that my test suite runs faster?

I want to use the CircleCI parallelism, not Surefire fork and parallel execution options.

like image 849
JBCP Avatar asked Sep 24 '14 02:09

JBCP


People also ask

Does surefire run tests in parallel?

The surefire offers a variety of options to execute tests in parallel, allowing you to make best use of the hardware at your disposal. But forking in particular can also help keeping the memory requirements low.

What is parallelism in CircleCI?

circleci/config. yml file. The parallelism key specifies how many independent executors are set up to run the steps. To run a job's steps in parallel, set the parallelism key to a value greater than 1.

Does maven run unit tests in parallel?

Maven Dependencies In a nutshell, Surefire provides two ways of executing tests in parallel: Multithreading inside a single JVM process. Forking multiple JVM processes.

Can I build a Java application with Maven on CircleCI?

This guide will help you get started with a Java application building with Maven on CircleCI. This is an example application showcasing how to run a Java app on CircleCI 2.1. This application uses the Spring PetClinic sample project.

How to build and test Java projects using Maven?

Here we are using the maven orb, which simplifies building and testing Java projects using Maven. The maven/test command checks out the code, builds, tests, and uploads the test result. The parameters of this command can be customized.

How do I follow a GitHub project in CircleCI?

If you want to step through it yourself, you can fork the project on GitHub and download it to your machine. Go to the Projects page in CircleCI and click the Follow Project button next to your forked project. Finally, delete everything in .circleci/config.yml.

How do I follow a forked project in CircleCI?

Go to the Projects page in CircleCI and click the Follow Project button next to your forked project. Finally, delete everything in .circleci/config.yml. Nice!


1 Answers

The maven-surefire-plugin supports its doesn't support parallelism, at least not in isolated fashion CircleCI supports (separate nodes for each test execution).

However, you can manually enable CircleCI-style parallelism using two methods:

  1. Use a shell script to select the tests to run per-node, and then using the -Dtest parameter.
  2. Custom JUnit 4 TestRule

Shell Script

Create a bin directory in your project, if you don't already have one.

In bin, create a shell script in your project called test.sh, with the following contents

#!/bin/bash

NODE_TOTAL=${CIRCLE_NODE_TOTAL:-1}
NODE_INDEX=${CIRCLE_NODE_INDEX:-0}

i=0
tests=()
for file in $(find ./src/test/java -name "*Test.java" | sort)
do
  if [ $(($i % ${NODE_TOTAL})) -eq ${NODE_INDEX} ]
  then
    test=`basename $file | sed -e "s/.java//"`
    tests+="${test},"
  fi
  ((i++))
done

mvn -Dtest=${tests} test

This script will search your src/test/java directory for all files ending in Test.java, and add them to the -Dtest parameter as a comma separated list, then call maven.

To enable your new test script, put the following in your circle.yml file:

test:
  override:
    - ./bin/test.sh:
        parallel: true

Things to note:

  1. You may need to customize this script if your filenames don't follow this naming convention, your files are located somewhere else, or you need to run a different lifecycle phase.
  2. If you have a very many number of tests, you may find your -Dtest parameter exceeds the maximum length of the Linux command line.

Junit4 TestRule

You can use a custom TestRule to do something similar to the above in Java code. This has the advantage of less CircleCI customized-configuration, but imposes some assumptions about CircleCI on your Java framework.

import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.StringUtils;
import org.junit.Assume;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

@Slf4j
final class CircleCiParallelRule implements TestRule {
    @Override
    public Statement apply(Statement statement, Description description) {

        boolean runTest = true;

        final String tName = description.getClassName() + "#" + description.getMethodName();

        final String numNodes = System.getenv("CIRCLE_NODE_TOTAL");
        final String curNode = System.getenv("CIRCLE_NODE_INDEX");

        if (StringUtils.isBlank(numNodes) || StringUtils.isBlank(curNode)) {
            log.trace("Running locally, so skipping");
        } else {
            final int hashCode = Math.abs(tName.hashCode());

            int nodeToRunOn = hashCode % Integer.parseInt(numNodes);
            final int curNodeInt = Integer.parseInt(curNode);

            runTest = nodeToRunOn == curNodeInt;

            log.trace("currentNode: " + curNodeInt + ", targetNode: " + nodeToRunOn + ", runTest: " + runTest);

            if (!runTest) {
                return new Statement() {
                    @Override
                    public void evaluate() throws Throwable {
                        Assume.assumeTrue("Skipping test, currentNode: " + curNode + ", targetNode: " + nodeToRunOn, false);
                    }
                };
            }
        }

        return statement;
    }
}

(Note I am using Project Lombok (log instantiation) and Apache Commons-Lang (for StringUtils) in the above code, but these can easily be eliminated if necessary.

To enable this, in your test baseclass you can do this to balance on a test-by-test basis:

// This will load-balance across multiple CircleCI nodes
@Rule public CircleCiParallelRule className = new CircleCiParallelRule();

Or if you want to balance class-by-class, you can do this:

// This will load-balance across multiple CircleCI nodes
@ClassRule public CircleCiParallelRule className = new CircleCiParallelRule();
like image 169
JBCP Avatar answered Oct 31 '22 18:10

JBCP