Archive

Groovy

We have found it useful to run our static analysis tools as part of our JUnit test suite, so that developers have a single task and IDE integration for code validation before a check-in, rather than having to run multiple tasks and tools. We first used that approach with CodeNarc, for analyzing our Groovy code. We also wanted the same integration for Java source code using PMD.

We use Groovy extensively for our automated tests, and we take advantage of Groovy’s integration with Ant using the Groovy AntBuilder class. That allows us to use the PMD Ant Task to execute PMD. Note that though the test class is in Groovy, the source code that we are analyzing is in Java.

1. Add PMD and Groovy-Ant Jars to the Classpath

First you need to add the PMD and Groovy-Ant jars to your project classpath. This is an excerpt from build.gradle for Gradle:

dependencies {
    ...
    // Support for PMD Ant Task 
    testCompile 'org.codehaus.groovy:groovy-ant:2.4.13'
    testCompile 'net.sourceforge.pmd:pmd-java:6.0.0'
}

2. Create a Groovy JUnit Test to Execute the PMD Ant Task

Here is the Groovy JUnit test class that executes PMD against our Java source code:

package sandbox

import java.util.logging.LogManager
import org.junit.Test

/**
 * JUnit test to run PMD against the project Java source.
 */ 
class RunPmdTest {

    private static final String RULESET_FILE = 'pmd-ruleset.xml'

    @Test
    void runPmd() {
        // Disable internal PMD logging (Java Util Logging)
        Logger.getLogger('net.sourceforge.pmd.cache')
            .setLevel(Level.SEVERE)

        def ant = new AntBuilder()
        ant.taskdef(name:'pmd', 
            classname:'net.sourceforge.pmd.ant.PMDTask')

        ant.pmd(shortFilenames:'true',
            cachelocation:'build/cache/pmd/pmd.cache', 
            failonerror:'true', failOnRuleViolation:'true') {

            sourceLanguage(name:'java', version:'1.8')

            ruleset(RULESET_FILE)

            formatter(type:'text', toConsole:'true')

            fileset(dir:'src/main/java') {
                include(name:'**/*.java')
            }
        }
    }
}

 Some things to note:

  • This test will fail if there are any PMD violations.
  • This example is based on a Gradle project. You may need to adjust the paths (e.g. “src/main/java” or “build/cache/pmd”), especially if you using this within a non-Gradle project.
  • In this example, the PMD results are only written to the console. You can add other formatters to write additional report types (HTML, XML, etc.). See the PMD Ant Task.
  • PMD logging is programmatically configured to streamline the PMD output.

3. Create the PMD Ruleset File

This is the “pmd-ruleset.xml” PMD ruleset file, located on the classpath:

<?xml version="1.0"?>
<ruleset name="Custom ruleset"
 xmlns="http://pmd.sourceforge.net/ruleset/2.0.0"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://pmd.sourceforge.net/ruleset/2.0.0
 http://pmd.sourceforge.net/ruleset_2_0_0.xsd">
 
 <description>Custom PMD RuleSet</description>
 
 <rule ref="category/java/bestpractices.xml/AvoidPrintStackTrace" />
 <rule ref="category/java/bestpractices.xml/UnusedImports" />
 <rule ref="category/java/bestpractices.xml/UnusedLocalVariable" />
 <rule ref="category/java/bestpractices.xml/UnusedPrivateField" />
 <rule ref="category/java/bestpractices.xml/UnusedPrivateMethod" />
</ruleset>

This is just a simple example ruleset. You will want to choose your own set of PMD rules. See the list of Java PMD rules.

We have a large Java/Groovy application with a JUnit test suite containing thousands of tests, that now takes several minutes to run. We wanted to try to reduce the test suite run time, and wanted a report of test times sorted longest to shortest, allowing us to focus on the worst offenders.

We used the JUnit RunListener facility to calculate the test run times and then sort and display a report at the end of the test suite. Specify the test suite class name in the testSuite property (field).

This class is implemented in Groovy, but the Java version would be very similar.


import org.junit.runner.Description
import org.junit.runner.JUnitCore
import org.junit.runner.Result
import org.junit.runner.notification.RunListener

/**
* Run a JUnit test suite and build a list of all tests (test methods) sorted
* by run time, in descending order. Run this class as a Java Application.
* Specify the test suite class in the testSuite property (field).
*/
class RunTestSuiteTimingReport {
    private static Class testSuite = MyTestSuite
    private static long showTestTimesGreaterThanOrEqualToMilliseconds = 1000L

    private static class TimingListener extends RunListener {
        private Map testStartTimes = [:]
        private Map testRunTimes = [:]

        @Override
        void testStarted(Description description) throws Exception {
            testStartTimes[description] = System.currentTimeMillis()
        }

        @Override
        void testFinished(Description description) throws Exception {
            long startTime = testStartTimes[description]
            testRunTimes[description] = System.currentTimeMillis() - startTime
        }

        @Override
        void testRunFinished(Result result) throws Exception {
            println "Test suite finished; ${testRunTimes.size()} total tests"

            // descending order, so reverse comparison
            def sortedRunTimes = testRunTimes.sort { a, b -> b.value  a.value }
            for(Description description: sortedRunTimes.keySet()) {
                long runTime = sortedRunTimes[description]
                if (runTime < showTestTimesGreaterThanOrEqualToMilliseconds) {
                    break;
                }
                println " - $description: $runTime ms"
            }
        }
    }

    static void main(String... args) {
        JUnitCore core= new JUnitCore()
        core.addListener(new TimingListener())
        core.run(testSuite) Runtime.runtime.exit(0)
    }
}