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

pmd / pmd / 902

27 Oct 2026 11:31AM UTC coverage: 80.047% (+0.05%) from 79.995%
902

push

github

web-flow
[tests] Remove flow control from tests (#7173)

20561 of 26684 branches covered (77.05%)

Branch coverage included in aggregate %.

0 of 46 new or added lines in 2 files covered. (0.0%)

9 existing lines in 1 file now uncovered.

43944 of 53900 relevant lines covered (81.53%)

0.82 hits per line

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

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

5
package net.sourceforge.pmd.test.lang.rule;
6

7
import static org.hamcrest.MatcherAssert.assertThat;
8
import static org.hamcrest.Matchers.containsString;
9
import static org.hamcrest.Matchers.emptyString;
10
import static org.junit.jupiter.api.Assertions.assertAll;
11
import static org.junit.jupiter.api.Assertions.assertEquals;
12
import static org.junit.jupiter.api.Assertions.assertFalse;
13
import static org.junit.jupiter.api.Assertions.assertNotNull;
14
import static org.junit.jupiter.api.Assertions.assertTrue;
15
import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS;
16

17
import java.io.BufferedReader;
18
import java.io.ByteArrayInputStream;
19
import java.io.ByteArrayOutputStream;
20
import java.io.FileNotFoundException;
21
import java.io.IOException;
22
import java.io.InputStream;
23
import java.io.InputStreamReader;
24
import java.nio.charset.StandardCharsets;
25
import java.util.ArrayList;
26
import java.util.Arrays;
27
import java.util.Collections;
28
import java.util.HashMap;
29
import java.util.HashSet;
30
import java.util.List;
31
import java.util.Locale;
32
import java.util.Map;
33
import java.util.Properties;
34
import java.util.Set;
35
import java.util.StringTokenizer;
36
import java.util.regex.Pattern;
37
import java.util.stream.Collectors;
38
import javax.xml.parsers.SAXParser;
39
import javax.xml.parsers.SAXParserFactory;
40

41
import org.apache.commons.io.FilenameUtils;
42
import org.junit.jupiter.api.BeforeAll;
43
import org.junit.jupiter.api.Test;
44
import org.junit.jupiter.api.TestInstance;
45
import org.junit.jupiter.api.function.Executable;
46
import org.junit.jupiter.params.ParameterizedTest;
47
import org.junit.jupiter.params.provider.MethodSource;
48
import org.slf4j.event.Level;
49
import org.xml.sax.InputSource;
50
import org.xml.sax.SAXException;
51
import org.xml.sax.SAXParseException;
52
import org.xml.sax.helpers.DefaultHandler;
53

54
import net.sourceforge.pmd.lang.Language;
55
import net.sourceforge.pmd.lang.LanguageRegistry;
56
import net.sourceforge.pmd.lang.rule.InternalApiBridge;
57
import net.sourceforge.pmd.lang.rule.Rule;
58
import net.sourceforge.pmd.lang.rule.RuleReference;
59
import net.sourceforge.pmd.lang.rule.RuleSet;
60
import net.sourceforge.pmd.lang.rule.RuleSetLoader;
61
import net.sourceforge.pmd.lang.rule.RuleSetWriter;
62
import net.sourceforge.pmd.lang.rule.impl.UnnecessaryPmdSuppressionRule;
63
import net.sourceforge.pmd.lang.rule.xpath.XPathRule;
64
import net.sourceforge.pmd.properties.PropertyDescriptor;
65
import net.sourceforge.pmd.util.log.internal.MessageReporterBase;
66

67
/**
68
 * Base test class to verify the language's rulesets. This class should be
69
 * subclassed for each language.
70
 */
71
@TestInstance(PER_CLASS)
72
public abstract class AbstractRuleSetFactoryTest {
73

74
    private static ValidateDefaultHandler validateDefaultHandler;
75
    private static SAXParser saxParser;
76

77
    // todo rename this field to validCoreRules or something. Make private.
78
    protected Set<String> validXPathClassNames = new HashSet<>();
×
79
    private final Set<String> languagesToSkip = new HashSet<>();
×
80
    private final Map<String, Set<String>> expectedMessagesPerRuleset = new HashMap<>();
×
81

82
    public AbstractRuleSetFactoryTest() {
83
        this(new String[0]);
×
84
    }
×
85

86
    public AbstractRuleSetFactoryTest(Map<String, Set<String>> expectedMessagesPerRuleset) {
87
        this();
×
88
        this.expectedMessagesPerRuleset.putAll(expectedMessagesPerRuleset);
×
89
    }
×
90

91
    /**
92
     * Constructor used when a module that depends on another module wants to filter out the dependee's rulesets.
93
     *
94
     * @param languagesToSkip {@link Language}s terse names that appear in the classpath via a dependency, but should be
95
     * skipped because they aren't the primary language which the concrete instance of this class is testing.
96
     */
97
    public AbstractRuleSetFactoryTest(String... languagesToSkip) {
×
98
        this.languagesToSkip.add("dummy");
×
99
        this.languagesToSkip.addAll(Arrays.asList(languagesToSkip));
×
100
        validXPathClassNames.add(XPathRule.class.getName());
×
101
        validXPathClassNames.add(UnnecessaryPmdSuppressionRule.class.getName());
×
102
    }
×
103

104
    public AbstractRuleSetFactoryTest(Language... languagesToSkip) {
105
        this(Arrays.stream(languagesToSkip).map(Language::getId).toArray(String[]::new));
×
106
    }
×
107

108

109
    /**
110
     * Setups the XML parser with validation.
111
     *
112
     * @throws Exception
113
     *             any error
114
     */
115
    @BeforeAll
116
    static void init() throws Exception {
117
        SAXParserFactory saxParserFactory = SAXParserFactory.newInstance();
×
118
        saxParserFactory.setValidating(true);
×
119
        saxParserFactory.setNamespaceAware(true);
×
120

121
        // Hope we're using Xerces, or this may not work!
122
        // Note: Features are listed here
123
        // http://xerces.apache.org/xerces2-j/features.html
124
        saxParserFactory.setFeature("http://xml.org/sax/features/validation", true);
×
125
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema", true);
×
126
        saxParserFactory.setFeature("http://apache.org/xml/features/validation/schema-full-checking", true);
×
127

128
        validateDefaultHandler = new ValidateDefaultHandler();
×
129

130
        saxParser = saxParserFactory.newSAXParser();
×
131
    }
×
132

133
    /**
134
     * Checks all rulesets of all languages on the classpath and verifies that
135
     * all rules are in alphabetical order.
136
     */
137
    @ParameterizedTest
138
    @MethodSource("getRuleSetFileNames")
139
    void testAllPMDBuiltInRuleSetsAreSorted(String fileName) {
NEW
140
        List<Executable> checks = new ArrayList<>();
×
UNCOV
141
        String lastName = null;
×
142
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
143
        for (Rule rule : ruleSet.getRules()) {
×
NEW
144
            final String prevName = lastName;
×
NEW
145
            checks.add(() -> assertTrue(prevName == null || String.CASE_INSENSITIVE_ORDER.compare(rule.getName(), prevName) > 0,
×
NEW
146
                rule.getName() + " should be before " + prevName));
×
147
            lastName = rule.getName();
×
148
        }
×
NEW
149
        assertAll(checks);
×
UNCOV
150
    }
×
151

152
    /**
153
     * Checks all rulesets of all languages on the classpath and verifies that
154
     * all rules have a 'since' attribute.
155
     */
156
    @ParameterizedTest
157
    @MethodSource("getRuleSetFileNames")
158
    void testAllPMDBuiltInRulesHaveSince(String fileName) {
NEW
159
        List<Executable> checks = new ArrayList<>();
×
160

161
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
NEW
162
        for (Rule rule : skipReferences(ruleSet)) {
×
NEW
163
            checks.add(() -> assertNotNull(rule.getSince(),
×
NEW
164
                "Rule " + fileName + "/" + rule.getName() + " is missing 'since' attribute"));
×
165
        }
×
NEW
166
        assertAll(checks);
×
UNCOV
167
    }
×
168

169
    /**
170
     * Checks all rulesets of all languages on the classpath and verifies that
171
     * all rules have the correct externalInfoUrl
172
     */
173
    @ParameterizedTest
174
    @MethodSource("getRuleSetFileNames")
175
    void testAllPMDBuiltInRulesHaveCorrectExternalInfoUrl(String fileName) {
NEW
176
        List<Executable> checks = new ArrayList<>();
×
177

178
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
NEW
179
        for (Rule rule : skipReferences(ruleSet)) {
×
UNCOV
180
            Language language = rule.getLanguage();
×
181

182
            // Is URL correct ?
NEW
183
            String expectedExternalInfoURL = "https://docs.pmd-code.org/.+/pmd_rules_"
×
NEW
184
                    + language.getId() + "_"
×
NEW
185
                    + FilenameUtils.getBaseName(fileName)
×
186
                    + ".html#"
NEW
187
                    + rule.getName().toLowerCase(Locale.ROOT);
×
188

NEW
189
            checks.add(() -> assertTrue(rule.getExternalInfoUrl() != null && rule.getExternalInfoUrl().matches(expectedExternalInfoURL),
×
NEW
190
                    "Rule " + fileName + "/" + rule.getName() + " seems to have an invalid 'externalInfoURL' value (" + rule.getExternalInfoUrl() + "), it should be: " + expectedExternalInfoURL + '\n'
×
191
            ));
192
        }
×
NEW
193
        assertAll(checks);
×
UNCOV
194
    }
×
195

196
    /**
197
     * Checks all rulesets of all languages on the classpath and verifies that
198
     * all rules have a valid classname.
199
     */
200
    @ParameterizedTest
201
    @MethodSource("getRuleSetFileNames")
202
    void testAllPMDBuiltInRulesHaveValidClassName(String fileName) {
NEW
203
        List<Executable> checks = new ArrayList<>();
×
204

205
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
NEW
206
        for (Rule rule : skipReferences(ruleSet)) {
×
UNCOV
207
            Language language = rule.getLanguage();
×
208
            String group = fileName.substring(fileName.lastIndexOf('/') + 1);
×
209
            group = group.substring(0, group.indexOf(".xml"));
×
210
            if (group.indexOf('-') >= 0) {
×
211
                group = group.substring(0, group.indexOf('-'));
×
212
            }
213

214
            // Proper class name/packaging?
215
            String expectedClassName = "net.sourceforge.pmd.lang." + language.getId() + ".rule." + group + "." + rule.getName() + "Rule";
×
NEW
216
            checks.add(() -> assertTrue(
×
NEW
217
                    rule.getRuleClass().equals(expectedClassName) || validXPathClassNames.contains(rule.getRuleClass()),
×
NEW
218
                    "Rule " + fileName + "/" + rule.getName() + " seems to have an invalid 'class' value (" + rule.getRuleClass() + "), it should be: " + expectedClassName + '\n'
×
219
            ));
UNCOV
220
        }
×
221

NEW
222
        assertAll(checks);
×
223
    }
×
224

225
    /**
226
     * Checks all rulesets of all languages on the classpath and verifies that
227
     * no rules have a suppression regex.
228
     */
229
    @ParameterizedTest
230
    @MethodSource("getRuleSetFileNames")
231
    void testAllPMDBuiltInRulesHaveNoSuppressionRegex(String fileName) {
NEW
232
        List<Executable> checks = new ArrayList<>();
×
233

234
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
NEW
235
        for (Rule rule : skipReferences(ruleSet)) {
×
NEW
236
            checks.add(() -> assertFalse(
×
NEW
237
                    rule.getProperty(Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR).isPresent(),
×
NEW
238
                "Rule " + fileName + "/" + rule.getName() + " should not have '" + Rule.VIOLATION_SUPPRESS_REGEX_DESCRIPTOR.name() + "', this is intended for end user customization only.\n"
×
239
            ));
240
        }
×
NEW
241
        assertAll(checks);
×
UNCOV
242
    }
×
243

244
    /**
245
     * Checks all rulesets of all languages on the classpath and verifies that
246
     * no rules have a suppression XPath.
247
     */
248
    @ParameterizedTest
249
    @MethodSource("getRuleSetFileNames")
250
    void testAllPMDBuiltInRulesHaveNoSuppressionXPath(String fileName) {
NEW
251
        List<Executable> checks = new ArrayList<>();
×
252

253
        RuleSet ruleSet = loadRuleSetByFileName(fileName);
×
NEW
254
        for (Rule rule : skipReferences(ruleSet)) {
×
NEW
255
            checks.add(() -> assertFalse(
×
NEW
256
                    rule.getProperty(Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR).isPresent(),
×
NEW
257
                    "Rule " + fileName + "/" + rule.getName() + " should not have '" + Rule.VIOLATION_SUPPRESS_XPATH_DESCRIPTOR.name() + "', this is intended for end user customization only.\n"
×
258
            ));
259
        }
×
NEW
260
        assertAll(checks);
×
UNCOV
261
    }
×
262

263
    /**
264
     * Verifies that all rulesets are valid XML according to the xsd schema.
265
     *
266
     * @throws Exception
267
     *             any error
268
     */
269
    @Test
270
    void testXmlSchema() throws Exception {
271
        boolean allValid = true;
×
272
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
273
        for (String fileName : ruleSetFileNames) {
×
274
            boolean valid = validateAgainstSchema(fileName);
×
275
            allValid = allValid && valid;
×
276
        }
×
277
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
278
    }
×
279

280
    /**
281
     * @deprecated Since 7.17.0. This method will be removed. PMD has the rule "MissingEncoding" for XML files that
282
     *             is used instead.
283
     */
284
    @Deprecated
285
    public static boolean hasCorrectEncoding(String fileName) throws IOException {
286
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
287
            // first bytes must be:
288
            byte[] expectedBytes = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes(StandardCharsets.UTF_8);
×
289
            byte[] bytes = new byte[expectedBytes.length];
×
290
            int count = inputStream.read(bytes);
×
291
            if (count != expectedBytes.length || !Arrays.equals(expectedBytes, bytes)) {
×
292
                return false;
×
293
            }
294
        }
×
295
        return true;
×
296
    }
297

298
    /**
299
     * Verifies that all rulesets are valid XML according to the DTD.
300
     *
301
     * @throws Exception
302
     *             any error
303
     */
304
    @Test
305
    void testDtd() throws Exception {
306
        boolean allValid = true;
×
307
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
308
        for (String fileName : ruleSetFileNames) {
×
309
            boolean valid = validateAgainstDtd(fileName);
×
310
            allValid = allValid && valid;
×
311
        }
×
312
        assertTrue(allValid, "All XML must parse without producing validation messages.");
×
313
    }
×
314

315
    /**
316
     * Reads and writes the rulesets to make sure, that no data is lost if the
317
     * rulesets are processed.
318
     *
319
     * @throws Exception
320
     *             any error
321
     */
322
    @Test
323
    void testReadWriteRoundTrip() throws Exception {
324

325
        List<String> ruleSetFileNames = getRuleSetFileNames();
×
326
        for (String fileName : ruleSetFileNames) {
×
327
            testRuleSet(fileName);
×
328
        }
×
329

330
    }
×
331

332
    // Gets all test PMD Ruleset XML files
333
    private List<String> getRuleSetFileNames() throws IOException {
334
        List<String> result = new ArrayList<>();
×
335

336
        for (Language language : LanguageRegistry.PMD.getLanguages()) {
×
337
            if (this.languagesToSkip.contains(language.getId())) {
×
338
                continue;
×
339
            }
340
            result.addAll(getRuleSetFileNames(language.getId()));
×
341
        }
×
342

343
        return result;
×
344
    }
345

346
    private List<String> getRuleSetFileNames(String language) throws IOException {
347
        List<String> ruleSetFileNames = new ArrayList<>();
×
348
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "rulesets/" + language + "/rulesets.properties"));
×
349
        ruleSetFileNames.addAll(getRuleSetFileNames(language, "category/" + language + "/categories.properties"));
×
350
        return ruleSetFileNames;
×
351
    }
352

353
    private List<String> getRuleSetFileNames(String language, String propertiesPath) throws IOException {
354
        List<String> ruleSetFileNames = new ArrayList<>();
×
355
        Properties properties = new Properties();
×
356
        @SuppressWarnings("PMD.CloseResource")
357
        InputStream input = loadResourceAsStream(propertiesPath);
×
358
        if (input == null) {
×
359
            // this might happen if a language is only support by CPD, but not
360
            // by PMD
361
            System.err.println("No rulesets found for language " + language + " at " + propertiesPath);
×
362
            return Collections.emptyList();
×
363
        }
364
        try (InputStream is = input) {
×
365
            properties.load(is);
×
366
        }
367
        String fileNames = properties.getProperty("rulesets.filenames");
×
368
        StringTokenizer st = new StringTokenizer(fileNames, ",");
×
369
        while (st.hasMoreTokens()) {
×
370
            ruleSetFileNames.add(st.nextToken());
×
371
        }
372
        return ruleSetFileNames;
×
373
    }
374

375
    private RuleSet loadRuleSetByFileName(String ruleSetFileName) {
376
        final StringBuilder messages = new StringBuilder();
×
377
        class Reporter extends MessageReporterBase {
×
378
            @Override
379
            protected void logImpl(Level level, String message) {
380
                messages.append(message).append(System.lineSeparator());
×
381
            }
×
382
        }
383

384
        RuleSet ruleSet = InternalApiBridge.withReporter(new RuleSetLoader(), new Reporter())
×
385
                .loadFromResource(ruleSetFileName);
×
386

387
        // normalize all line-endings to \n - in case we run under Windows...
388
        String allMessages = messages.toString().replaceAll("\\R", "\n");
×
389

390
        if (expectedMessagesPerRuleset.containsKey(ruleSetFileName)) {
×
391
            for (String expectedMessage : expectedMessagesPerRuleset.get(ruleSetFileName)) {
×
392
                assertThat(allMessages, containsString(expectedMessage));
×
393
                allMessages = allMessages.replace(expectedMessage, "");
×
394
            }
×
395
            assertThat("There should be no other warnings while loading the ruleset, but found: " + allMessages,
×
396
                    allMessages, emptyString());
×
397
        } else {
398
            assertThat("There should be no warnings while loading the ruleset, but found: " + allMessages,
×
399
                    allMessages, emptyString());
×
400
        }
401

402
        return ruleSet;
×
403
    }
404

405
    private boolean validateAgainstSchema(String fileName) throws IOException, SAXException {
406
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
407
            boolean valid = validateAgainstSchema(inputStream);
×
408
            if (!valid) {
×
409
                System.err.println("Validation against XML Schema failed for: " + fileName);
×
410
            }
411
            return valid;
×
412
        }
413
    }
414

415
    private boolean validateAgainstSchema(InputStream inputStream) throws IOException, SAXException {
416

417
        saxParser.parse(inputStream, validateDefaultHandler.resetValid());
×
418
        inputStream.close();
×
419
        return validateDefaultHandler.isValid();
×
420
    }
421

422
    private boolean validateAgainstDtd(String fileName) throws IOException, SAXException {
423
        try (InputStream inputStream = loadResourceAsStream(fileName)) {
×
424
            boolean valid = validateAgainstDtd(inputStream);
×
425
            if (!valid) {
×
426
                System.err.println("Validation against DTD failed for: " + fileName);
×
427
            }
428
            return valid;
×
429
        }
430
    }
431

432
    private boolean validateAgainstDtd(InputStream inputStream) throws IOException, SAXException {
433

434
        // Read file into memory
435
        String file = readFullyToString(inputStream);
×
436
        inputStream.close();
×
437

438
        String rulesetNamespace = RuleSetWriter.RULESET_2_0_0_NS_URI;
×
439

440
        // Remove XML Schema stuff, replace with DTD
441
        file = file.replaceAll("<\\?xml [ a-zA-Z0-9=\".-]*\\?>", "");
×
442
        file = file.replaceAll("xmlns=\"" + rulesetNamespace + "\"", "");
×
443
        file = file.replaceAll("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"", "");
×
444
        file = file.replaceAll("xsi:schemaLocation=\"" + rulesetNamespace
×
445
                + " https://pmd.sourceforge.io/ruleset_\\d_0_0.xsd\"", "");
446

447
        if (RuleSetWriter.RULESET_2_0_0_NS_URI.equals(rulesetNamespace)) {
×
448
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
449
                + "<!DOCTYPE ruleset SYSTEM \"https://pmd.sourceforge.io/ruleset_2_0_0.dtd\">" + System.lineSeparator()
×
450
                + file;
451
        } else {
452
            file = "<?xml version=\"1.0\"?>" + System.lineSeparator()
×
453
                + "<!DOCTYPE ruleset>" + System.lineSeparator()
×
454
                + file;
455
        }
456

457
        try (InputStream modifiedStream = new ByteArrayInputStream(file.getBytes(StandardCharsets.UTF_8))) {
×
458
            saxParser.parse(modifiedStream, validateDefaultHandler.resetValid());
×
459
        }
460
        return validateDefaultHandler.isValid();
×
461
    }
462

463
    private String readFullyToString(InputStream inputStream) throws IOException {
464
        StringBuilder buf = new StringBuilder(64 * 1024);
×
465
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
×
466
            String line;
467
            while ((line = reader.readLine()) != null) {
×
468
                buf.append(line);
×
469
                buf.append(System.lineSeparator());
×
470
            }
471
            return buf.toString();
×
472
        }
473
    }
474

475
    private static InputStream loadResourceAsStream(String resource) {
476
        return AbstractRuleSetFactoryTest.class.getClassLoader().getResourceAsStream(resource);
×
477
    }
478

479
    private void testRuleSet(String fileName) throws IOException, SAXException {
480

481
        // Load original XML
482
        // String xml1 =
483
        // readFullyToString(ResourceLoader.loadResourceAsStream(fileName));
484
        // System.out.println("xml1: " + xml1);
485

486
        // Load the original RuleSet
487
        RuleSet ruleSet1 = loadRuleSetByFileName(fileName);
×
488

489
        // Write to XML, first time
490
        ByteArrayOutputStream outputStream1 = new ByteArrayOutputStream();
×
491
        RuleSetWriter writer1 = new RuleSetWriter(outputStream1);
×
492
        writer1.write(ruleSet1);
×
493
        writer1.close();
×
494
        String xml2 = new String(outputStream1.toByteArray(), StandardCharsets.UTF_8);
×
495
        // System.out.println("xml2: " + xml2);
496

497
        // Read RuleSet from XML, first time
498
        RuleSetLoader loader = new RuleSetLoader();
×
499
        RuleSet ruleSet2 = loader.loadFromString("readRuleSet1.xml", xml2);
×
500

501
        // Do write/read a 2nd time, just to be sure
502

503
        // Write to XML, second time
504
        ByteArrayOutputStream outputStream2 = new ByteArrayOutputStream();
×
505
        RuleSetWriter writer2 = new RuleSetWriter(outputStream2);
×
506
        writer2.write(ruleSet2);
×
507
        writer2.close();
×
508
        String xml3 = new String(outputStream2.toByteArray(), StandardCharsets.UTF_8);
×
509
        // System.out.println("xml3: " + xml3);
510

511
        // Read RuleSet from XML, second time
512
        RuleSet ruleSet3 = loader.loadFromString("readRuleSet2.xml", xml3);
×
513

514
        // The 2 written XMLs should all be valid w.r.t Schema/DTD
515
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml2.getBytes(StandardCharsets.UTF_8))),
×
516
                "1st roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
517
        assertTrue(validateAgainstSchema(new ByteArrayInputStream(xml3.getBytes(StandardCharsets.UTF_8))),
×
518
                "2nd roundtrip RuleSet XML is not valid against Schema (filename: " + fileName + ")");
519
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml2.getBytes(StandardCharsets.UTF_8))),
×
520
                "1st roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
521
        assertTrue(validateAgainstDtd(new ByteArrayInputStream(xml3.getBytes(StandardCharsets.UTF_8))),
×
522
                "2nd roundtrip RuleSet XML is not valid against DTD (filename: " + fileName + ")");
523

524
        // All 3 versions of the RuleSet should be the same
525
        assertEqualsRuleSet("Original RuleSet and 1st roundtrip Ruleset not the same (filename: " + fileName + ")",
×
526
                ruleSet1, ruleSet2);
527
        assertEqualsRuleSet("1st roundtrip Ruleset and 2nd roundtrip RuleSet not the same (filename: " + fileName + ")",
×
528
                ruleSet2, ruleSet3);
529

530
        // It's hard to compare the XML DOMs. At least the roundtrip ones should
531
        // textually be the same.
532
        assertEquals(xml2, xml3,
×
533
                "1st roundtrip RuleSet XML and 2nd roundtrip RuleSet XML (filename: " + fileName + ")");
534
    }
×
535

536
    private void assertEqualsRuleSet(String message, RuleSet ruleSet1, RuleSet ruleSet2) {
537
        assertEquals(ruleSet1.getName(), ruleSet2.getName(), message + ", RuleSet name");
×
538
        assertEquals(ruleSet1.getDescription(), ruleSet2.getDescription(), message + ", RuleSet description");
×
539
        assertEquals(ruleSet1.getFileExclusions(), ruleSet2.getFileExclusions(),
×
540
                message + ", RuleSet exclude patterns");
541
        assertEquals(ruleSet1.getFileInclusions(), ruleSet2.getFileInclusions(),
×
542
                message + ", RuleSet include patterns");
543
        assertEquals(ruleSet1.getRules().size(), ruleSet2.getRules().size(), message + ", RuleSet rule count");
×
544

545
        for (int i = 0; i < ruleSet1.getRules().size(); i++) {
×
546
            Rule rule1 = ((List<Rule>) ruleSet1.getRules()).get(i);
×
547
            Rule rule2 = ((List<Rule>) ruleSet2.getRules()).get(i);
×
548

549
            assertFalse(rule1 instanceof RuleReference != rule2 instanceof RuleReference,
×
550
                    message + ", Different RuleReference");
551

552
            if (rule1 instanceof RuleReference) {
×
553
                RuleReference ruleReference1 = (RuleReference) rule1;
×
554
                RuleReference ruleReference2 = (RuleReference) rule2;
×
555
                assertEquals(ruleReference1.getOverriddenMinimumLanguageVersion(),
×
556
                        ruleReference2.getOverriddenMinimumLanguageVersion(),
×
557
                        message + ", RuleReference overridden minimum language version");
558
                assertEquals(ruleReference1.getOverriddenMaximumLanguageVersion(),
×
559
                        ruleReference2.getOverriddenMaximumLanguageVersion(),
×
560
                        message + ", RuleReference overridden maximum language version");
561
                assertEquals(ruleReference1.isOverriddenDeprecated(), ruleReference2.isOverriddenDeprecated(),
×
562
                        message + ", RuleReference overridden deprecated");
563
                assertEquals(ruleReference1.getOverriddenName(), ruleReference2.getOverriddenName(),
×
564
                        message + ", RuleReference overridden name");
565
                assertEquals(ruleReference1.getOverriddenDescription(), ruleReference2.getOverriddenDescription(),
×
566
                        message + ", RuleReference overridden description");
567
                assertEquals(ruleReference1.getOverriddenMessage(), ruleReference2.getOverriddenMessage(),
×
568
                        message + ", RuleReference overridden message");
569
                assertEquals(ruleReference1.getOverriddenExternalInfoUrl(), ruleReference2.getOverriddenExternalInfoUrl(),
×
570
                        message + ", RuleReference overridden external info url");
571
                assertEquals(ruleReference1.getOverriddenPriority(), ruleReference2.getOverriddenPriority(),
×
572
                        message + ", RuleReference overridden priority");
573
                assertEquals(ruleReference1.getOverriddenExamples(), ruleReference2.getOverriddenExamples(),
×
574
                        message + ", RuleReference overridden examples");
575
            }
576

577
            assertEquals(rule1.getName(), rule2.getName(), message + ", Rule name");
×
578
            assertEquals(rule1.getRuleClass(), rule2.getRuleClass(), message + ", Rule class");
×
579
            assertEquals(rule1.getDescription(), rule2.getDescription(),
×
580
                    message + ", Rule description " + rule1.getName());
×
581
            assertEquals(rule1.getMessage(), rule2.getMessage(), message + ", Rule message");
×
582
            assertEquals(rule1.getExternalInfoUrl(), rule2.getExternalInfoUrl(), message + ", Rule external info url");
×
583
            assertEquals(rule1.getPriority(), rule2.getPriority(), message + ", Rule priority");
×
584
            assertEquals(rule1.getExamples(), rule2.getExamples(), message + ", Rule examples");
×
585

586
            List<PropertyDescriptor<?>> propertyDescriptors1 = rule1.getPropertyDescriptors();
×
587
            List<PropertyDescriptor<?>> propertyDescriptors2 = rule2.getPropertyDescriptors();
×
588
            assertEquals(propertyDescriptors1, propertyDescriptors2, message + ", Rule property descriptor ");
×
589
            for (int j = 0; j < propertyDescriptors1.size(); j++) {
×
590
                Object value1 = rule1.getProperty(propertyDescriptors1.get(j));
×
591
                Object value2 = rule2.getProperty(propertyDescriptors2.get(j));
×
592
                // special case for Pattern, there is no equals method
593
                if (value1 instanceof Pattern && value2 instanceof Pattern) {
×
594
                    value1 = ((Pattern) value1).pattern();
×
595
                    value2 = ((Pattern) value2).pattern();
×
596
                }
597
                assertEquals(value1, value2, message + ", Rule " + rule1.getName() + " property "
×
598
                    + propertyDescriptors1.get(j).name());
×
599
            }
600
            assertEquals(propertyDescriptors1.size(), propertyDescriptors2.size(),
×
601
                    message + ", Rule property descriptor count");
602
        }
603
    }
×
604

605
    private List<Rule> skipReferences(RuleSet ruleSet) {
NEW
606
        return ruleSet.getRules().stream()
×
NEW
607
                .filter(r -> !(r instanceof RuleReference))
×
NEW
608
                .collect(Collectors.toList());
×
609
    }
610

611
    /**
612
     * Validator for the SAX parser
613
     */
614
    private static class ValidateDefaultHandler extends DefaultHandler {
615
        private boolean valid = true;
×
616
        private final Map<String, String> schemaMapping;
617

618
        ValidateDefaultHandler() {
×
619
            schemaMapping = new HashMap<>();
×
620
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.xsd", "ruleset_2_0_0.xsd");
×
621
            schemaMapping.put("https://pmd.sourceforge.io/ruleset_2_0_0.dtd", "ruleset_2_0_0.dtd");
×
622
        }
×
623

624
        public ValidateDefaultHandler resetValid() {
625
            valid = true;
×
626
            return this;
×
627
        }
628

629
        public boolean isValid() {
630
            return valid;
×
631
        }
632

633
        @Override
634
        public void error(SAXParseException e) {
635
            log("Error", e);
×
636
        }
×
637

638
        @Override
639
        public void fatalError(SAXParseException e) {
640
            log("FatalError", e);
×
641
        }
×
642

643
        @Override
644
        public void warning(SAXParseException e) {
645
            log("Warning", e);
×
646
        }
×
647

648
        private void log(String prefix, SAXParseException e) {
649
            String message = prefix + " at (" + e.getLineNumber() + ", " + e.getColumnNumber() + "): " + e.getMessage();
×
650
            System.err.println(message);
×
651
            valid = false;
×
652
        }
×
653

654
        @Override
655
        public InputSource resolveEntity(String publicId, String systemId) throws IOException {
656
            String resource = schemaMapping.get(systemId);
×
657

658
            if (resource != null) {
×
659
                InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource);
×
660
                if (inputStream == null) {
×
661
                    throw new FileNotFoundException(resource);
×
662
                }
663
                return new InputSource(inputStream);
×
664
            }
665
            throw new IllegalArgumentException(
×
666
                    "No clue how to handle: publicId=" + publicId + ", systemId=" + systemId);
667
        }
668
    }
669

670
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc