View Javadoc
1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.processor;
5   
6   import java.io.ByteArrayInputStream;
7   import java.io.IOException;
8   import java.io.InputStream;
9   import java.util.ArrayList;
10  import java.util.Collections;
11  import java.util.List;
12  import java.util.concurrent.atomic.AtomicInteger;
13  
14  import org.junit.Assert;
15  import org.junit.Test;
16  
17  import net.sourceforge.pmd.PMDConfiguration;
18  import net.sourceforge.pmd.ReportListener;
19  import net.sourceforge.pmd.RuleContext;
20  import net.sourceforge.pmd.RuleSetFactory;
21  import net.sourceforge.pmd.RuleViolation;
22  import net.sourceforge.pmd.lang.ast.Node;
23  import net.sourceforge.pmd.lang.rule.AbstractRule;
24  import net.sourceforge.pmd.renderers.Renderer;
25  import net.sourceforge.pmd.stat.Metric;
26  import net.sourceforge.pmd.util.datasource.DataSource;
27  
28  public class MultiThreadProcessorTest {
29  
30      @Test
31      public void testRulesThreadSafety() {
32          PMDConfiguration configuration = new PMDConfiguration();
33          configuration.setRuleSets("rulesets/MultiThreadProcessorTest/basic.xml");
34          configuration.setThreads(2);
35          List<DataSource> files = new ArrayList<DataSource>();
36          files.add(new StringDataSource("file1-violation.dummy", "ABC"));
37          files.add(new StringDataSource("file2-foo.dummy", "DEF"));
38  
39          SimpleReportListener reportListener = new SimpleReportListener();
40          RuleContext ctx = new RuleContext();
41          ctx.getReport().addListener(reportListener);
42  
43          MultiThreadProcessor processor = new MultiThreadProcessor(configuration);
44          RuleSetFactory ruleSetFactory = new RuleSetFactory();
45          processor.processFiles(ruleSetFactory, files, ctx, Collections.<Renderer>emptyList());
46  
47          // if the rule is not executed, then maybe a ConcurrentModificationException happened
48          Assert.assertEquals("Test rule has not been executed", 2, NotThreadSafeRule.count.get());
49          // if the violation is not reported, then the rule instances have been shared between the threads
50          Assert.assertEquals("Missing violation", 1, reportListener.violations.get());
51      }
52  
53      private static class StringDataSource implements DataSource {
54          private final String data;
55          private final String name;
56          public StringDataSource(String name, String data) {
57              this.name = name;
58              this.data = data;
59          }
60          @Override
61          public InputStream getInputStream() throws IOException {
62              return new ByteArrayInputStream(data.getBytes("UTF-8"));
63          }
64          @Override
65          public String getNiceFileName(boolean shortNames, String inputFileName) {
66              return name;
67          }
68      }
69  
70      public static class NotThreadSafeRule extends AbstractRule {
71          public static AtomicInteger count = new AtomicInteger(0);
72          private boolean hasViolation; // this variable will be overridden between the threads
73          @Override
74          public void apply(List<? extends Node> nodes, RuleContext ctx) {
75              count.incrementAndGet();
76  
77              if (ctx.getSourceCodeFilename().contains("violation")) {
78                  hasViolation = true;
79              } else {
80                  letTheOtherThreadRun(10);
81                  hasViolation = false;
82              }
83  
84              letTheOtherThreadRun(100);
85              if (hasViolation) {
86                  addViolation(ctx, nodes.get(0));
87              }
88          }
89          private void letTheOtherThreadRun(int millis) {
90              try {
91                  Thread.yield();
92                  Thread.sleep(millis);
93              } catch (InterruptedException e) {
94                  // ignored
95              }
96          }
97      }
98  
99      private static class SimpleReportListener implements ReportListener {
100         public AtomicInteger violations = new AtomicInteger(0);
101         @Override
102         public void ruleViolationAdded(RuleViolation ruleViolation) {
103             violations.incrementAndGet();
104         }
105         @Override
106         public void metricAdded(Metric metric) {
107         }
108     }
109 }