forked from checkstyle/regression-tool
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue checkstyle#79: Processing UT changes to get possible property v…
…alues
- Loading branch information
Showing
7 changed files
with
561 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
115 changes: 115 additions & 0 deletions
115
src/main/java/com/github/checkstyle/regression/module/UnitTestProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// 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.github.checkstyle.regression.module; | ||
|
||
import java.io.File; | ||
import java.lang.reflect.Field; | ||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Set; | ||
|
||
import com.github.checkstyle.regression.data.ModuleInfo; | ||
import com.puppycrawl.tools.checkstyle.Checker; | ||
import com.puppycrawl.tools.checkstyle.DefaultConfiguration; | ||
import com.puppycrawl.tools.checkstyle.TreeWalker; | ||
import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | ||
import com.puppycrawl.tools.checkstyle.api.CheckstyleException; | ||
import com.puppycrawl.tools.checkstyle.api.Configuration; | ||
import com.puppycrawl.tools.checkstyle.api.FileSetCheck; | ||
|
||
/** | ||
* Processes the unit test class of a checkstyle module. | ||
* This utility class would run {@link UnitTestProcessorCheck} on the file with | ||
* the given path and return the collected property info. | ||
* @author LuoLiangchen | ||
*/ | ||
public final class UnitTestProcessor { | ||
/** Prevents instantiation. */ | ||
private UnitTestProcessor() { | ||
} | ||
|
||
/** | ||
* Processes the unit test class with the given path. | ||
* @param path the path of the unit test class | ||
* @return the unit test method name to properties map | ||
* @throws CheckstyleException failure when running the check | ||
* @throws ReflectiveOperationException failure of reflection on checker and tree walker | ||
*/ | ||
public static Map<String, Set<ModuleInfo.Property>> process(String path) | ||
throws CheckstyleException, ReflectiveOperationException { | ||
final DefaultConfiguration moduleConfig = createModuleConfig(UnitTestProcessorCheck.class); | ||
final Configuration dc = createTreeWalkerConfig(moduleConfig); | ||
final Checker checker = new Checker(); | ||
checker.setModuleClassLoader(Thread.currentThread().getContextClassLoader()); | ||
checker.configure(dc); | ||
final List<File> processedFiles = Collections.singletonList(new File(path)); | ||
checker.process(processedFiles); | ||
|
||
final UnitTestProcessorCheck check = getCheckInstance(checker); | ||
return check.getUnitTestToPropertiesMap(); | ||
} | ||
|
||
/** | ||
* Gets the instance of {@link UnitTestProcessorCheck} from the checker. | ||
* @param checker the checker which run the check | ||
* @return the instance of {@link UnitTestProcessorCheck} | ||
* @throws ReflectiveOperationException failure of reflection | ||
*/ | ||
@SuppressWarnings("unchecked") | ||
private static UnitTestProcessorCheck getCheckInstance(Checker checker) | ||
throws ReflectiveOperationException { | ||
final Field fileSetChecks = checker.getClass().getDeclaredField("fileSetChecks"); | ||
fileSetChecks.setAccessible(true); | ||
final TreeWalker treeWalker = | ||
(TreeWalker) ((List<FileSetCheck>) fileSetChecks.get(checker)).get(0); | ||
final Field ordinaryChecks = treeWalker.getClass().getDeclaredField("ordinaryChecks"); | ||
ordinaryChecks.setAccessible(true); | ||
final Set<AbstractCheck> checks = (Set<AbstractCheck>) ordinaryChecks.get(treeWalker); | ||
return (UnitTestProcessorCheck) checks.iterator().next(); | ||
} | ||
|
||
/** | ||
* Creates {@link DefaultConfiguration} for the {@link TreeWalker} | ||
* based on the given {@link Configuration} instance. | ||
* @param config {@link Configuration} instance. | ||
* @return {@link DefaultConfiguration} for the {@link TreeWalker} | ||
* based on the given {@link Configuration} instance. | ||
*/ | ||
private static DefaultConfiguration createTreeWalkerConfig(Configuration config) { | ||
final DefaultConfiguration dc = | ||
new DefaultConfiguration("configuration"); | ||
final DefaultConfiguration twConf = createModuleConfig(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; | ||
} | ||
|
||
/** | ||
* Creates {@link DefaultConfiguration} for the given class. | ||
* @param clazz the class of module | ||
* @return the {@link DefaultConfiguration} of the module class | ||
*/ | ||
private static DefaultConfiguration createModuleConfig(Class<?> clazz) { | ||
return new DefaultConfiguration(clazz.getName()); | ||
} | ||
} |
211 changes: 211 additions & 0 deletions
211
src/main/java/com/github/checkstyle/regression/module/UnitTestProcessorCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// 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.github.checkstyle.regression.module; | ||
|
||
import java.util.Collections; | ||
import java.util.LinkedHashMap; | ||
import java.util.LinkedHashSet; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
|
||
import com.github.checkstyle.regression.data.ImmutableProperty; | ||
import com.github.checkstyle.regression.data.ModuleInfo; | ||
import com.puppycrawl.tools.checkstyle.DefaultConfiguration; | ||
import com.puppycrawl.tools.checkstyle.api.AbstractCheck; | ||
import com.puppycrawl.tools.checkstyle.api.DetailAST; | ||
import com.puppycrawl.tools.checkstyle.api.TokenTypes; | ||
|
||
/** | ||
* The custom check which processes the unit test class of a checkstyle module. | ||
* This check would walk through the {@code @Test} annotation, find variable definition | ||
* like {@code final DefaultConfiguration checkConfig = createModuleConfig(FooCheck.class)} | ||
* and grab the property info from {@link DefaultConfiguration#addAttribute(String, String)} | ||
* method call. | ||
* @author LuoLiangchen | ||
*/ | ||
public class UnitTestProcessorCheck extends AbstractCheck { | ||
/** The map of unit test method name to properties. */ | ||
private final Map<String, Set<ModuleInfo.Property>> unitTestToProperties = | ||
new LinkedHashMap<>(); | ||
|
||
@Override | ||
public int[] getDefaultTokens() { | ||
return new int[] { | ||
TokenTypes.ANNOTATION, | ||
}; | ||
} | ||
|
||
@Override | ||
public int[] getRequiredTokens() { | ||
return new int[] { | ||
TokenTypes.ANNOTATION, | ||
}; | ||
} | ||
|
||
@Override | ||
public int[] getAcceptableTokens() { | ||
return new int[] { | ||
TokenTypes.ANNOTATION, | ||
}; | ||
} | ||
|
||
@Override | ||
public void visitToken(DetailAST ast) { | ||
if ("Test".equals(ast.findFirstToken(TokenTypes.IDENT).getText())) { | ||
final DetailAST methodDef = ast.getParent().getParent(); | ||
final DetailAST methodBlock = methodDef.findFirstToken(TokenTypes.SLIST); | ||
final Optional<String> configVariableName = | ||
getModuleConfigVariableName(methodBlock); | ||
if (configVariableName.isPresent()) { | ||
final Set<ModuleInfo.Property> properties = new LinkedHashSet<>(); | ||
|
||
for (DetailAST expr : getAllChildrenWithToken(methodBlock, TokenTypes.EXPR)) { | ||
if (isAddAttributeMethodCall(expr.getFirstChild(), configVariableName.get())) { | ||
final DetailAST elist = | ||
expr.getFirstChild().findFirstToken(TokenTypes.ELIST); | ||
final String key = convertExprToText(elist.getFirstChild()); | ||
final String value = convertExprToText(elist.getLastChild()); | ||
properties.add(ImmutableProperty.builder().name(key).value(value).build()); | ||
} | ||
} | ||
|
||
if (!unitTestToProperties.containsValue(properties)) { | ||
final String methodName = methodDef.findFirstToken(TokenTypes.IDENT).getText(); | ||
unitTestToProperties.put(methodName, properties); | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Gets the map of unit test method name to properties. | ||
* @return the map of unit test method name to properties | ||
*/ | ||
public Map<String, Set<ModuleInfo.Property>> getUnitTestToPropertiesMap() { | ||
return Collections.unmodifiableMap(unitTestToProperties); | ||
} | ||
|
||
/** | ||
* Gets the module config variable name, if it exists. | ||
* @param methodBlock the UT method block ast, which should have a type {@link TokenTypes#SLIST} | ||
* @return the optional variable name, if it exists | ||
*/ | ||
private static Optional<String> getModuleConfigVariableName(DetailAST methodBlock) { | ||
Optional<String> returnValue = Optional.empty(); | ||
|
||
for (DetailAST ast = methodBlock.getFirstChild(); ast != null; ast = ast.getNextSibling()) { | ||
if (ast.getType() == TokenTypes.VARIABLE_DEF) { | ||
final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); | ||
final DetailAST assign = ast.findFirstToken(TokenTypes.ASSIGN); | ||
if (isDefaultConfigurationType(type) && isCreateModuleConfigAssign(assign)) { | ||
returnValue = Optional.of(type.getNextSibling().getText()); | ||
break; | ||
} | ||
} | ||
} | ||
|
||
return returnValue; | ||
} | ||
|
||
/** | ||
* Checks whether this {@link TokenTypes#TYPE} ast is {@link DefaultConfiguration}. | ||
* @param ast the {@link TokenTypes#TYPE} ast | ||
* @return true if the type is {@link DefaultConfiguration} | ||
*/ | ||
private static boolean isDefaultConfigurationType(DetailAST ast) { | ||
return "DefaultConfiguration".equals(ast.getFirstChild().getText()); | ||
} | ||
|
||
/** | ||
* Checks whether this {@link TokenTypes#ASSIGN} ast contains | ||
* a {@code createModuleConfig} method call. | ||
* @param ast the {@link TokenTypes#ASSIGN} ast | ||
* @return true if the assignment contains a {@code createModuleConfig} method call | ||
*/ | ||
private static boolean isCreateModuleConfigAssign(DetailAST ast) { | ||
final boolean result; | ||
|
||
if (ast == null) { | ||
result = false; | ||
} | ||
else { | ||
final DetailAST exprChild = ast.getFirstChild().getFirstChild(); | ||
result = exprChild.getType() == TokenTypes.METHOD_CALL | ||
&& exprChild.getFirstChild().getType() == TokenTypes.IDENT | ||
&& "createModuleConfig".equals(exprChild.getFirstChild().getText()); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Gets all children of a ast with the given tokens type. | ||
* @param parent the parent ast | ||
* @param type the given tokens type | ||
* @return the children with the given tokens type | ||
*/ | ||
private static List<DetailAST> getAllChildrenWithToken(DetailAST parent, int type) { | ||
final List<DetailAST> returnValue = new LinkedList<>(); | ||
|
||
for (DetailAST ast = parent.getFirstChild(); ast != null; ast = ast.getNextSibling()) { | ||
if (ast.getType() == type) { | ||
returnValue.add(ast); | ||
} | ||
} | ||
|
||
return returnValue; | ||
} | ||
|
||
/** | ||
* Checks whether this expression is an {@code addAttribute} method call on an instance with | ||
* the given variable name. | ||
* @param ast the ast to check | ||
* @param variableName the given variable name of the module config instance | ||
* @return true if the expression is a valid {@code addAttribute} method call | ||
*/ | ||
private static boolean isAddAttributeMethodCall(DetailAST ast, String variableName) { | ||
final boolean result; | ||
|
||
if (ast.getType() == TokenTypes.METHOD_CALL | ||
&& ast.getFirstChild().getType() == TokenTypes.DOT) { | ||
final DetailAST dot = ast.getFirstChild(); | ||
result = variableName.equals(dot.getFirstChild().getText()) | ||
&& "addAttribute".equals(dot.getLastChild().getText()); | ||
} | ||
else { | ||
result = false; | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Converts an expression to raw text. | ||
* @param ast the expression ast to convert | ||
* @return the converted raw text | ||
*/ | ||
private static String convertExprToText(DetailAST ast) { | ||
final String original = ast.getFirstChild().getText(); | ||
return original.substring(1, original.length() - 1); | ||
} | ||
} |
Oops, something went wrong.