1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 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;
21  
22  import static org.junit.Assert.assertEquals;
23  import static org.junit.Assert.assertSame;
24  import static org.junit.Assert.assertTrue;
25  import static org.junit.Assert.fail;
26  
27  import java.io.File;
28  import java.lang.reflect.Constructor;
29  import java.lang.reflect.InvocationTargetException;
30  import java.lang.reflect.Method;
31  import java.nio.file.Files;
32  import java.nio.file.Paths;
33  import java.util.ArrayList;
34  import java.util.List;
35  import java.util.Properties;
36  
37  import org.junit.Test;
38  import org.powermock.reflect.Whitebox;
39  import org.xml.sax.InputSource;
40  import org.xml.sax.SAXException;
41  
42  import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
43  import com.puppycrawl.tools.checkstyle.api.Configuration;
44  
45  /**
46   * Unit test for ConfigurationLoader.
47   */
48  public class ConfigurationLoaderTest extends AbstractPathTestSupport {
49  
50      @Override
51      protected String getPackageLocation() {
52          return "com/puppycrawl/tools/checkstyle/configurationloader";
53      }
54  
55      private Configuration loadConfiguration(String name) throws Exception {
56          return loadConfiguration(name, new Properties());
57      }
58  
59      private Configuration loadConfiguration(
60          String name, Properties props) throws Exception {
61          final String fName = getPath(name);
62  
63          return ConfigurationLoader.loadConfiguration(fName, new PropertiesExpander(props));
64      }
65  
66      /**
67       * Non meaningful javadoc just to contain "noinspection" tag.
68       * Till https://youtrack.jetbrains.com/issue/IDEA-187209
69       * @return method class
70       * @throws Exception if smth wrong
71       * @noinspection JavaReflectionMemberAccess
72       */
73      private static Method getReplacePropertiesMethod() throws Exception {
74          final Class<?>[] params = new Class<?>[3];
75          params[0] = String.class;
76          params[1] = PropertyResolver.class;
77          params[2] = String.class;
78          final Class<ConfigurationLoader> configurationLoaderClass = ConfigurationLoader.class;
79          final Method replacePropertiesMethod =
80              configurationLoaderClass.getDeclaredMethod("replaceProperties", params);
81          replacePropertiesMethod.setAccessible(true);
82          return replacePropertiesMethod;
83      }
84  
85      @Test
86      public void testResourceLoadConfiguration() throws Exception {
87          final Properties props = new Properties();
88          props.setProperty("checkstyle.basedir", "basedir");
89  
90          // load config that's only found in the classpath
91          final DefaultConfiguration config =
92              (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
93                  getPath("InputConfigurationLoaderChecks.xml"), new PropertiesExpander(props));
94  
95          //verify the root, and property substitution
96          final Properties attributes = new Properties();
97          attributes.setProperty("tabWidth", "4");
98          attributes.setProperty("basedir", "basedir");
99          verifyConfigNode(config, "Checker", 3, attributes);
100     }
101 
102     @Test
103     public void testResourceLoadConfigurationWithMultiThreadConfiguration() throws Exception {
104         final Properties props = new Properties();
105         props.setProperty("checkstyle.basedir", "basedir");
106 
107         final PropertiesExpander propertiesExpander = new PropertiesExpander(props);
108         final String configPath = getPath("InputConfigurationLoaderChecks.xml");
109         final ThreadModeSettings multiThreadModeSettings =
110             new ThreadModeSettings(4, 2);
111 
112         try {
113             ConfigurationLoader.loadConfiguration(
114                 configPath, propertiesExpander, multiThreadModeSettings);
115             fail("An exception is expected");
116         }
117         catch (IllegalArgumentException ex) {
118             assertEquals("Invalid exception message",
119                 "Multi thread mode for Checker module is not implemented",
120                 ex.getMessage());
121         }
122     }
123 
124     @Test
125     public void testResourceLoadConfigurationWithSingleThreadConfiguration() throws Exception {
126         final Properties props = new Properties();
127         props.setProperty("checkstyle.basedir", "basedir");
128 
129         final PropertiesExpander propertiesExpander = new PropertiesExpander(props);
130         final String configPath = getPath("InputConfigurationLoaderChecks.xml");
131         final ThreadModeSettings singleThreadModeSettings =
132             ThreadModeSettings.SINGLE_THREAD_MODE_INSTANCE;
133 
134         final DefaultConfiguration config =
135             (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
136                 configPath, propertiesExpander, singleThreadModeSettings);
137 
138         final Properties attributes = new Properties();
139         attributes.setProperty("tabWidth", "4");
140         attributes.setProperty("basedir", "basedir");
141         verifyConfigNode(config, "Checker", 3, attributes);
142     }
143 
144     @Test
145     public void testEmptyConfiguration() throws Exception {
146         final DefaultConfiguration config =
147             (DefaultConfiguration) loadConfiguration("InputConfigurationLoaderEmpty.xml");
148         verifyConfigNode(config, "Checker", 0, new Properties());
149     }
150 
151     @Test
152     public void testEmptyModuleResolver() throws Exception {
153         final DefaultConfiguration config =
154             (DefaultConfiguration) loadConfiguration(
155                 "InputConfigurationLoaderEmpty.xml", new Properties());
156         verifyConfigNode(config, "Checker", 0, new Properties());
157     }
158 
159     @Test
160     public void testMissingPropertyName() throws Exception {
161         try {
162             loadConfiguration("InputConfigurationLoaderMissingPropertyName.xml");
163             fail("missing property name");
164         }
165         catch (CheckstyleException ex) {
166             assertTrue("Invalid exception message: " + ex.getMessage(),
167                     ex.getMessage().contains("\"name\""));
168             assertTrue("Invalid exception message: " + ex.getMessage(),
169                     ex.getMessage().contains("\"property\""));
170             assertTrue("Invalid exception message: " + ex.getMessage(),
171                     ex.getMessage().endsWith(":8:41"));
172         }
173     }
174 
175     @Test
176     public void testMissingPropertyNameInMethodWithBooleanParameter() throws Exception {
177         try {
178             final String fName = getPath("InputConfigurationLoaderMissingPropertyName.xml");
179             ConfigurationLoader.loadConfiguration(fName, new PropertiesExpander(new Properties()),
180                     false);
181 
182             fail("missing property name");
183         }
184         catch (CheckstyleException ex) {
185             assertTrue("Invalid exception message: " + ex.getMessage(),
186                     ex.getMessage().contains("\"name\""));
187             assertTrue("Invalid exception message: " + ex.getMessage(),
188                     ex.getMessage().contains("\"property\""));
189             assertTrue("Invalid exception message: " + ex.getMessage(),
190                     ex.getMessage().endsWith(":8:41"));
191         }
192     }
193 
194     @Test
195     public void testMissingPropertyValue() throws Exception {
196         try {
197             loadConfiguration("InputConfigurationLoaderMissingPropertyValue.xml");
198             fail("missing property value");
199         }
200         catch (CheckstyleException ex) {
201             assertTrue("Invalid exception message: " + ex.getMessage(),
202                     ex.getMessage().contains("\"value\""));
203             assertTrue("Invalid exception message: " + ex.getMessage(),
204                     ex.getMessage().contains("\"property\""));
205             assertTrue("Invalid exception message: " + ex.getMessage(),
206                     ex.getMessage().endsWith(":8:43"));
207         }
208     }
209 
210     @Test
211     public void testMissingConfigName() throws Exception {
212         try {
213             loadConfiguration("InputConfigurationLoaderMissingConfigName.xml");
214             fail("missing module name");
215         }
216         catch (CheckstyleException ex) {
217             assertTrue("Invalid exception message: " + ex.getMessage(),
218                     ex.getMessage().contains("\"name\""));
219             assertTrue("Invalid exception message: " + ex.getMessage(),
220                     ex.getMessage().contains("\"module\""));
221             assertTrue("Invalid exception message: " + ex.getMessage(),
222                     ex.getMessage().endsWith(":7:23"));
223         }
224     }
225 
226     @Test
227     public void testMissingConfigParent() throws Exception {
228         try {
229             loadConfiguration("InputConfigurationLoaderMissingConfigParent.xml");
230             fail("missing module parent");
231         }
232         catch (CheckstyleException ex) {
233             assertTrue("Invalid exception message: " + ex.getMessage(),
234                     ex.getMessage().contains("\"property\""));
235             assertTrue("Invalid exception message: " + ex.getMessage(),
236                     ex.getMessage().contains("\"module\""));
237             assertTrue("Invalid exception message: " + ex.getMessage(),
238                     ex.getMessage().endsWith(":8:38"));
239         }
240     }
241 
242     @Test
243     public void testCheckstyleChecks() throws Exception {
244         final Properties props = new Properties();
245         props.setProperty("checkstyle.basedir", "basedir");
246 
247         final DefaultConfiguration config =
248             (DefaultConfiguration) loadConfiguration(
249                 "InputConfigurationLoaderChecks.xml", props);
250 
251         //verify the root, and property substitution
252         final Properties atts = new Properties();
253         atts.setProperty("tabWidth", "4");
254         atts.setProperty("basedir", "basedir");
255         verifyConfigNode(config, "Checker", 3, atts);
256 
257         //verify children
258         final Configuration[] children = config.getChildren();
259         atts.clear();
260         verifyConfigNode(
261             (DefaultConfiguration) children[1], "JavadocPackage", 0, atts);
262         verifyConfigNode(
263             (DefaultConfiguration) children[2], "Translation", 0, atts);
264         atts.setProperty("testName", "testValue");
265         verifyConfigNode(
266             (DefaultConfiguration) children[0],
267             "TreeWalker",
268             8,
269             atts);
270 
271         //verify TreeWalker's first, last, NoWhitespaceAfterCheck
272         final Configuration[] grandchildren = children[0].getChildren();
273         atts.clear();
274         verifyConfigNode(
275             (DefaultConfiguration) grandchildren[0],
276             "AvoidStarImport",
277             0,
278             atts);
279         atts.setProperty("format", "System.out.println");
280         verifyConfigNode(
281             (DefaultConfiguration) grandchildren[grandchildren.length - 1],
282             "GenericIllegalRegexp",
283             0,
284             atts);
285         atts.clear();
286         atts.setProperty("tokens", "DOT");
287         atts.setProperty("allowLineBreaks", "true");
288         verifyConfigNode(
289             (DefaultConfiguration) grandchildren[6],
290             "NoWhitespaceAfter",
291             0,
292             atts);
293     }
294 
295     @Test
296     public void testCustomMessages() throws Exception {
297         final Properties props = new Properties();
298         props.setProperty("checkstyle.basedir", "basedir");
299 
300         final DefaultConfiguration config =
301             (DefaultConfiguration) loadConfiguration(
302                 "InputConfigurationLoaderCustomMessages.xml", props);
303 
304         final Configuration[] children = config.getChildren();
305         final Configuration[] grandchildren = children[0].getChildren();
306 
307         final String expectedKey = "name.invalidPattern";
308         assertTrue("Messages should contain key: " + expectedKey,
309             grandchildren[0].getMessages()
310             .containsKey(expectedKey));
311     }
312 
313     private static void verifyConfigNode(
314         DefaultConfiguration config, String name, int childrenLength,
315         Properties atts) throws Exception {
316         assertEquals("name.", name, config.getName());
317         assertEquals(
318             "children.length.",
319             childrenLength,
320             config.getChildren().length);
321 
322         final String[] attNames = config.getAttributeNames();
323         assertEquals("attributes.length", atts.size(), attNames.length);
324 
325         for (String attName : attNames) {
326             assertEquals(
327                 "attribute[" + attName + "]",
328                 atts.getProperty(attName),
329                 config.getAttribute(attName));
330         }
331     }
332 
333     @Test
334     public void testReplacePropertiesNoReplace() throws Exception {
335         final String[] testValues = {null, "", "a", "$a", "{a",
336                                      "{a}", "a}", "$a}", "$", "a$b", };
337         final Properties props = initProperties();
338         for (String testValue : testValues) {
339             final String value = (String) getReplacePropertiesMethod().invoke(
340                 null, testValue, new PropertiesExpander(props), null);
341             assertEquals("\"" + testValue + "\"", value, testValue);
342         }
343     }
344 
345     @Test
346     public void testReplacePropertiesSyntaxError() throws Exception {
347         final Properties props = initProperties();
348         try {
349             final String value = (String) getReplacePropertiesMethod().invoke(
350                 null, "${a", new PropertiesExpander(props), null);
351             fail("expected to fail, instead got: " + value);
352         }
353         catch (InvocationTargetException ex) {
354             assertEquals("Invalid exception cause message",
355                 "Syntax error in property: ${a", ex.getCause().getMessage());
356         }
357     }
358 
359     @Test
360     public void testReplacePropertiesMissingProperty() throws Exception {
361         final Properties props = initProperties();
362         try {
363             final String value = (String) getReplacePropertiesMethod().invoke(
364                 null, "${c}", new PropertiesExpander(props), null);
365             fail("expected to fail, instead got: " + value);
366         }
367         catch (InvocationTargetException ex) {
368             assertEquals("Invalid exception cause message",
369                 "Property ${c} has not been set", ex.getCause().getMessage());
370         }
371     }
372 
373     @Test
374     public void testReplacePropertiesReplace() throws Exception {
375         final String[][] testValues = {
376             {"${a}", "A"},
377             {"x${a}", "xA"},
378             {"${a}x", "Ax"},
379             {"${a}${b}", "AB"},
380             {"x${a}${b}", "xAB"},
381             {"${a}x${b}", "AxB"},
382             {"${a}${b}x", "ABx"},
383             {"x${a}y${b}", "xAyB"},
384             {"${a}x${b}y", "AxBy"},
385             {"x${a}${b}y", "xABy"},
386             {"x${a}y${b}z", "xAyBz"},
387             {"$$", "$"},
388         };
389         final Properties props = initProperties();
390         for (String[] testValue : testValues) {
391             final String value = (String) getReplacePropertiesMethod().invoke(
392                 null, testValue[0], new PropertiesExpander(props), null);
393             assertEquals("\"" + testValue[0] + "\"",
394                 testValue[1], value);
395         }
396     }
397 
398     private static Properties initProperties() {
399         final Properties props = new Properties();
400         props.setProperty("a", "A");
401         props.setProperty("b", "B");
402         return props;
403     }
404 
405     @Test
406     public void testExternalEntity() throws Exception {
407         final Properties props = new Properties();
408         props.setProperty("checkstyle.basedir", "basedir");
409 
410         System.setProperty(
411                 XmlLoader.LoadExternalDtdFeatureProvider.ENABLE_EXTERNAL_DTD_LOAD, "true");
412 
413         final DefaultConfiguration config =
414             (DefaultConfiguration) loadConfiguration(
415                 "InputConfigurationLoaderExternalEntity.xml", props);
416 
417         final Properties atts = new Properties();
418         atts.setProperty("tabWidth", "4");
419         atts.setProperty("basedir", "basedir");
420         verifyConfigNode(config, "Checker", 2, atts);
421     }
422 
423     @Test
424     public void testExternalEntitySubdirectory() throws Exception {
425         final Properties props = new Properties();
426         props.setProperty("checkstyle.basedir", "basedir");
427 
428         System.setProperty(
429                 XmlLoader.LoadExternalDtdFeatureProvider.ENABLE_EXTERNAL_DTD_LOAD, "true");
430 
431         final DefaultConfiguration config =
432             (DefaultConfiguration) loadConfiguration(
433                 "subdir/InputConfigurationLoaderExternalEntitySubDir.xml", props);
434 
435         final Properties attributes = new Properties();
436         attributes.setProperty("tabWidth", "4");
437         attributes.setProperty("basedir", "basedir");
438         verifyConfigNode(config, "Checker", 2, attributes);
439     }
440 
441     @Test
442     public void testExternalEntityFromUri() throws Exception {
443         final Properties props = new Properties();
444         props.setProperty("checkstyle.basedir", "basedir");
445 
446         System.setProperty(
447                 XmlLoader.LoadExternalDtdFeatureProvider.ENABLE_EXTERNAL_DTD_LOAD, "true");
448 
449         final File file = new File(
450                 getPath("subdir/InputConfigurationLoaderExternalEntitySubDir.xml"));
451         final DefaultConfiguration config =
452             (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
453                     file.toURI().toString(), new PropertiesExpander(props));
454 
455         final Properties atts = new Properties();
456         atts.setProperty("tabWidth", "4");
457         atts.setProperty("basedir", "basedir");
458         verifyConfigNode(config, "Checker", 2, atts);
459     }
460 
461     @Test
462     public void testIncorrectTag() throws Exception {
463         final Class<?> aClassParent = ConfigurationLoader.class;
464         final Constructor<?> ctorParent = aClassParent.getDeclaredConstructor(
465                 PropertyResolver.class, boolean.class, ThreadModeSettings.class);
466         ctorParent.setAccessible(true);
467         final Object objParent = ctorParent.newInstance(null, true, null);
468 
469         final Class<?> aClass = Class.forName("com.puppycrawl.tools.checkstyle."
470                 + "ConfigurationLoader$InternalLoader");
471         final Constructor<?> constructor = aClass.getDeclaredConstructor(objParent.getClass());
472         constructor.setAccessible(true);
473 
474         final Object obj = constructor.newInstance(objParent);
475 
476         try {
477             Whitebox.invokeMethod(obj, "startElement", "", "", "hello", null);
478 
479             fail("Exception is expected");
480         }
481         catch (IllegalStateException ex) {
482             assertEquals("Invalid exception cause message",
483                 "Unknown name:" + "hello" + ".", ex.getMessage());
484         }
485     }
486 
487     @Test
488     public void testNonExistentPropertyName() throws Exception {
489         try {
490             loadConfiguration("InputConfigurationLoaderNonexistentProperty.xml");
491             fail("exception in expected");
492         }
493         catch (CheckstyleException ex) {
494             assertEquals("Invalid exception message",
495                 "unable to parse configuration stream", ex.getMessage());
496             assertSame("Expected cause of type SAXException",
497                 SAXException.class, ex.getCause().getClass());
498             assertSame("Expected cause of type CheckstyleException",
499                 CheckstyleException.class, ex.getCause().getCause().getClass());
500             assertEquals("Invalid exception cause message",
501                 "Property ${nonexistent} has not been set",
502                 ex.getCause().getCause().getMessage());
503         }
504     }
505 
506     @Test
507     public void testConfigWithIgnore() throws Exception {
508         final DefaultConfiguration config =
509                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
510                         getPath("InputConfigurationLoaderModuleIgnoreSeverity.xml"),
511                         new PropertiesExpander(new Properties()), true);
512 
513         final Configuration[] children = config.getChildren();
514         assertEquals("Invalid children count", 0, children[0].getChildren().length);
515     }
516 
517     @Test
518     public void testConfigWithIgnoreUsingInputSource() throws Exception {
519         final DefaultConfiguration config =
520                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(new InputSource(
521                         new File(getPath("InputConfigurationLoaderModuleIgnoreSeverity.xml"))
522                             .toURI().toString()),
523                         new PropertiesExpander(new Properties()), true);
524 
525         final Configuration[] children = config.getChildren();
526         assertEquals("Invalid children count", 0, children[0].getChildren().length);
527     }
528 
529     @Test
530     public void testConfigCheckerWithIgnore() throws Exception {
531         final DefaultConfiguration config =
532                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
533                         getPath("InputConfigurationLoaderCheckerIgnoreSeverity.xml"),
534                         new PropertiesExpander(new Properties()), true);
535 
536         final Configuration[] children = config.getChildren();
537         assertEquals("Invalid children count", 0, children.length);
538     }
539 
540     @Test
541     public void testLoadConfigurationWrongUrl() {
542         try {
543             final DefaultConfiguration config =
544                     (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
545                             ";InputConfigurationLoaderModuleIgnoreSeverity.xml",
546                             new PropertiesExpander(new Properties()), true);
547 
548             final Configuration[] children = config.getChildren();
549             assertEquals("Invalid children count", 0, children[0].getChildren().length);
550             fail("Exception is expected");
551         }
552         catch (CheckstyleException ex) {
553             assertEquals("Invalid exception message",
554                     "Unable to find: ;InputConfigurationLoaderModuleIgnoreSeverity.xml",
555                     ex.getMessage());
556         }
557     }
558 
559     @Test
560     public void testLoadConfigurationDeprecated() throws Exception {
561         final DefaultConfiguration config =
562                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
563                         Files.newInputStream(Paths.get(
564                             getPath("InputConfigurationLoaderModuleIgnoreSeverity.xml"))),
565                         new PropertiesExpander(new Properties()), true);
566 
567         final Configuration[] children = config.getChildren();
568         assertEquals("Invalid children count",
569             0, children[0].getChildren().length);
570     }
571 
572     @Test
573     public void testReplacePropertiesDefault() throws Exception {
574         final Properties props = new Properties();
575         final String defaultValue = "defaultValue";
576 
577         final String value = (String) getReplacePropertiesMethod().invoke(
578             null, "${checkstyle.basedir}", new PropertiesExpander(props), defaultValue);
579 
580         assertEquals("Invalid property value", defaultValue, value);
581     }
582 
583     @Test
584     public void testLoadConfigurationFromClassPath() throws Exception {
585         final DefaultConfiguration config =
586                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
587                         getPath("InputConfigurationLoaderModuleIgnoreSeverity.xml"),
588                         new PropertiesExpander(new Properties()), true);
589 
590         final Configuration[] children = config.getChildren();
591         assertEquals("Invalid children count",
592             0, children[0].getChildren().length);
593     }
594 
595     @Test
596     public void testParsePropertyString() throws Exception {
597         final List<String> propertyRefs = new ArrayList<>();
598         final List<String> fragments = new ArrayList<>();
599 
600         Whitebox.invokeMethod(ConfigurationLoader.class,
601                 "parsePropertyString", "$",
602                fragments, propertyRefs);
603         assertEquals("Fragments list has unexpected amount of items",
604                 1, fragments.size());
605     }
606 
607     @Test
608     public void testConstructors() throws Exception {
609         final Properties props = new Properties();
610         props.setProperty("checkstyle.basedir", "basedir");
611         final String fName = getPath("InputConfigurationLoaderChecks.xml");
612 
613         final Configuration configuration = ConfigurationLoader.loadConfiguration(fName,
614                 new PropertiesExpander(props), ConfigurationLoader.IgnoredModulesOptions.OMIT);
615         assertEquals("Name is not expected", "Checker", configuration.getName());
616 
617         final DefaultConfiguration configuration1 =
618                 (DefaultConfiguration) ConfigurationLoader.loadConfiguration(
619                         new InputSource(Files.newInputStream(Paths.get(
620                             getPath("InputConfigurationLoaderModuleIgnoreSeverity.xml")))),
621                         new PropertiesExpander(new Properties()),
622                         ConfigurationLoader.IgnoredModulesOptions.EXECUTE);
623 
624         final Configuration[] children = configuration1.getChildren();
625         assertEquals("Unexpected children size", 1, children[0].getChildren().length);
626     }
627 
628 }