From 54ce3036b2b4faa8edff04838844c9303bd525df Mon Sep 17 00:00:00 2001 From: Andreas Dangel Date: Thu, 10 Mar 2016 20:01:59 +0100 Subject: [PATCH] Add unit test to verify concurrency issue #1461 References pr #75 --- .../processor/MultiThreadProcessorTest.java | 109 ++++++++++++++++++ .../MultiThreadProcessorTest/basic.xml | 15 +++ 2 files changed, 124 insertions(+) create mode 100644 pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java create mode 100644 pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml diff --git a/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java b/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java new file mode 100644 index 0000000000..3aa643821b --- /dev/null +++ b/pmd-core/src/test/java/net/sourceforge/pmd/processor/MultiThreadProcessorTest.java @@ -0,0 +1,109 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.processor; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Assert; +import org.junit.Test; + +import net.sourceforge.pmd.PMDConfiguration; +import net.sourceforge.pmd.ReportListener; +import net.sourceforge.pmd.RuleContext; +import net.sourceforge.pmd.RuleSetFactory; +import net.sourceforge.pmd.RuleViolation; +import net.sourceforge.pmd.lang.ast.Node; +import net.sourceforge.pmd.lang.rule.AbstractRule; +import net.sourceforge.pmd.renderers.Renderer; +import net.sourceforge.pmd.stat.Metric; +import net.sourceforge.pmd.util.datasource.DataSource; + +public class MultiThreadProcessorTest { + + @Test + public void testRulesThreadSafety() { + PMDConfiguration configuration = new PMDConfiguration(); + configuration.setRuleSets("rulesets/MultiThreadProcessorTest/basic.xml"); + configuration.setThreads(2); + List files = new ArrayList<>(); + files.add(new StringDataSource("file1-violation.dummy", "ABC")); + files.add(new StringDataSource("file2-foo.dummy", "DEF")); + + SimpleReportListener reportListener = new SimpleReportListener(); + RuleContext ctx = new RuleContext(); + ctx.getReport().addListener(reportListener); + + MultiThreadProcessor processor = new MultiThreadProcessor(configuration); + RuleSetFactory ruleSetFactory = new RuleSetFactory(); + processor.processFiles(ruleSetFactory, files, ctx, Collections.emptyList()); + + // if the rule is not executed, then maybe a ConcurrentModificationException happened + Assert.assertEquals("Test rule has not been executed", 2, NotThreadSafeRule.count.get()); + // if the violation is not reported, then the rule instances have been shared between the threads + Assert.assertEquals("Missing violation", 1, reportListener.violations.get()); + } + + private static class StringDataSource implements DataSource { + private final String data; + private final String name; + public StringDataSource(String name, String data) { + this.name = name; + this.data = data; + } + @Override + public InputStream getInputStream() throws IOException { + return new ByteArrayInputStream(data.getBytes("UTF-8")); + } + @Override + public String getNiceFileName(boolean shortNames, String inputFileName) { + return name; + } + } + + public static class NotThreadSafeRule extends AbstractRule { + public static AtomicInteger count = new AtomicInteger(0); + private boolean hasViolation; // this variable will be overridden between the threads + @Override + public void apply(List nodes, RuleContext ctx) { + count.incrementAndGet(); + + if (ctx.getSourceCodeFilename().contains("violation")) { + hasViolation = true; + } else { + letTheOtherThreadRun(10); + hasViolation = false; + } + + letTheOtherThreadRun(100); + if (hasViolation) { + addViolation(ctx, nodes.get(0)); + } + } + private void letTheOtherThreadRun(int millis) { + try { + Thread.yield(); + Thread.sleep(millis); + } catch (InterruptedException e) { + // ignored + } + } + } + + private static class SimpleReportListener implements ReportListener { + public AtomicInteger violations = new AtomicInteger(0); + @Override + public void ruleViolationAdded(RuleViolation ruleViolation) { + violations.incrementAndGet(); + } + @Override + public void metricAdded(Metric metric) { + } + } +} diff --git a/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml new file mode 100644 index 0000000000..499b6a4596 --- /dev/null +++ b/pmd-core/src/test/resources/rulesets/MultiThreadProcessorTest/basic.xml @@ -0,0 +1,15 @@ + + + + + Ruleset used by test RuleSetReferenceIdTest + + + + Foo + 3 + + + \ No newline at end of file