[doc] Add unit test for rule doc generator

Therefore the file writer has been mocked. The rulesets to be
documented are now given, rather than determined by the generator.
This commit is contained in:
Andreas Dangel
2017-08-11 11:32:05 +02:00
parent 0b9b7e2a5d
commit d6c23fd17f
9 changed files with 337 additions and 26 deletions

View File

@ -0,0 +1,19 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.docs;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
public class DefaultFileWriter implements FileWriter {
@Override
public void write(Path path, List<String> lines) throws IOException {
Files.createDirectories(path.getParent());
Files.write(path, lines, StandardCharsets.UTF_8);
}
}

View File

@ -0,0 +1,14 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.docs;
import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
public interface FileWriter {
void write(Path path, List<String> lines) throws IOException;
}

View File

@ -6,18 +6,27 @@ package net.sourceforge.pmd.docs;
import java.nio.file.FileSystems;
import java.nio.file.Path;
import java.util.Iterator;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetFactory;
import net.sourceforge.pmd.RuleSetNotFoundException;
public class GenerateRuleDocsCmd {
private GenerateRuleDocsCmd() {
// Utility class
}
public static void main(String[] args) {
public static void main(String[] args) throws RuleSetNotFoundException {
long start = System.currentTimeMillis();
RuleDocGenerator generator = new RuleDocGenerator();
Path output = FileSystems.getDefault().getPath(args[0]).resolve("..").toAbsolutePath().normalize();
System.out.println("Generating docs into " + output);
generator.generate(output);
RuleDocGenerator generator = new RuleDocGenerator(new DefaultFileWriter(), output);
RuleSetFactory ruleSetFactory = new RuleSetFactory();
Iterator<RuleSet> registeredRuleSets = ruleSetFactory.getRegisteredRuleSets();
generator.generate(registeredRuleSets);
System.out.println("Generated docs in " + (System.currentTimeMillis() - start) + " ms");
}
}

View File

@ -5,7 +5,6 @@
package net.sourceforge.pmd.docs;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
@ -29,7 +28,6 @@ import org.apache.commons.lang3.StringUtils;
import net.sourceforge.pmd.PropertyDescriptor;
import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetFactory;
import net.sourceforge.pmd.RuleSetNotFoundException;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.rule.XPathRule;
@ -40,21 +38,25 @@ public class RuleDocGenerator {
private static final String RULESET_INDEX_FILENAME_PATTERN = "docs/pages/pmd/rules/${language.tersename}/${ruleset.name}.md";
private static final String RULESET_INDEX_PERMALINK_PATTERN = "pmd_rules_${language.tersename}_${ruleset.name}.html";
private Path root;
private final Path root;
private final FileWriter writer;
public void generate(Path root) {
public RuleDocGenerator(FileWriter writer, Path root) {
this.root = Objects.requireNonNull(root, "Root directory must be provided");
this.writer = Objects.requireNonNull(writer, "A file writer must be provided");
Path docsDir = root.resolve("docs");
if (!Files.exists(docsDir) || !Files.isDirectory(docsDir)) {
throw new IllegalArgumentException("Couldn't find \"docs\" subdirectory");
}
}
Map<Language, List<RuleSet>> rulesets;
public void generate(Iterator<RuleSet> rulesets) {
Map<Language, List<RuleSet>> sortedRulesets;
try {
rulesets = loadAndSortRulesets();
generateLanguageIndex(rulesets);
generateRuleSetIndex(rulesets);
sortedRulesets = sortRulesets(rulesets);
generateLanguageIndex(sortedRulesets);
generateRuleSetIndex(sortedRulesets);
} catch (RuleSetNotFoundException | IOException e) {
throw new RuntimeException(e);
@ -65,23 +67,20 @@ public class RuleDocGenerator {
return root.resolve(FilenameUtils.normalize(filename));
}
private Map<Language, List<RuleSet>> loadAndSortRulesets() throws RuleSetNotFoundException {
RuleSetFactory ruleSetFactory = new RuleSetFactory();
Iterator<RuleSet> registeredRuleSets = ruleSetFactory.getRegisteredRuleSets();
private Map<Language, List<RuleSet>> sortRulesets(Iterator<RuleSet> rulesets) throws RuleSetNotFoundException {
Map<Language, List<RuleSet>> rulesetsByLanguage = new HashMap<>();
Map<Language, List<RuleSet>> rulesets = new HashMap<>();
while (registeredRuleSets.hasNext()) {
RuleSet ruleset = registeredRuleSets.next();
while (rulesets.hasNext()) {
RuleSet ruleset = rulesets.next();
Language language = getRuleSetLanguage(ruleset);
if (!rulesets.containsKey(language)) {
rulesets.put(language, new ArrayList<RuleSet>());
if (!rulesetsByLanguage.containsKey(language)) {
rulesetsByLanguage.put(language, new ArrayList<RuleSet>());
}
rulesets.get(language).add(ruleset);
rulesetsByLanguage.get(language).add(ruleset);
}
for (List<RuleSet> rulesetsOfOneLanguage : rulesets.values()) {
for (List<RuleSet> rulesetsOfOneLanguage : rulesetsByLanguage.values()) {
Collections.sort(rulesetsOfOneLanguage, new Comparator<RuleSet>() {
@Override
public int compare(RuleSet o1, RuleSet o2) {
@ -89,7 +88,7 @@ public class RuleDocGenerator {
}
});
}
return rulesets;
return rulesetsByLanguage;
}
/**
@ -152,7 +151,7 @@ public class RuleDocGenerator {
}
System.out.println("Generated " + path);
Files.write(path, lines, StandardCharsets.UTF_8);
writer.write(path, lines);
}
}
@ -219,6 +218,7 @@ public class RuleDocGenerator {
for (Rule rule : getSortedRules(ruleset)) {
lines.add("## " + rule.getName());
lines.add("");
if (rule.getSince() != null) {
lines.add("**Since:** " + rule.getSince());
lines.add("");
@ -230,6 +230,7 @@ public class RuleDocGenerator {
lines.add("");
if (!rule.getExamples().isEmpty()) {
lines.add("**Example(s):**");
lines.add("");
for (String example : rule.getExamples()) {
lines.add("```");
lines.add(StringUtils.stripToEmpty(example));
@ -260,8 +261,7 @@ public class RuleDocGenerator {
}
}
Files.createDirectories(path.getParent());
Files.write(path, lines, StandardCharsets.UTF_8);
writer.write(path, lines);
System.out.println("Generated " + path);
}
}

View File

@ -0,0 +1,46 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.docs;
import java.io.IOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
public class MockedFileWriter implements FileWriter {
public static class FileEntry {
private String filename;
private String content;
public String getFilename() {
return filename;
}
public String getContent() {
return content;
}
}
private List<FileEntry> data = new ArrayList<>();
@Override
public void write(Path path, List<String> lines) throws IOException {
FileEntry entry = new FileEntry();
entry.filename = path.toString();
entry.content = StringUtils.join(lines, System.getProperty("line.separator"));
data.add(entry);
}
public List<FileEntry> getData() {
return data;
}
public void reset() {
data.clear();
}
}

View File

@ -0,0 +1,64 @@
/**
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.docs;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import net.sourceforge.pmd.RuleSet;
import net.sourceforge.pmd.RuleSetFactory;
import net.sourceforge.pmd.RuleSetNotFoundException;
import net.sourceforge.pmd.docs.MockedFileWriter.FileEntry;
public class RuleDocGeneratorTest {
private MockedFileWriter writer = new MockedFileWriter();
private Path root;
@Before
public void setup() throws IOException {
writer.reset();
root = Files.createTempDirectory("pmd-ruledocgenerator-test");
Files.createDirectory(root.resolve("docs"));
}
@After
public void cleanup() throws IOException {
Files.delete(root.resolve("docs"));
Files.delete(root);
}
@Test
public void testSingleRuleset() throws RuleSetNotFoundException, IOException {
RuleDocGenerator generator = new RuleDocGenerator(writer, root);
RuleSetFactory rsf = new RuleSetFactory();
RuleSet ruleset = rsf.createRuleSet("rulesets/ruledoctest/sample.xml");
generator.generate(Arrays.asList(ruleset).iterator());
assertEquals(2, writer.getData().size());
FileEntry languageIndex = writer.getData().get(0);
assertTrue(languageIndex.getFilename().endsWith("docs/pages/pmd/rules/java.md"));
assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/java.md")),
languageIndex.getContent());
FileEntry ruleSetIndex = writer.getData().get(1);
assertTrue(ruleSetIndex.getFilename().endsWith("docs/pages/pmd/rules/java/sample.md"));
assertEquals(IOUtils.toString(RuleDocGeneratorTest.class.getResourceAsStream("/expected/sample.md")),
ruleSetIndex.getContent());
}
}

View File

@ -0,0 +1,12 @@
---
title: Java Rules
permalink: pmd_rules_java.html
folder: pmd/rules
---
List of rulesets and rules contained in each ruleset.
* [Sample](pmd_rules_java_sample.html): Sample ruleset to test rule doc generation.
## Sample
* [JumbledIncrementer](pmd_rules_java_sample.html#jumbledincrementer): Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional.
* [OverrideBothEqualsAndHashcode](pmd_rules_java_sample.html#overridebothequalsandhashcode): Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or ov...

View File

@ -0,0 +1,62 @@
---
title: Sample
summary: Sample ruleset to test rule doc generation.
permalink: pmd_rules_java_sample.html
folder: pmd/rules/java
sidebaractiveurl: /pmd_rules_java.html
editmepath: ../rulesets/ruledoctest/sample.xml
---
## JumbledIncrementer
**Since:** 1.0
**Priority:** Medium (3)
Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional.
**Example(s):**
```
public class JumbledIncrementerRule1 {
public void foo() {
for (int i = 0; i < 10; i++) { // only references 'i'
for (int k = 0; k < 20; i++) { // references both 'i' and 'k'
System.out.println("Hello");
}
}
}
}
```
## OverrideBothEqualsAndHashcode
**Since:** 0.4
**Priority:** Medium (3)
Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass.
**Example(s):**
```
public class Bar { // poor, missing a hashcode() method
public boolean equals(Object o) {
// do some comparison
}
}
public class Baz { // poor, missing an equals() method
public int hashCode() {
// return some hash value
}
}
public class Foo { // perfect, both methods provided
public boolean equals(Object other) {
// do some comparison
}
public int hashCode() {
// return some hash value
}
}
```

View File

@ -0,0 +1,85 @@
<?xml version="1.0"?>
<ruleset name="Sample"
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>
Sample ruleset to test rule doc generation.
</description>
<rule name="OverrideBothEqualsAndHashcode"
language="java"
since="0.4"
message="Ensure you override both equals() and hashCode()"
class="net.sourceforge.pmd.lang.java.rule.basic.OverrideBothEqualsAndHashcodeRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_sample.html#overridebothequalsandhashcode">
<description>
Override both public boolean Object.equals(Object other), and public int Object.hashCode(), or override neither. Even if you are inheriting a hashCode() from a parent class, consider implementing hashCode and explicitly delegating to your superclass.
</description>
<priority>3</priority>
<example>
<![CDATA[
public class Bar { // poor, missing a hashcode() method
public boolean equals(Object o) {
// do some comparison
}
}
public class Baz { // poor, missing an equals() method
public int hashCode() {
// return some hash value
}
}
public class Foo { // perfect, both methods provided
public boolean equals(Object other) {
// do some comparison
}
public int hashCode() {
// return some hash value
}
}
]]>
</example>
</rule>
<rule name="JumbledIncrementer"
language="java"
since="1.0"
message="Avoid modifying an outer loop incrementer in an inner loop for update expression"
class="net.sourceforge.pmd.lang.rule.XPathRule"
externalInfoUrl="${pmd.website.baseurl}/pmd_rules_java_sample.html#jumbledincrementer">
<description>
Avoid jumbled loop incrementers - its usually a mistake, and is confusing even if intentional.
</description>
<priority>3</priority>
<properties>
<property name="xpath">
<value>
<![CDATA[
//ForStatement
[
ForUpdate/StatementExpressionList/StatementExpression/PostfixExpression/PrimaryExpression/PrimaryPrefix/Name/@Image
=
ancestor::ForStatement/ForInit//VariableDeclaratorId/@Image
]
]]>
</value>
</property>
</properties>
<example>
<![CDATA[
public class JumbledIncrementerRule1 {
public void foo() {
for (int i = 0; i < 10; i++) { // only references 'i'
for (int k = 0; k < 20; i++) { // references both 'i' and 'k'
System.out.println("Hello");
}
}
}
}
]]>
</example>
</rule>
</ruleset>