Skip to content

Commit

Permalink
Issue checkstyle#4592: Added AbstractModuleTestSupport and GenericWhi…
Browse files Browse the repository at this point in the history
…tespaceCheckTest, ParenPadCheckTest, FileTabCharacterCheckTest, PackageAnnotationCheckTest now extend from it
  • Loading branch information
subkrish committed Jul 18, 2017
1 parent c094935 commit 258d149
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 50 deletions.
4 changes: 2 additions & 2 deletions config/suppressions.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<suppress checks="AvoidStaticImport" files=".*[\\/]src[\\/](test|it)[\\/]"/>
<suppress checks="ClassDataAbstractionCoupling" files="[\\/]IndentationCheckTest.java$"/>
<suppress checks="EqualsAvoidNull" files="[\\/]Int.*FilterTest.java$"/>
<suppress checks="VisibilityModifier" files="[\\/]BaseCheckTestSupport.java$"/>
<suppress checks="VisibilityModifier" files="[\\/]BaseCheckTestSupport.java$|[\\/]AbstractModuleTestSupport.java$"/>
<suppress checks="WriteTag" files=".*[\\/]src[\\/](test|it)[\\/]"/>
<suppress checks="MethodCount" files=".*[\\/]src[\\/](test|it)[\\/]"/>

Expand Down Expand Up @@ -73,7 +73,7 @@
<suppress checks="ClassDataAbstractionCoupling" files="AutomaticBean\.java"/>
<!-- they are aggregators of logic, usage a several of classes are ok -->
<suppress checks="ClassDataAbstractionCoupling" files="(Checker|TreeWalker|Main|CheckstyleAntTask|JavadocDetailNodeParser)\.java"/>
<suppress checks="ClassDataAbstractionCoupling" files="(CheckerTest|TreeWalkerTest|BaseCheckTestSupport|XdocsPagesTest|CheckstyleAntTaskTest)\.java"/>
<suppress checks="ClassDataAbstractionCoupling" files="(CheckerTest|TreeWalkerTest|BaseCheckTestSupport|AbstractModuleTestSupport|XdocsPagesTest|CheckstyleAntTaskTest)\.java"/>
<suppress checks="ClassDataAbstractionCoupling" files="PropertyCacheFile\.java"/>
<suppress checks="ClassDataAbstractionCoupling" files="PropertyCacheFileTest\.java"/>
<suppress checks="ClassFanOutComplexity" files="[\\/]Main\.java"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
////////////////////////////////////////////////////////////////////////////////
// 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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.stream.Collectors;

import com.google.common.collect.MapDifference;
import com.google.common.collect.Maps;
import com.puppycrawl.tools.checkstyle.api.Configuration;
import com.puppycrawl.tools.checkstyle.api.LocalizedMessage;

public abstract class AbstractModuleTestSupport extends AbstractPathTestSupport {

protected final ByteArrayOutputStream stream = new ByteArrayOutputStream();

protected static DefaultConfiguration createCheckConfig(Class<?> clazz) {
return new DefaultConfiguration(clazz.getName());
}

/**
* Creates {@link Checker} instance based on the given {@link Configuration} instance.
* @param checkConfig {@link Configuration} instance.
* @return {@link Checker} instance based on the given {@link Configuration} instance.
* @throws Exception if an exception occurs during checker configuration.
*/
public Checker createChecker(Configuration checkConfig)
throws Exception {
final DefaultConfiguration dc = createCheckerConfig(checkConfig);
final Checker checker = new Checker();
checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader());
checker.configure(dc);
checker.addListener(new BriefUtLogger(stream));
return checker;
}

/**
* Creates {@link DefaultConfiguration} for the {@link Checker}
* based on the given {@link Configuration} instance.
* @param config {@link Configuration} instance.
* @return {@link DefaultConfiguration} for the {@link Checker}
* based on the given {@link Configuration} instance.
*/
protected DefaultConfiguration createCheckerConfig(Configuration config) {
final DefaultConfiguration dc =
new DefaultConfiguration("configuration");
final DefaultConfiguration twConf = createCheckConfig(TreeWalker.class);
// make sure that the tests always run with this charset
dc.addAttribute("charset", "UTF-8");
dc.addChild(twConf);
twConf.addChild(config);
return dc;
}

/**
* Returns canonical path for the file with the given file name.
* The path is formed base on the non-compilable resources location.
* This implementation uses 'src/test/resources-noncompilable/com/puppycrawl/tools/checkstyle/'
* as a non-compilable resource location.
* @param filename file name.
* @return canonical path for the file with the given file name.
* @throws IOException if I/O exception occurs while forming the path.
*/
protected final String getNonCompilablePath(String filename) throws IOException {
return new File("src/test/resources-noncompilable/" + getPackageLocation() + "/"
+ filename).getCanonicalPath();
}

/**
* Performs verification of the file with the given file name. Uses specified configuration.
* Expected messages are represented by the array of strings.
* This implementation uses overloaded
* {@link BaseCheckTestSupport#verify(Checker, File[], String, String...)} method inside.
* @param aConfig configuration.
* @param fileName file name to verify.
* @param expected an array of expected messages.
* @throws Exception if exception occurs during verification process.
*/
protected void verify(Configuration aConfig, String fileName, String... expected)
throws Exception {
verify(createChecker(aConfig), fileName, fileName, expected);
}

/**
* Performs verification of the file with the given file name.
* Uses provided {@link Checker} instance.
* Expected messages are represented by the array of strings.
* This implementation uses overloaded
* {@link BaseCheckTestSupport#verify(Checker, String, String, String...)} method inside.
* @param checker {@link Checker} instance.
* @param fileName file name to verify.
* @param expected an array of expected messages.
* @throws Exception if exception occurs during verification process.
*/
protected void verify(Checker checker, String fileName, String... expected)
throws Exception {
verify(checker, fileName, fileName, expected);
}

/**
* Performs verification of the file with the given file name.
* Uses provided {@link Checker} instance.
* Expected messages are represented by the array of strings.
* This implementation uses overloaded
* {@link BaseCheckTestSupport#verify(Checker, File[], String, String...)} method inside.
* @param checker {@link Checker} instance.
* @param processedFilename file name to verify.
* @param messageFileName message file name.
* @param expected an array of expected messages.
* @throws Exception if exception occurs during verification process.
*/
protected void verify(Checker checker,
String processedFilename,
String messageFileName,
String... expected)
throws Exception {
verify(checker,
new File[] {new File(processedFilename)},
messageFileName, expected);
}

/**
* We keep two verify methods with separate logic only for convenience of debugging
* We have minimum amount of multi-file test cases
*/
protected void verify(Checker checker,
File[] processedFiles,
String messageFileName,
String... expected)
throws Exception {
stream.flush();
final List<File> theFiles = new ArrayList<>();
Collections.addAll(theFiles, processedFiles);
final int errs = checker.process(theFiles);

// process each of the lines
final ByteArrayInputStream inputStream =
new ByteArrayInputStream(stream.toByteArray());
try (LineNumberReader lnr = new LineNumberReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

final List<String> actuals = lnr.lines().limit(expected.length)
.sorted().collect(Collectors.toList());
Arrays.sort(expected);

for (int i = 0; i < expected.length; i++) {
final String expectedResult = messageFileName + ":" + expected[i];
assertEquals("error message " + i, expectedResult, actuals.get(i));
}

assertEquals("unexpected output: " + lnr.readLine(),
expected.length, errs);
}

checker.destroy();
}

/**
* Performs verification of the given files.
* @param checker {@link Checker} instance
* @param processedFiles files to process.
* @param expectedViolations a map of expected violations per files.
* @throws Exception if exception occurs during verification process.
*/
protected void verify(Checker checker,
File[] processedFiles,
Map<String, List<String>> expectedViolations)
throws Exception {
stream.flush();
final List<File> theFiles = new ArrayList<>();
Collections.addAll(theFiles, processedFiles);
final int errs = checker.process(theFiles);

// process each of the lines
final Map<String, List<String>> actualViolations = getActualViolations(errs);
final Map<String, List<String>> realExpectedViolations =
Maps.filterValues(expectedViolations, input -> !input.isEmpty());
final MapDifference<String, List<String>> violationDifferences =
Maps.difference(realExpectedViolations, actualViolations);

final Map<String, List<String>> missingViolations =
violationDifferences.entriesOnlyOnLeft();
final Map<String, List<String>> unexpectedViolations =
violationDifferences.entriesOnlyOnRight();
final Map<String, MapDifference.ValueDifference<List<String>>> differingViolations =
violationDifferences.entriesDiffering();

final StringBuilder message = new StringBuilder();
if (!missingViolations.isEmpty()) {
message.append("missing violations: ").append(missingViolations);
}
if (!unexpectedViolations.isEmpty()) {
if (message.length() > 0) {
message.append('\n');
}
message.append("unexpected violations: ").append(unexpectedViolations);
}
if (!differingViolations.isEmpty()) {
if (message.length() > 0) {
message.append('\n');
}
message.append("differing violations: ").append(differingViolations);
}

assertTrue(message.toString(),
missingViolations.isEmpty()
&& unexpectedViolations.isEmpty()
&& differingViolations.isEmpty());

checker.destroy();
}

private Map<String, List<String>> getActualViolations(int errorCount) throws IOException {
// process each of the lines
final ByteArrayInputStream inputStream =
new ByteArrayInputStream(stream.toByteArray());

try (LineNumberReader lnr = new LineNumberReader(
new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {

final Map<String, List<String>> actualViolations = new HashMap<>();
for (String line = lnr.readLine(); line != null && lnr.getLineNumber() <= errorCount;
line = lnr.readLine()) {
// have at least 2 characters before the splitting colon,
// to not split after the drive letter on windows
final String[] actualViolation = line.split("(?<=.{2}):", 2);
final String actualViolationFileName = actualViolation[0];
final String actualViolationMessage = actualViolation[1];

List<String> actualViolationsPerFile =
actualViolations.get(actualViolationFileName);
if (actualViolationsPerFile == null) {
actualViolationsPerFile = new ArrayList<>();
actualViolations.put(actualViolationFileName, actualViolationsPerFile);
}
actualViolationsPerFile.add(actualViolationMessage);
}

return actualViolations;
}
}

/**
* Gets the check message 'as is' from appropriate 'messages.properties'
* file.
*
* @param messageKey the key of message in 'messages.properties' file.
* @param arguments the arguments of message in 'messages.properties' file.
*/
protected String getCheckMessage(String messageKey, Object... arguments) {
return internalGetCheckMessage(getMessageBundle(), messageKey, arguments);
}

/**
* Gets the check message 'as is' from appropriate 'messages.properties'
* file.
*
* @param messageBundle the bundle name.
* @param messageKey the key of message in 'messages.properties' file.
* @param arguments the arguments of message in 'messages.properties' file.
*/
private static String internalGetCheckMessage(
String messageBundle, String messageKey, Object... arguments) {
final ResourceBundle resourceBundle = ResourceBundle.getBundle(
messageBundle,
Locale.getDefault(),
Thread.currentThread().getContextClassLoader(),
new LocalizedMessage.Utf8Control());
final String pattern = resourceBundle.getString(messageKey);
final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT);
return formatter.format(arguments);
}

private String getMessageBundle() {
final String className = getClass().getName();
return getMessageBundle(className);
}

private static String getMessageBundle(String className) {
final String messageBundle;
final String messages = "messages";
final int endIndex = className.lastIndexOf('.');
if (endIndex < 0) {
messageBundle = messages;
}
else {
final String packageName = className.substring(0, endIndex);
messageBundle = packageName + "." + messages;
}
return messageBundle;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,19 @@

import static com.puppycrawl.tools.checkstyle.checks.annotation.PackageAnnotationCheck.MSG_KEY;

import java.io.File;
import java.io.IOException;

import org.junit.Assert;
import org.junit.Test;

import com.puppycrawl.tools.checkstyle.BaseCheckTestSupport;
import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import com.puppycrawl.tools.checkstyle.utils.CommonUtils;

public class PackageAnnotationCheckTest extends BaseCheckTestSupport {
@Override
protected String getPath(String filename) throws IOException {
return super.getPath("checks" + File.separator
+ "annotation" + File.separator
+ "packageannotation" + File.separator
+ filename);
}
public class PackageAnnotationCheckTest extends AbstractModuleTestSupport {

@Override
protected String getNonCompilablePath(String filename) throws IOException {
return super.getNonCompilablePath("checks" + File.separator
+ "annotation" + File.separator
+ "packageannotation" + File.separator
+ filename);
protected String getPackageLocation() {
return "com/puppycrawl/tools/checkstyle/checks/annotation/packageannotation";
}

/**
Expand Down
Loading

0 comments on commit 258d149

Please sign in to comment.