View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.internal;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  
24  import java.beans.PropertyDescriptor;
25  import java.io.File;
26  import java.io.IOException;
27  import java.nio.file.Files;
28  import java.nio.file.Path;
29  import java.util.ArrayList;
30  import java.util.Arrays;
31  import java.util.Collections;
32  import java.util.List;
33  import java.util.Map;
34  import java.util.Set;
35  import java.util.stream.Collectors;
36  import java.util.stream.Stream;
37  
38  import org.apache.commons.beanutils.PropertyUtils;
39  import org.junit.jupiter.api.Test;
40  
41  import com.puppycrawl.tools.checkstyle.AbstractPathTestSupport;
42  import com.puppycrawl.tools.checkstyle.internal.utils.CheckUtil;
43  import com.puppycrawl.tools.checkstyle.internal.utils.XdocUtil;
44  
45  public class XdocsExampleFileTest {
46  
47      private static final Set<String> COMMON_PROPERTIES = Set.of(
48          "severity",
49          "id",
50          "fileExtensions",
51          "tabWidth",
52          "fileContents",
53          "tokens",
54          "javadocTokens",
55          "violateExecutionOnNonTightHtml"
56      );
57  
58      @Test
59      public void testAllCheckPropertiesAreUsedInXdocsExamples() throws Exception {
60          final Map<String, Set<String>> usedPropertiesByCheck =
61              XdocUtil.extractUsedPropertiesFromXdocsExamples();
62          final List<String> failures = new ArrayList<>();
63  
64          for (Class<?> checkClass : CheckUtil.getCheckstyleChecks()) {
65              final String checkSimpleName = checkClass.getSimpleName();
66  
67              final Set<String> definedProperties = Arrays.stream(
68                      PropertyUtils.getPropertyDescriptors(checkClass))
69                  .filter(descriptor -> descriptor.getWriteMethod() != null)
70                  .map(PropertyDescriptor::getName)
71                  .filter(property -> !COMMON_PROPERTIES.contains(property))
72                  .collect(Collectors.toUnmodifiableSet());
73  
74              final Set<String> usedProperties =
75                  usedPropertiesByCheck.getOrDefault(checkSimpleName, Collections.emptySet());
76  
77              for (String property : definedProperties) {
78                  if (!usedProperties.contains(property)) {
79                      failures.add("Missing property in xdoc: '"
80                              + property + "' of " + checkSimpleName);
81                  }
82              }
83          }
84          if (!failures.isEmpty()) {
85              assertWithMessage("Xdocs are missing properties:\n" + String.join("\n", failures))
86                      .fail();
87          }
88      }
89  
90      @Test
91      public void testAllExampleFilesHaveCorrespondingTestMethods() throws Exception {
92          final Path examplesResources = Path.of("src/xdocs-examples/resources");
93          final Path examplesNonCompilable = Path.of("src/xdocs-examples/resources-noncompilable");
94          final Path examplesTestRoot = Path.of(
95              "src/xdocs-examples/java/com/puppycrawl/tools/checkstyle/checks");
96          final List<String> failures = new ArrayList<>();
97  
98          try (Stream<Path> testFiles = Files.walk(examplesTestRoot)) {
99              testFiles
100                 .filter(path -> path.toString().endsWith("ExamplesTest.java"))
101                 .forEach(testFile -> {
102                     try {
103                         scanFile(testFile, examplesResources, examplesNonCompilable, failures);
104                     }
105                     catch (IOException exception) {
106                         throw new IllegalStateException("Error processing: "
107                                      + testFile, exception);
108                     }
109                 });
110         }
111         if (!failures.isEmpty()) {
112             assertWithMessage("Example files are missing corresponding test methods:\n"
113                     + String.join("\n", failures))
114                     .fail();
115         }
116     }
117 
118     private static void scanFile(Path testFile, Path examplesResources, Path examplesNonCompilable,
119             List<String> failures)
120             throws IOException {
121         final String testContent = Files.readString(testFile);
122 
123         final String className = Path.of("src/xdocs-examples/java").toAbsolutePath()
124                 .relativize(testFile.toAbsolutePath()).toString()
125                 .replace(File.separator, ".")
126                 .replaceFirst("\\.java$", "");
127 
128         try {
129             final Class<?> testClass = Class.forName(className);
130             final AbstractPathTestSupport instance = (AbstractPathTestSupport) testClass
131                     .getDeclaredConstructor().newInstance();
132             final String packageLocation = instance.getPackageLocation();
133 
134             scanExampleDirectory(examplesResources.resolve(packageLocation),
135                     testContent, testFile, failures);
136             scanExampleDirectory(examplesNonCompilable.resolve(packageLocation),
137                     testContent, testFile, failures);
138         }
139         catch (ReflectiveOperationException exception) {
140             throw new IllegalStateException("Failed to instantiate " + className, exception);
141         }
142     }
143 
144     private static void scanExampleDirectory(Path exampleDir, String testContent,
145             Path testFile, List<String> failures) throws IOException {
146         if (Files.exists(exampleDir) && Files.isDirectory(exampleDir)) {
147             try (Stream<Path> exampleFiles = Files.list(exampleDir)) {
148                 exampleFiles
149                     .filter(path -> {
150                         final String fileName = path.getFileName()
151                                 .toString();
152                         return fileName.matches("Example\\d+\\.java");
153                     })
154                     .forEach(exampleFile -> {
155                         final String fileName = exampleFile.getFileName()
156                                 .toString();
157                         if (!testContent.contains("\"" + fileName + "\"")) {
158                             failures.add("Missing test for " + fileName + " in "
159                                         + testFile.getFileName());
160                         }
161                     });
162             }
163         }
164     }
165 }