-
Notifications
You must be signed in to change notification settings - Fork 201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PiranhaJava] Remove configured test methods #151
base: master
Are you sure you want to change the base?
Changes from 2 commits
028fb16
46ed05a
2866b72
c6bf298
d29883a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,13 @@ The properties file has the following template: | |
}, | ||
... | ||
], | ||
"testMethodProperties": [ | ||
{ | ||
"methodName": "when", | ||
"argumentIndex": 0 | ||
}, | ||
... | ||
], | ||
"enumProperties": | ||
[ | ||
{ | ||
|
@@ -106,7 +113,13 @@ public void some_unit_test() { ... } | |
|
||
when `IsTreated` is `true`, and will be deleted completely when `IsTreated` is `false`. | ||
|
||
An optional top-level field is `enumProperties`. | ||
An optional top-level field is `testMethodProperties`. | ||
|
||
Within that, there is an array of JSON objects, having the required fields `methodName` and `argumentIndex`. The both behave the same as the fields with the same name in `methodProperties`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The -> They |
||
|
||
What this field does, is that if we find one of the `methodProperties` fields inside a method that matches one of the methods in `testMethodProperties`, we remove that method. This is useful for removing `mock()` wrappers or `assert()` calls that are no longer useful after a flag is cleaned up. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we refer this to as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we keep the pattern so they all still end in properties? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not even sure that Perhaps |
||
|
||
Another optional top-level field is `enumProperties`. | ||
Within that, there is an array of JSON objects, having the required fields `enumName` and `argumentIndex`. | ||
|
||
What this field does, is if you specify an enum class name, Piranha will remove enum constants that have a constructor with a string argument that matches your `FlagName` value, along with their usages. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,20 @@ | |
"argumentIndex": 0 | ||
} | ||
], | ||
"testMethodProperties": [ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One thing I wonder about test library methods vs flag-configuration-system methods, is that they tend to have fairly generic names (like Specially if this is going to ship as the default/base config file for Piranha. |
||
{ | ||
"methodName": "mock", | ||
"argumentIndex": 0 | ||
}, | ||
{ | ||
"methodName": "accept", | ||
"argumentIndex": 0 | ||
}, | ||
{ | ||
"methodName": "expect", | ||
"argumentIndex": 1 | ||
} | ||
], | ||
"linkURL": "<provide_your_url>", | ||
"annotations": [ | ||
"ToggleTesting", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -58,7 +58,8 @@ | |
import com.uber.piranha.config.Config; | ||
import com.uber.piranha.config.PiranhaConfigurationException; | ||
import com.uber.piranha.config.PiranhaEnumRecord; | ||
import com.uber.piranha.config.PiranhaMethodRecord; | ||
import com.uber.piranha.config.PiranhaFlagMethodRecord; | ||
import com.uber.piranha.config.PiranhaTestMethodRecord; | ||
import com.uber.piranha.testannotations.AnnotationArgument; | ||
import com.uber.piranha.testannotations.ResolvedTestAnnotation; | ||
import java.util.ArrayList; | ||
|
@@ -344,7 +345,7 @@ private API getXPAPI(ExpressionTree et, VisitorState state) { | |
} | ||
MemberSelectTree mst = (MemberSelectTree) mit.getMethodSelect(); | ||
String methodName = mst.getIdentifier().toString(); | ||
ImmutableCollection<PiranhaMethodRecord> methodRecords = | ||
ImmutableCollection<PiranhaFlagMethodRecord> methodRecords = | ||
this.config.getMethodRecordsForName(methodName); | ||
if (methodRecords.size() > 0) { | ||
return getXPAPI(mit, state, methodRecords); | ||
|
@@ -356,8 +357,8 @@ private API getXPAPI(ExpressionTree et, VisitorState state) { | |
private API getXPAPI( | ||
MethodInvocationTree mit, | ||
VisitorState state, | ||
ImmutableCollection<PiranhaMethodRecord> methodRecordsForName) { | ||
for (PiranhaMethodRecord methodRecord : methodRecordsForName) { | ||
ImmutableCollection<PiranhaFlagMethodRecord> methodRecordsForName) { | ||
for (PiranhaFlagMethodRecord methodRecord : methodRecordsForName) { | ||
// when argumentIndex is specified, if mit's argument at argIndex doesn't match xpFlagName, | ||
// skip to next method property map | ||
Optional<Integer> optionalArgumentIdx = methodRecord.getArgumentIdx(); | ||
|
@@ -880,19 +881,61 @@ public Description matchBinary(BinaryTree tree, VisitorState state) { | |
return Description.NO_MATCH; | ||
} | ||
|
||
private boolean matchTestMethod(MethodInvocationTree methodTree, VisitorState state) { | ||
Symbol receiverSymbol = ASTHelpers.getSymbol(methodTree.getMethodSelect()); | ||
String methodName = receiverSymbol.getSimpleName().toString(); | ||
for (PiranhaTestMethodRecord methodRecord : config.getTestMethodRecordsForName(methodName)) { | ||
Optional<Integer> argumentIdx = methodRecord.getArgumentIdx(); | ||
if (argumentIdx.isPresent()) { | ||
if (methodTree.getArguments().size() > argumentIdx.get()) { | ||
ExpressionTree argTree = methodTree.getArguments().get(argumentIdx.get()); | ||
API api = getXPAPI(argTree, state); | ||
if (api != API.UNKNOWN) { | ||
return true; | ||
} | ||
} | ||
} else { | ||
for (ExpressionTree argTree : methodTree.getArguments()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does this mean the flagTest API can occur at any index? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, but only if the argument index is not defined. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is consistent with other configuration options. Though, if I recall, the index-less config was mostly allowed for backwards compatibility. Maybe it should be index-required for configuration options going forward? |
||
API api = getXPAPI(argTree, state); | ||
if (api != API.UNKNOWN) { | ||
return true; | ||
} | ||
} | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
@Override | ||
public Description matchExpressionStatement(ExpressionStatementTree tree, VisitorState state) { | ||
if (shouldSkip(state)) return Description.NO_MATCH; | ||
if (overLaps(tree, state)) { | ||
return Description.NO_MATCH; | ||
} | ||
|
||
boolean updateCode = false; | ||
|
||
if (tree.getExpression().getKind().equals(Kind.METHOD_INVOCATION)) { | ||
MethodInvocationTree mit = (MethodInvocationTree) tree.getExpression(); | ||
ExpressionTree receiver = ASTHelpers.getReceiver(mit); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is this logic external to the |
||
if (receiver == null) { | ||
if (matchTestMethod(mit, state)) { | ||
updateCode = true; | ||
} | ||
} else if (receiver.getKind() == Kind.METHOD_INVOCATION) { | ||
if (matchTestMethod((MethodInvocationTree) receiver, state)) { | ||
updateCode = true; | ||
} | ||
} | ||
|
||
API api = getXPAPI(mit, state); | ||
if (api.equals(API.DELETE_METHOD) | ||
|| api.equals(API.SET_TREATED) | ||
|| api.equals(API.SET_CONTROL)) { | ||
updateCode = true; | ||
} | ||
|
||
if (updateCode) { | ||
Description.Builder builder = buildDescription(tree); | ||
SuggestedFix.Builder fixBuilder = SuggestedFix.builder(); | ||
fixBuilder.delete(tree); | ||
|
@@ -1192,7 +1235,7 @@ private void recursiveScanTestMethodStats( | |
// only when the flag name matches, and we want to verify that no calls are being made to | ||
// set | ||
// unrelated flags (i.e. count them in counters.allSetters). | ||
for (PiranhaMethodRecord methodRecord : config.getMethodRecordsForName(methodName)) { | ||
for (PiranhaFlagMethodRecord methodRecord : config.getMethodRecordsForName(methodName)) { | ||
if (methodRecord.getApiType().equals(XPFlagCleaner.API.SET_TREATED)) { | ||
counters.allSetters += 1; | ||
// If the test is asking for the flag in treated condition, but we are setting it to | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,7 +41,8 @@ public final class Config { | |
/* Names of top-level fields within properties.json */ | ||
private static final String LINK_URL_KEY = "linkURL"; | ||
private static final String ANNOTATIONS_KEY = "annotations"; | ||
private static final String METHODS_KEY = "methodProperties"; | ||
private static final String FLAG_METHODS_KEY = "methodProperties"; | ||
private static final String TEST_METHODS_KEY = "testMethodProperties"; | ||
private static final String CLEANUP_OPTS_KEY = "cleanupOptions"; | ||
private static final String ENUMS_KEY = "enumProperties"; | ||
|
||
|
@@ -64,13 +65,22 @@ public final class Config { | |
private static final boolean DEFAULT_TESTS_CLEAN_BY_SETTERS_IGNORE_OTHERS = false; | ||
|
||
/** | ||
* configMethodsMap is a map where key is method name and value is a list where each item in the | ||
* list is a map that corresponds to each method property from properties.json. In most cases, the | ||
* list would have only one element. But if someone reuses the same method name with different | ||
* configMethodProperties is a map where key is method name and value is a list where each item in | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch! 👍 |
||
* the list is a map that corresponds to each method property from properties.json. In most cases, | ||
* the list would have only one element. But if someone reuses the same method name with different | ||
* returnType/receiverType/argumentIndex, the list would have each method property map as one | ||
* element. | ||
*/ | ||
private final ImmutableMultimap<String, PiranhaMethodRecord> configMethodProperties; | ||
private final ImmutableMultimap<String, PiranhaFlagMethodRecord> configMethodProperties; | ||
|
||
/** | ||
* configTestMethodProperties is a map where key is method name and value is a list where each | ||
* item in the list is a map that corresponds to each test method property from properties.json. | ||
* In most cases, the list would have only one element. But if someone reuses the same method name | ||
* with different returnType/receiverType/argumentIndex, the list would have each method property | ||
* map as one element. | ||
*/ | ||
private final ImmutableMultimap<String, PiranhaTestMethodRecord> configTestMethodProperties; | ||
|
||
/** | ||
* configEnumProperties is a map where key is enum name and value is a list where each item in the | ||
|
@@ -99,12 +109,14 @@ public final class Config { | |
// Constructor is private, a Config object can be generated using the class' static methods, | ||
// in particular Config.fromJSONFile([properties.json]) | ||
private Config( | ||
ImmutableMultimap<String, PiranhaMethodRecord> configMethodProperties, | ||
ImmutableMultimap<String, PiranhaFlagMethodRecord> configMethodProperties, | ||
ImmutableMultimap<String, PiranhaTestMethodRecord> configTestMethodProperties, | ||
ImmutableMultimap<String, PiranhaEnumRecord> configEnumProperties, | ||
TestAnnotationResolver testAnnotationResolver, | ||
ImmutableMap<String, Object> cleanupOptions, | ||
String linkURL) { | ||
this.configMethodProperties = configMethodProperties; | ||
this.configTestMethodProperties = configTestMethodProperties; | ||
this.configEnumProperties = configEnumProperties; | ||
this.testAnnotationResolver = testAnnotationResolver; | ||
this.cleanupOptions = cleanupOptions; | ||
|
@@ -115,15 +127,29 @@ private Config( | |
* Return all configuration method records matching a given method name. | ||
* | ||
* @param methodName the method name to search | ||
* @return A collection of {@link PiranhaMethodRecord} objects, representing each method | ||
* @return A collection of {@link PiranhaFlagMethodRecord} objects, representing each method | ||
* definition in the piranha json configuration file matching {@code methodName}. | ||
*/ | ||
public ImmutableCollection<PiranhaMethodRecord> getMethodRecordsForName(String methodName) { | ||
public ImmutableCollection<PiranhaFlagMethodRecord> getMethodRecordsForName(String methodName) { | ||
return configMethodProperties.containsKey(methodName) | ||
? configMethodProperties.get(methodName) | ||
: ImmutableSet.of(); | ||
} | ||
|
||
/** | ||
* Return all configuration test method records matching a given method name. | ||
* | ||
* @param methodName the method name to search | ||
* @return A collection of {@link PiranhaTestMethodRecord} objects, representing each method | ||
* definition in the piranha json configuration file matching {@code methodName}. | ||
*/ | ||
public ImmutableCollection<PiranhaTestMethodRecord> getTestMethodRecordsForName( | ||
String methodName) { | ||
return configTestMethodProperties.containsKey(methodName) | ||
? configTestMethodProperties.get(methodName) | ||
: ImmutableSet.of(); | ||
} | ||
|
||
/** | ||
* Returns whether any configuration enum records exist. Useful for skipping logic if enum | ||
* properties are not configured. | ||
|
@@ -266,7 +292,9 @@ public static Config fromJSONFile(String configFile, boolean isArgumentIndexOpti | |
} | ||
|
||
String linkURL = DEFAULT_PIRANHA_URL; | ||
ImmutableMultimap.Builder<String, PiranhaMethodRecord> methodsBuilder = | ||
ImmutableMultimap.Builder<String, PiranhaFlagMethodRecord> methodsBuilder = | ||
ImmutableMultimap.builder(); | ||
ImmutableMultimap.Builder<String, PiranhaTestMethodRecord> testMethodsBuilder = | ||
ImmutableMultimap.builder(); | ||
ImmutableMultimap.Builder<String, PiranhaEnumRecord> enumsBuilder = | ||
ImmutableMultimap.builder(); | ||
|
@@ -294,17 +322,26 @@ public static Config fromJSONFile(String configFile, boolean isArgumentIndexOpti | |
} | ||
} | ||
} | ||
if (propertiesJson.get(METHODS_KEY) != null) { | ||
if (propertiesJson.get(FLAG_METHODS_KEY) != null) { | ||
for (Map<String, Object> methodProperty : | ||
(List<Map<String, Object>>) propertiesJson.get(METHODS_KEY)) { | ||
PiranhaMethodRecord methodRecord = | ||
PiranhaMethodRecord.parseFromJSONPropertyEntryMap( | ||
(List<Map<String, Object>>) propertiesJson.get(FLAG_METHODS_KEY)) { | ||
PiranhaFlagMethodRecord methodRecord = | ||
PiranhaFlagMethodRecord.parseFromJSONPropertyEntryMap( | ||
methodProperty, isArgumentIndexOptional); | ||
methodsBuilder.put(methodRecord.getMethodName(), methodRecord); | ||
} | ||
} else { | ||
throw new PiranhaConfigurationException("methodProperties not found, required."); | ||
} | ||
if (propertiesJson.get(TEST_METHODS_KEY) != null) { | ||
for (Map<String, Object> methodProperty : | ||
(List<Map<String, Object>>) propertiesJson.get(TEST_METHODS_KEY)) { | ||
PiranhaTestMethodRecord methodRecord = | ||
PiranhaTestMethodRecord.parseFromJSONPropertyEntryMap( | ||
methodProperty, isArgumentIndexOptional); | ||
testMethodsBuilder.put(methodRecord.getMethodName(), methodRecord); | ||
} | ||
} | ||
if (propertiesJson.get(ENUMS_KEY) != null) { | ||
for (Map<String, Object> enumProperty : | ||
(List<Map<String, Object>>) propertiesJson.get(ENUMS_KEY)) { | ||
|
@@ -327,6 +364,7 @@ public static Config fromJSONFile(String configFile, boolean isArgumentIndexOpti | |
} | ||
return new Config( | ||
methodsBuilder.build(), | ||
testMethodsBuilder.build(), | ||
enumsBuilder.build(), | ||
annotationResolverBuilder.build(), | ||
cleanupOptionsBuilder.build(), | ||
|
@@ -365,6 +403,7 @@ public static Config fromJSONFile(String configFile, boolean isArgumentIndexOpti | |
*/ | ||
public static Config emptyConfig() { | ||
return new Config( | ||
ImmutableMultimap.of(), | ||
ImmutableMultimap.of(), | ||
ImmutableMultimap.of(), | ||
TestAnnotationResolver.builder().build(), | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either no new line here, or add a new line on the other similar property "headers" above and below.