• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 4516

27 Mar 2025 03:42PM UTC coverage: 79.841% (+2.0%) from 77.853%
4516

push

github

adangel
[doc] Fix search index (#5618)

Merge pull request #5618 from adangel:doc/fix-search

16710 of 21943 branches covered (76.15%)

Branch coverage included in aggregate %.

34885 of 42679 relevant lines covered (81.74%)

0.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

31.33
/pmd-test/src/main/java/net/sourceforge/pmd/test/RuleTst.java
1
/**
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.test;
6

7
import static org.junit.jupiter.api.Assertions.assertEquals;
8
import static org.junit.jupiter.api.Assertions.fail;
9

10
import java.io.File;
11
import java.io.IOException;
12
import java.io.InputStream;
13
import java.io.StringWriter;
14
import java.net.URI;
15
import java.net.URISyntaxException;
16
import java.net.URL;
17
import java.nio.file.Path;
18
import java.nio.file.Paths;
19
import java.security.CodeSource;
20
import java.util.ArrayList;
21
import java.util.Collection;
22
import java.util.Collections;
23
import java.util.Comparator;
24
import java.util.List;
25
import java.util.Map;
26

27
import org.apache.commons.lang3.StringUtils;
28
import org.junit.jupiter.api.DynamicTest;
29
import org.junit.jupiter.api.TestFactory;
30
import org.xml.sax.InputSource;
31

32
import net.sourceforge.pmd.PMDConfiguration;
33
import net.sourceforge.pmd.PmdAnalysis;
34
import net.sourceforge.pmd.internal.util.ClasspathClassLoader;
35
import net.sourceforge.pmd.lang.LanguageVersion;
36
import net.sourceforge.pmd.lang.document.FileId;
37
import net.sourceforge.pmd.lang.document.TextFile;
38
import net.sourceforge.pmd.lang.rule.Rule;
39
import net.sourceforge.pmd.lang.rule.RuleSet;
40
import net.sourceforge.pmd.lang.rule.RuleSetLoadException;
41
import net.sourceforge.pmd.lang.rule.RuleSetLoader;
42
import net.sourceforge.pmd.properties.PropertyDescriptor;
43
import net.sourceforge.pmd.renderers.TextRenderer;
44
import net.sourceforge.pmd.reporting.GlobalAnalysisListener;
45
import net.sourceforge.pmd.reporting.Report;
46
import net.sourceforge.pmd.reporting.RuleViolation;
47
import net.sourceforge.pmd.test.schema.RuleTestCollection;
48
import net.sourceforge.pmd.test.schema.RuleTestDescriptor;
49
import net.sourceforge.pmd.test.schema.TestSchemaParser;
50

51
/**
52
 * Advanced methods for test cases
53
 */
54
public abstract class RuleTst {
1✔
55

56
    protected void setUp() {
57
        // This method is intended to be overridden by subclasses.
58
    }
×
59

60
    protected List<Rule> getRules() {
61
        return Collections.emptyList();
×
62
    }
63

64
    /**
65
     * Find a rule in a certain ruleset by name.
66
     */
67
    public static Rule findRule(String ruleSet, String ruleName) {
68
        try {
69
            RuleSet parsedRset = new RuleSetLoader().warnDeprecated(false).loadFromResource(ruleSet);
×
70
            Rule rule = parsedRset.getRuleByName(ruleName);
×
71
            if (rule == null) {
×
72
                fail("Rule " + ruleName + " not found in ruleset " + ruleSet);
×
73
            } else {
74
                rule.setRuleSetName(ruleSet);
×
75
            }
76
            return rule;
×
77
        } catch (RuleSetLoadException e) {
×
78
            e.printStackTrace();
×
79
            fail("Couldn't find ruleset " + ruleSet);
×
80
            return null;
×
81
        }
82
    }
83

84
    /**
85
     * Run the rule on the given code, and check the expected number of violations.
86
     */
87
    void runTest(RuleTestDescriptor test) {
88
        Rule rule = test.getRule();
1✔
89

90
        // always reinitialize the rule, regardless of test.getReinitializeRule() (#3976 / #3302)
91
        rule = reinitializeRule(rule);
1✔
92

93
        Map<PropertyDescriptor<?>, Object> oldProperties = rule.getPropertiesByPropertyDescriptor();
1✔
94
        try {
95
            int res;
96
            Report report;
97
            try {
98
                // Set test specific properties onto the Rule
99
                if (test.getProperties() != null) {
1!
100
                    for (Map.Entry<Object, Object> entry : test.getProperties().entrySet()) {
1!
101
                        String propertyName = (String) entry.getKey();
×
102
                        PropertyDescriptor propertyDescriptor = rule.getPropertyDescriptor(propertyName);
×
103
                        if (propertyDescriptor == null) {
×
104
                            throw new IllegalArgumentException(
×
105
                                    "No such property '" + propertyName + "' on Rule " + rule.getName());
×
106
                        }
107

108
                        Object value = propertyDescriptor.serializer().fromString((String) entry.getValue());
×
109
                        rule.setProperty(propertyDescriptor, value);
×
110
                    }
×
111
                }
112

113
                String dysfunctionReason = rule.dysfunctionReason();
1✔
114
                if (StringUtils.isNotBlank(dysfunctionReason)) {
1!
115
                    throw new RuntimeException("Rule is not configured correctly: " + dysfunctionReason);
×
116
                }
117

118
                report = processUsingStringReader(test, rule);
1✔
119
                res = report.getViolations().size();
1✔
120
            } catch (Exception e) {
×
121
                e.printStackTrace();
×
122
                throw new RuntimeException('"' + test.getDescription() + "\" failed", e);
×
123
            }
1✔
124
            if (test.getExpectedProblems() != res) {
1!
125
                printReport(test, report);
×
126
            }
127
            assertEquals(test.getExpectedProblems(), res,
1✔
128
                         '"' + test.getDescription() + "\" resulted in wrong number of failures,");
1✔
129
            assertMessages(report, test);
1✔
130
            assertLineNumbers(report, test);
1✔
131
        } finally {
132
            // Restore old properties
133
            for (Map.Entry<PropertyDescriptor<?>, Object> entry : oldProperties.entrySet()) {
1✔
134
                rule.setProperty((PropertyDescriptor) entry.getKey(), entry.getValue());
1✔
135
            }
1✔
136
        }
137
    }
1✔
138

139

140
    /**
141
     * Code to be executed if the rule is reinitialised.
142
     *
143
     * @param rule The rule to reinitialise
144
     *
145
     * @return The rule once it has been reinitialised
146
     */
147
    private Rule reinitializeRule(Rule rule) {
148
        return rule.deepCopy();
1✔
149
    }
150

151

152
    private void assertMessages(Report report, RuleTestDescriptor test) {
153
        if (report == null || test.getExpectedMessages().isEmpty()) {
1!
154
            return;
1✔
155
        }
156

157
        List<String> expectedMessages = test.getExpectedMessages();
×
158
        if (report.getViolations().size() != expectedMessages.size()) {
×
159
            throw new RuntimeException("Test setup error: number of expected messages doesn't match "
×
160
                                           + "number of violations for test case '" + test.getDescription() + "'");
×
161
        }
162

163
        int index = 0;
×
164
        for (RuleViolation violation : report.getViolations()) {
×
165
            String actual = violation.getDescription();
×
166
            if (!expectedMessages.get(index).equals(actual)) {
×
167
                printReport(test, report);
×
168
            }
169
            assertEquals(expectedMessages.get(index), actual,
×
170
                         '"' + test.getDescription() + "\" produced wrong message on violation number " + (index + 1)
×
171
                             + ".");
172
            index++;
×
173
        }
×
174
    }
×
175

176
    private void assertLineNumbers(Report report, RuleTestDescriptor test) {
177
        if (report == null || test.getExpectedLineNumbers().isEmpty()) {
1!
178
            return;
×
179
        }
180

181
        List<Integer> expected = test.getExpectedLineNumbers();
1✔
182
        List<Integer> expectedEndLines = test.getExpectedEndLineNumbers();
1✔
183
        if (report.getViolations().size() != expected.size()) {
1!
184
            throw new RuntimeException("Test setup error: number of expected line numbers " + expected.size()
×
185
                                           + " doesn't match number of violations " + report.getViolations().size()
×
186
                                           + " for test case '"
187
                                           + test.getDescription() + "'");
×
188
        }
189

190
        int index = 0;
1✔
191
        for (RuleViolation violation : report.getViolations()) {
1✔
192
            Integer actualBeginLine = violation.getBeginLine();
1✔
193
            Integer actualEndLine = violation.getEndLine();
1✔
194

195
            boolean isFailing = expected.get(index) != actualBeginLine.intValue();
1!
196
            isFailing |= (!expectedEndLines.isEmpty() && expectedEndLines.get(index) != actualEndLine.intValue());
1!
197

198
            if (isFailing) {
1!
199
                printReport(test, report);
×
200
            }
201
            assertEquals(expected.get(index), actualBeginLine,
1✔
202
                         '"' + test.getDescription() + "\" violation on wrong line number: violation number "
1✔
203
                             + (index + 1) + ".");
204
            if (!expectedEndLines.isEmpty()) {
1!
205
                assertEquals(expectedEndLines.get(index), actualEndLine,
1✔
206
                        '"' + test.getDescription() + "\" violation on wrong end line number: violation number "
1✔
207
                            + (index + 1) + ".");
208
            }
209
            index++;
1✔
210
        }
1✔
211
    }
1✔
212

213
    private void printReport(RuleTestDescriptor test, Report report) {
214
        System.out.println("--------------------------------------------------------------");
×
215
        System.out.println("Test Failure: " + test.getDescription());
×
216
        System.out.println(
×
217
            " -> Expected " + test.getExpectedProblems() + " problem(s), " + report.getViolations().size()
×
218
                + " problem(s) found.");
219
        System.out.println(" -> Expected messages: " + test.getExpectedMessages());
×
220
        System.out.println(" -> Expected begin line numbers: " + test.getExpectedLineNumbers());
×
221
        if (!test.getExpectedEndLineNumbers().isEmpty()) {
×
222
            System.out.println(" -> Expected   end line numbers: " + test.getExpectedEndLineNumbers());
×
223
        }
224
        System.out.println();
×
225
        StringWriter reportOutput = new StringWriter();
×
226
        TextRenderer renderer = new TextRenderer();
×
227
        renderer.setWriter(reportOutput);
×
228
        try {
229
            renderer.start();
×
230
            renderer.renderFileReport(report);
×
231
            renderer.end();
×
232
        } catch (IOException e) {
×
233
            throw new RuntimeException(e);
×
234
        }
×
235
        System.out.println(reportOutput);
×
236
        System.out.println("--------------------------------------------------------------");
×
237
    }
×
238

239
    private Report processUsingStringReader(RuleTestDescriptor test, Rule rule) {
240
        return runTestFromString(test.getCode(), rule, test.getLanguageVersion());
1✔
241
    }
242

243
    private static final ClassLoader TEST_AUXCLASSPATH_CLASSLOADER;
244

245
    static {
246
        final Path PATH_TO_JRT_FS_JAR;
247
        // find jrt-fs.jar to be added to auxclasspath
248
        // Similar logic like jdk.internal.jrtfs.SystemImage
249
        CodeSource codeSource = Object.class.getProtectionDomain().getCodeSource();
1✔
250
        if (codeSource == null) {
1!
251
            PATH_TO_JRT_FS_JAR = Paths.get(System.getProperty("java.home"), "lib", "jrt-fs.jar");
1✔
252
        } else {
253
            URL location = codeSource.getLocation();
×
254
            if (!"file".equalsIgnoreCase(location.getProtocol())) {
×
255
                throw new IllegalStateException("Object.class loaded in unexpected way from " + location);
×
256
            }
257
            try {
258
                PATH_TO_JRT_FS_JAR = Paths.get(location.toURI());
×
259
            } catch (URISyntaxException e) {
×
260
                throw new IllegalStateException(e);
×
261
            }
×
262
        }
263

264
        try {
265
            TEST_AUXCLASSPATH_CLASSLOADER = new ClasspathClassLoader(PATH_TO_JRT_FS_JAR.toString(), PMDConfiguration.class.getClassLoader());
1✔
266
        } catch (IOException e) {
×
267
            throw new RuntimeException(e);
×
268
        }
1✔
269
    }
1✔
270

271
    /**
272
     * Run the rule on the given code and put the violations in the report.
273
     */
274
    Report runTestFromString(String code, Rule rule, LanguageVersion languageVersion) {
275
        PMDConfiguration configuration = new PMDConfiguration();
1✔
276
        configuration.setIgnoreIncrementalAnalysis(true);
1✔
277
        configuration.setDefaultLanguageVersion(languageVersion);
1✔
278
        configuration.setThreads(0); // don't use separate threads
1✔
279
        configuration.setClassLoader(TEST_AUXCLASSPATH_CLASSLOADER);
1✔
280

281
        try (PmdAnalysis pmd = PmdAnalysis.create(configuration)) {
1✔
282
            pmd.files().addFile(TextFile.forCharSeq(code, FileId.fromPathLikeString("file"), languageVersion));
1✔
283
            pmd.addRuleSet(RuleSet.forSingleRule(rule));
1✔
284
            pmd.addListener(GlobalAnalysisListener.exceptionThrower());
1✔
285
            return pmd.performAnalysisAndCollectReport();
1✔
286
        }
287
    }
288

289
    /**
290
     * getResourceAsStream tries to find the XML file in weird locations if the
291
     * ruleName includes the package, so we strip it here.
292
     */
293
    private String getCleanRuleName(Rule rule) {
294
        String fullClassName = rule.getClass().getName();
×
295
        if (fullClassName.equals(rule.getName())) {
×
296
            // We got the full class name, so we'll use the stripped name
297
            // instead
298
            String packageName = rule.getClass().getPackage().getName();
×
299
            return fullClassName.substring(packageName.length() + 1);
×
300
        } else {
301
            return rule.getName(); // Test is using findRule, smart!
×
302
        }
303
    }
304

305
    /**
306
     * Extract a set of tests from an XML file. The file should be
307
     * ./xml/RuleName.xml relative to the test class. The format is defined in
308
     * rule-tests_1_0_0.xsd in pmd-test-schema.
309
     */
310
    RuleTestCollection parseTestCollection(Rule rule) {
311
        String testsFileName = getCleanRuleName(rule);
×
312
        return parseTestCollection(rule, testsFileName);
×
313
    }
314

315
    private RuleTestCollection parseTestCollection(Rule rule, String testsFileName) {
316
        return parseTestXml(rule, testsFileName, "xml/");
×
317
    }
318

319
    /**
320
     * Extract a set of tests from an XML file with the given name. The file
321
     * should be ./xml/[testsFileName].xml relative to the test class. The
322
     * format is defined in test-data.xsd.
323
     */
324
    private RuleTestCollection parseTestXml(Rule rule, String testsFileName, String baseDirectory) {
325
        String testXmlFileName = baseDirectory + testsFileName + ".xml";
×
326
        String absoluteUriToTestXmlFile = new File(".").getAbsoluteFile().toURI() + "/src/test/resources/"
×
327
                + this.getClass().getPackage().getName().replaceAll("\\.", "/")
×
328
                + "/" + testXmlFileName;
329

330
        try (InputStream inputStream = getClass().getResourceAsStream(testXmlFileName)) {
×
331
            if (inputStream == null) {
×
332
                throw new RuntimeException("Couldn't find " + testXmlFileName);
×
333
            }
334
            InputSource source = new InputSource();
×
335
            source.setByteStream(inputStream);
×
336
            source.setSystemId(testXmlFileName);
×
337
            TestSchemaParser parser = new TestSchemaParser();
×
338
            RuleTestCollection ruleTestCollection = parser.parse(rule, source);
×
339
            ruleTestCollection.setAbsoluteUriToTestXmlFile(absoluteUriToTestXmlFile);
×
340
            return ruleTestCollection;
×
341
        } catch (Exception e) {
×
342
            throw new RuntimeException("Couldn't parse " + testXmlFileName + ", due to: " + e, e);
×
343
        }
344
    }
345

346
    /**
347
     * Run a set of tests defined in an XML test-data file for a rule. The file
348
     * should be ./xml/RuleName.xml relative to the test-class. The format is
349
     * defined in test-data.xsd.
350
     */
351
    public void runTests(Rule rule) {
352
        runTests(parseTestCollection(rule));
×
353
    }
×
354

355
    /**
356
     * Run a set of tests defined in a XML test-data file. The file should be
357
     * ./xml/[testsFileName].xml relative to the test-class. The format is
358
     * defined in test-data.xsd.
359
     */
360
    public void runTests(Rule rule, String testsFileName) {
361
        runTests(parseTestCollection(rule, testsFileName));
×
362
    }
×
363

364
    private void runTests(RuleTestCollection tests) {
365
        for (RuleTestDescriptor test : tests.getTests()) {
×
366
            runTest(test);
×
367
        }
×
368
    }
×
369

370
    @TestFactory
371
    Collection<DynamicTest> ruleTests() {
372
        setUp();
×
373
        final List<Rule> rules = new ArrayList<>(getRules());
×
374
        rules.sort(Comparator.comparing(Rule::getName));
×
375

376
        List<DynamicTest> tests = new ArrayList<>();
×
377
        for (Rule r : rules) {
×
378
            RuleTestCollection ruleTests = parseTestCollection(r);
×
379
            RuleTestDescriptor focused = ruleTests.getFocusedTestOrNull();
×
380
            for (RuleTestDescriptor t : ruleTests.getTests()) {
×
381
                if (focused != null && !focused.equals(t)) {
×
382
                    t.setDisabled(true); // disable it
×
383
                }
384
                tests.add(toDynamicTest(ruleTests, t));
×
385
            }
×
386
        }
×
387
        return tests;
×
388
    }
389

390
    private DynamicTest toDynamicTest(RuleTestCollection collection, RuleTestDescriptor testDescriptor) {
391
        URI testSourceUri = URI.create(
×
392
            collection.getAbsoluteUriToTestXmlFile() + "?line=" + testDescriptor.getLineNumber());
×
393
        if (testDescriptor.isDisabled()) {
×
394
            return DynamicTest.dynamicTest("[IGNORED] " + testDescriptor.getDescription(),
×
395
                                           testSourceUri,
396
                                           () -> { });
×
397
        }
398
        return DynamicTest.dynamicTest(testDescriptor.getDescription(),
×
399
                                       testSourceUri,
400
                                       () -> runTest(testDescriptor));
×
401
    }
402
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc