////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2017 the original author or authors.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
////////////////////////////////////////////////////////////////////////////////
package com.puppycrawl.tools.checkstyle;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.powermock.api.mockito.PowerMockito.when;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
import com.puppycrawl.tools.checkstyle.api.Configuration;
/**
* Unit test for ConfigurationLoader.
* @author Rick Giles
* @author lkuehne
*/
@RunWith(PowerMockRunner.class)
@PrepareForTest({DefaultConfiguration.class, ConfigurationLoader.class})
public class ConfigurationLoaderTest {
private static String getConfigPath(String filename) {
return "src/test/resources/com/puppycrawl/tools/checkstyle/configs/" + filename;
}
private static Configuration loadConfiguration(String name)
throws CheckstyleException {
return loadConfiguration(name, new Properties());
}
private static Configuration loadConfiguration(
String name, Properties props) throws CheckstyleException {
final String fName = getConfigPath(name);
return ConfigurationLoader.loadConfiguration(
fName, new PropertiesExpander(props));
}
private static Method getReplacePropertiesMethod() throws Exception {
final Class<?>[] params = new Class<?>[3];
params[0] = String.class;
params[1] = PropertyResolver.class;
params[2] = String.class;
final Class<ConfigurationLoader> configurationLoaderClass = ConfigurationLoader.class;
final Method replacePropertiesMethod =
configurationLoaderClass.getDeclaredMethod("replaceProperties", params);
replacePropertiesMethod.setAccessible(true);
return replacePropertiesMethod;
}
@Test
public void testResourceLoadConfiguration() throws Exception {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
// load config that's only found in the classpath
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
getConfigPath("checkstyle_checks.xml"), new PropertiesExpander(props));
//verify the root, and property substitution
final Properties attributes = new Properties();
attributes.setProperty("tabWidth", "4");
attributes.setProperty("basedir", "basedir");
verifyConfigNode(config, "Checker", 3, attributes);
}
@Test
public void testEmptyConfiguration() throws Exception {
final DefaultConfiguration config =
(DefaultConfiguration) loadConfiguration("empty_configuration.xml");
verifyConfigNode(config, "Checker", 0, new Properties());
}
@Test
public void testMissingPropertyName() {
try {
loadConfiguration("missing_property_name.xml");
fail("missing property name");
}
catch (CheckstyleException ex) {
assertTrue(ex.getMessage().contains("\"name\""));
assertTrue(ex.getMessage().contains("\"property\""));
assertTrue(ex.getMessage().endsWith(":8:41"));
}
}
@Test
public void testMissingPropertyValue() {
try {
loadConfiguration("missing_property_value.xml");
fail("missing property value");
}
catch (CheckstyleException ex) {
assertTrue(ex.getMessage().contains("\"value\""));
assertTrue(ex.getMessage().contains("\"property\""));
assertTrue(ex.getMessage().endsWith(":8:41"));
}
}
@Test
public void testMissingConfigName() {
try {
loadConfiguration("missing_config_name.xml");
fail("missing module name");
}
catch (CheckstyleException ex) {
assertTrue(ex.getMessage().contains("\"name\""));
assertTrue(ex.getMessage().contains("\"module\""));
assertTrue(ex.getMessage().endsWith(":7:23"));
}
}
@Test
public void testMissingConfigParent() {
try {
loadConfiguration("missing_config_parent.xml");
fail("missing module parent");
}
catch (CheckstyleException ex) {
assertTrue(ex.getMessage().contains("\"property\""));
assertTrue(ex.getMessage().contains("\"module\""));
assertTrue(ex.getMessage().endsWith(":8:38"));
}
}
@Test
public void testCheckstyleChecks() throws Exception {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
final DefaultConfiguration config =
(DefaultConfiguration) loadConfiguration(
"checkstyle_checks.xml", props);
//verify the root, and property substitution
final Properties atts = new Properties();
atts.setProperty("tabWidth", "4");
atts.setProperty("basedir", "basedir");
verifyConfigNode(config, "Checker", 3, atts);
//verify children
final Configuration[] children = config.getChildren();
atts.clear();
verifyConfigNode(
(DefaultConfiguration) children[1], "JavadocPackage", 0, atts);
verifyConfigNode(
(DefaultConfiguration) children[2], "Translation", 0, atts);
atts.setProperty("testName", "testValue");
verifyConfigNode(
(DefaultConfiguration) children[0],
"TreeWalker",
8,
atts);
//verify TreeWalker's first, last, NoWhitespaceAfterCheck
final Configuration[] grandchildren = children[0].getChildren();
atts.clear();
verifyConfigNode(
(DefaultConfiguration) grandchildren[0],
"AvoidStarImport",
0,
atts);
atts.setProperty("format", "System.out.println");
verifyConfigNode(
(DefaultConfiguration) grandchildren[grandchildren.length - 1],
"GenericIllegalRegexp",
0,
atts);
atts.clear();
atts.setProperty("tokens", "DOT");
atts.setProperty("allowLineBreaks", "true");
verifyConfigNode(
(DefaultConfiguration) grandchildren[6],
"NoWhitespaceAfter",
0,
atts);
}
@Test
public void testCustomMessages() throws CheckstyleException {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
final DefaultConfiguration config =
(DefaultConfiguration) loadConfiguration(
"custom_messages.xml", props);
final Configuration[] children = config.getChildren();
final Configuration[] grandchildren = children[0].getChildren();
assertTrue(grandchildren[0].getMessages()
.containsKey("name.invalidPattern"));
}
private static void verifyConfigNode(
DefaultConfiguration config, String name, int childrenLength,
Properties atts) throws Exception {
assertEquals("name.", name, config.getName());
assertEquals(
"children.length.",
childrenLength,
config.getChildren().length);
final String[] attNames = config.getAttributeNames();
assertEquals("attributes.length", atts.size(), attNames.length);
for (String attName : attNames) {
assertEquals(
"attribute[" + attName + "]",
atts.getProperty(attName),
config.getAttribute(attName));
}
}
@Test
public void testReplacePropertiesNoReplace() throws Exception {
final String[] testValues = {null, "", "a", "$a", "{a",
"{a}", "a}", "$a}", "$", "a$b", };
final Properties props = initProperties();
for (String testValue : testValues) {
final String value = (String) getReplacePropertiesMethod().invoke(
null, testValue, new PropertiesExpander(props), null);
assertEquals("\"" + testValue + "\"", value, testValue);
}
}
@Test
public void testReplacePropertiesSyntaxError() throws Exception {
final Properties props = initProperties();
try {
final String value = (String) getReplacePropertiesMethod().invoke(
null, "${a", new PropertiesExpander(props), null);
fail("expected to fail, instead got: " + value);
}
catch (InvocationTargetException ex) {
assertEquals("Syntax error in property: ${a", ex.getCause().getMessage());
}
}
@Test
public void testReplacePropertiesMissingProperty() throws Exception {
final Properties props = initProperties();
try {
final String value = (String) getReplacePropertiesMethod().invoke(
null, "${c}", new PropertiesExpander(props), null);
fail("expected to fail, instead got: " + value);
}
catch (InvocationTargetException ex) {
assertEquals("Property ${c} has not been set", ex.getCause().getMessage());
}
}
@Test
public void testReplacePropertiesReplace() throws Exception {
final String[][] testValues = {
{"${a}", "A"},
{"x${a}", "xA"},
{"${a}x", "Ax"},
{"${a}${b}", "AB"},
{"x${a}${b}", "xAB"},
{"${a}x${b}", "AxB"},
{"${a}${b}x", "ABx"},
{"x${a}y${b}", "xAyB"},
{"${a}x${b}y", "AxBy"},
{"x${a}${b}y", "xABy"},
{"x${a}y${b}z", "xAyBz"},
{"$$", "$"},
};
final Properties props = initProperties();
for (String[] testValue : testValues) {
final String value = (String) getReplacePropertiesMethod().invoke(
null, testValue[0], new PropertiesExpander(props), null);
assertEquals("\"" + testValue[0] + "\"",
testValue[1], value);
}
}
private static Properties initProperties() {
final Properties props = new Properties();
props.setProperty("a", "A");
props.setProperty("b", "B");
return props;
}
@Test
public void testExternalEntity() throws Exception {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
final DefaultConfiguration config =
(DefaultConfiguration) loadConfiguration(
"including.xml", props);
final Properties atts = new Properties();
atts.setProperty("tabWidth", "4");
atts.setProperty("basedir", "basedir");
verifyConfigNode(config, "Checker", 2, atts);
}
@Test
public void testExternalEntitySubdirectory() throws Exception {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
final DefaultConfiguration config =
(DefaultConfiguration) loadConfiguration(
"subdir/including.xml", props);
final Properties attributes = new Properties();
attributes.setProperty("tabWidth", "4");
attributes.setProperty("basedir", "basedir");
verifyConfigNode(config, "Checker", 2, attributes);
}
@Test
public void testExternalEntityFromUri() throws Exception {
final Properties props = new Properties();
props.setProperty("checkstyle.basedir", "basedir");
final File file = new File(getConfigPath("subdir/including.xml"));
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
file.toURI().toString(), new PropertiesExpander(props));
final Properties atts = new Properties();
atts.setProperty("tabWidth", "4");
atts.setProperty("basedir", "basedir");
verifyConfigNode(config, "Checker", 2, atts);
}
@Test
public void testIncorrectTag() throws Exception {
try {
final Class<?> aClassParent = ConfigurationLoader.class;
Constructor<?> ctorParent = null;
final Constructor<?>[] parentConstructors = aClassParent.getDeclaredConstructors();
for (Constructor<?> parentConstructor: parentConstructors) {
parentConstructor.setAccessible(true);
ctorParent = parentConstructor;
}
final Class<?> aClass = Class.forName("com.puppycrawl.tools.checkstyle."
+ "ConfigurationLoader$InternalLoader");
Constructor<?> constructor = null;
final Constructor<?>[] constructors = aClass.getDeclaredConstructors();
for (Constructor<?> constr: constructors) {
constr.setAccessible(true);
constructor = constr;
}
final Object objParent = ctorParent.newInstance(null, true);
final Object obj = constructor.newInstance(objParent);
final Class<?>[] param = new Class<?>[] {String.class, String.class,
String.class, Attributes.class, };
final Method method = aClass.getDeclaredMethod("startElement", param);
method.invoke(obj, "", "", "hello", null);
fail("Exception is expected");
}
catch (InvocationTargetException ex) {
assertTrue(ex.getCause() instanceof IllegalStateException);
assertEquals("Unknown name:" + "hello" + ".", ex.getCause().getMessage());
}
}
@Test
public void testNonExistingPropertyName() {
try {
loadConfiguration("config_nonexisting_property.xml");
fail("exception in expected");
}
catch (CheckstyleException ex) {
assertEquals("unable to parse configuration stream", ex.getMessage());
assertEquals("Property ${nonexisting} has not been set",
ex.getCause().getMessage());
}
}
@Test
public void testConfigWithIgnore() throws CheckstyleException {
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
getConfigPath("config_with_ignore.xml"),
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children[0].getChildren().length);
}
@Test
public void testConfigWithIgnoreUsingInputSource() throws CheckstyleException {
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(new InputSource(
new File(getConfigPath("config_with_ignore.xml")).toURI().toString()),
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children[0].getChildren().length);
}
@Test
public void testConfigCheckerWithIgnore() throws CheckstyleException {
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
getConfigPath("config_with_checker_ignore.xml"),
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children.length);
}
@Test
public void testLoadConfigurationWrongUrl() {
try {
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
";config_with_ignore.xml",
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children[0].getChildren().length);
fail("Exception is expected");
}
catch (CheckstyleException ex) {
assertEquals("Unable to find: ;config_with_ignore.xml", ex.getMessage());
}
}
@Test
public void testLoadConfigurationDeprecated() {
try {
@SuppressWarnings("deprecation")
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
new FileInputStream(getConfigPath("config_with_ignore.xml")),
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children[0].getChildren().length);
}
catch (CheckstyleException | FileNotFoundException ex) {
fail("unexpected exception");
}
}
@Test
public void testReplacePropertiesDefault() throws Exception {
final Properties props = new Properties();
final String defaultValue = "defaultValue";
final String value = (String) getReplacePropertiesMethod().invoke(
null, "${checkstyle.basedir}", new PropertiesExpander(props), defaultValue);
assertEquals(defaultValue, value);
}
@Test
public void testLoadConfigurationFromClassPath() {
try {
final DefaultConfiguration config =
(DefaultConfiguration) ConfigurationLoader.loadConfiguration(
"/com/puppycrawl/tools/checkstyle/configs/"
+ "config_with_ignore.xml",
new PropertiesExpander(new Properties()), true);
final Configuration[] children = config.getChildren();
assertEquals(0, children[0].getChildren().length);
}
catch (CheckstyleException ex) {
fail("unexpected exception");
}
}
/**
* This SuppressWarning("unchecked") required to suppress
* "Unchecked generics array creation for varargs parameter" during mock
* @throws Exception could happen from PowerMokito calls and getAttribute
*/
@SuppressWarnings("unchecked")
@Test
public void testConfigWithIgnoreExceptionalAttributes() throws Exception {
// emulate exception from unrelated code, but that is same try-catch
final DefaultConfiguration tested = PowerMockito.mock(DefaultConfiguration.class);
when(tested.getAttributeNames()).thenReturn(new String[] {"severity"});
when(tested.getName()).thenReturn("MemberName");
when(tested.getAttribute("severity")).thenThrow(CheckstyleException.class);
// to void creation of 2 other mocks for now reason, only one moc is used for all cases
PowerMockito.whenNew(DefaultConfiguration.class)
.withArguments("MemberName").thenReturn(tested);
PowerMockito.whenNew(DefaultConfiguration.class)
.withArguments("Checker").thenReturn(tested);
PowerMockito.whenNew(DefaultConfiguration.class)
.withArguments("TreeWalker").thenReturn(tested);
try {
ConfigurationLoader.loadConfiguration(
getConfigPath("config_with_ignore.xml"),
new PropertiesExpander(new Properties()), true);
fail("Exception is expected");
}
catch (CheckstyleException expected) {
assertEquals("Problem during accessing 'severity' attribute for MemberName",
expected.getCause().getMessage());
}
}
}