-
Notifications
You must be signed in to change notification settings - Fork 295
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
Enhanced Serialization Test Infrastructure #579
Conversation
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.
Overall this change looks good. I have some minor comments
values.length == 10, | ||
"Needs exactly 10 values to create FixDisplay object but found: " + values.length); | ||
// Fixes are written in Temp Directory and is not known at compile time, therefore, | ||
// relative |
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.
extra newline in comment
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.
public class SerializationTestHelper { | ||
private FixDisplay[] expectedOutputFixes; | ||
private final Path outputPath; | ||
public class SerializationTestHelper<T> { |
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.
public class SerializationTestHelper<T> { | |
public class SerializationTestHelper<T extends Display> { |
See other comment about Display
interface
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.
Hi @msridhar, I believe this is ready for another review. |
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.
LGTM, just a couple more minor things
private final DisplayFactory<FixDisplay> fixDisplayFactory; | ||
private final DisplayFactory<ErrorDisplay> errorDisplayFactory; | ||
|
||
enum Modes { |
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.
This enum seems like overkill. I think you could just have four static final
fields, two for the filenames and two for the headers, and use those.
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.
} | ||
|
||
private void clearOutput() { | ||
public SerializationTestHelper<T> setOutputFileName(String fileName, String header) { |
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.
public SerializationTestHelper<T> setOutputFileName(String fileName, String header) { | |
public SerializationTestHelper<T> setOutputFileNameAndHeader(String fileName, String header) { |
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.
@msridhar, resolved the comments. Ready for review. |
Adding @lazaroclapp in case he wants to review before landing |
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.
For the most part, this looks good, some comments below!
|
||
/** | ||
* Helper class to represent a {@link com.uber.nullaway.fixserialization.out.ErrorInfo} contents in | ||
* {@code String}. |
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.
"in String" or "in a test case's (expected or actual) output." ? (Similar fix for FixDispaly
, although it wasn't introduced in this PR)
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.
} | ||
ErrorDisplay that = (ErrorDisplay) o; | ||
return type.equals(that.type) | ||
&& (message.contains(that.message) || that.message.contains(message)) |
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.
Is there a reason for this double contains test as opposed to .equals(...)
between the strings? What am I missing?
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.
Yes, the reason is that actual complete error messages from NullAway
can be really long and may make the tests less readable and may not be the primary intention of the test. For instance, an actual error reported by NullAway
is:
initializer method does not guarantee @NonNull field foo (line 4) is initialized along all control-flow paths (remember to check for exceptions or early returns).
Which with this approach it is simply written in the test as initializer method does not guarantee @NonNull field foo
.
This is very similar to // BUG: Diagnostic contains:
approach. Please let me know if you rather to see the complete error message.
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.
Ah, makes sense to me. I think I was thinking this was an &&
, but it's basically if either message is a substring of the other. Makes sense! Maybe worth a quick comment on that line.
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.
public class SerializationTestHelper<T extends Display> { | ||
|
||
private final Path outputDir; | ||
private List<T> expectedOutputs; |
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.
Any reason for this being a mutable list rather than ImmutableList
?
Note: I get why actualOutputs
is mutable, but we don't ever change expectedOutputs
(which makes sense), so it probably should be immutable.
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.
.append("\n"); | ||
} | ||
fail(errorMessage.toString()); | ||
} | ||
|
||
private FixDisplay[] readOutputFixes() { | ||
ArrayList<FixDisplay> fixDisplays = new ArrayList<>(); | ||
private List<T> readActualOutputs(Path output, String header) { |
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.
The name of the argument output
refers to the file path, the name outputs
refers to individual items of type T
found in the output of the test case. This is confusing (it is easy, when reading the code below, to fall into the trap of thinking than output
is an element of outputs
). I'd actually recommend this argument keep the older name of outputPath
to distinguish it from the actual outputs.
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.
Yes, sure. Thank you very much for noting that.
} | ||
compilationTestHelper.doTest(); | ||
List<T> actualOutputs = readActualOutputs(outputPath, header); |
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.
Why is header
passed here, if it's already a field of this
?
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.
Oh sorry, it was left from old codes.
c10a7d2
// paths are getting compared. | ||
fixDisplay.uri = fixDisplay.uri.substring(fixDisplay.uri.indexOf("com/uber/")); | ||
fixDisplays.add(fixDisplay); | ||
T outputInstance = factory.fromValuesInString(line.split("\\t")); |
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.
If the argument is renamed outputPath
, then it would make a lot of sense for this to be T output
.
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.
.doTest(); | ||
} | ||
|
||
@Test | ||
public void test_custom_annot() { | ||
public void SuggestCustomAnnotTest() { |
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.
This is a method, name should start with lowercase.
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.
.doTest(); | ||
} | ||
|
||
@Test | ||
public void test_method_param_protection_test() { | ||
public void ExamineMethodParamProtectionTest() { |
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.
(See comment above: lowercase first letter)
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.
private String configPath; | ||
private Path root; |
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.
@msridhar The output files these test read are controlled by the path passed when constructing the SerializationTestHelper<>
object plus the file passed to .expectedOutputs(...)
. For most of these tests, that is ${this.root}/${this.SUGGEST_FIX_FILE_NAME}
.
That means these tests can't ever run in parallel. Just out of curiosity, is there any guarantee for gradle/JUnit such as: different test classes run in parallel, but test cases within the same @RunWith
annotated class run serially?
That would make sense to me, and would mean this is correct. But I can't find no such guarantee in the gradle parallel testing docs, just a warning that:
When using parallel test execution, make sure your tests are properly isolated from one another. Tests that interact with the filesystem are particularly prone to conflict, causing intermittent test failures.
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.
@lazaroclapp AFAIK Gradle will not run test methods within the same test class in parallel with each other. It only runs test methods from different test classes in parallel. This could cause a problem down the road if we split this test into multiple tests I guess. Could we fix by using a unique temp (sub-)directory root
in every test?
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.
This would work even if we split the test into multiple classes, because each such class will likely have its own private Path root
instance. My only worry here is if there is some aggressively parallel JUnit+Gradle mode that runs a unit tests within the same test class in parallel, if you don't think that's likely (and it would break a lot of setUp(...)
methods I've seen), then this is probably fine as it is.
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.
I am assuming the latest decision is to leave this as it is. In that case this is ready for another round of review.
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.
Looking more, I think the way @Rule
works, the temporary folder should already get created fresh for each test and deleted afterward. Due to how these things work, I don't think any test runner would ever attempt to run tests within a class in parallel. So I think this code is fine as-is. That said, I did find another change we should make that I will comment on separately.
@@ -59,12 +59,12 @@ public SerializationTestHelper(Path outputDir) { | |||
|
|||
@SafeVarargs | |||
public final SerializationTestHelper<T> setExpectedOutputs(T... outputs) { | |||
this.expectedOutputs = Arrays.asList(outputs); | |||
this.expectedOutputs = ImmutableList.copyOf(Arrays.asList(outputs)); |
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.
You can directly pass the outputs
array to copyOf(...)
. There is a version of that method that takes an array and this avoids the double list construction.
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.
Oh, great. Looks much better.
@@ -70,8 +102,9 @@ public void cleanup() { | |||
} |
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.
This cleanup()
method should be deleted. JUnit will already take care of deleting the temporaryFolder
after each test.
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.
Oh great, Deleted it.
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.
LGTM!
Following Type Change Serialization PR NullAway can serialize information regarding the source code through the creation of different objects (e.g.
ErrorInfo
andSuggestedFixInfo
).The current testing infrastructure works as below:
A
SerializationTestHelper
object is instantiated and the input source code is passed with the API below:And the expected
FixDisplay
is passed to thetester
using the API below:And finally after setting the correct flags, the
doTest()
method is invoked.At this moment,
SerializationTestHelper
instance will only look forfixes.tsv
file, and for each line in the file, it creates the correspondingFixDisplay
instances, therefore, the expected output can only be in the type ofFixDisplay
. Also, no test is created to verify correctness of error serialization feature. SinceSerializationTestHelper
is not flexible to test different output types, it cannot be reused in testing upcoming features of serialization package.This PR generalizes the
SerializationTestHelper
class to be reusable with any type of serialization output.With the new implementation,
tester
instance will look for the target file which is configurable, verifies the correctness of the header and then creates instances ofT
per line in the output file and executes the tests.Also tests to cover error serialization is added.
Regards