-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Move most skips to single file, expand API to handle conditions #1557
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.
- better renaming to avoid confusion
- consider refactoring
modifier
out of skip class
/** | ||
* Reactive only. | ||
*/ | ||
WAIT_FOR_BATCH_CURSOR_CREATION, |
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.
technically speaking, modifier
mechanism doesn't belong to skips
scope (as anticipated by the class name and its implementation) and adding it here seems unnatural and confusing.
Could we separate its concern into its own class? Then we could simplify the logic in this class a lot.
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.
Renamed class
if (this.shouldApply != null || this.matchWasPerformed) { | ||
throw new IllegalStateException("Condition must be specified first and once."); | ||
} | ||
this.shouldApply = condition; |
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.
both shouldApply
and condition
naming leave room of improvement. Currently the naming would not help too much on mastering the gist without reading code very carefully.
maybe condition
and shouldApply
could both be changed to precondition
(one example only) ? Then code reader might be less hard-pressed.
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.
Renamed
return this; | ||
public TestApplicator test(final String dir, final String file, final String test) { | ||
boolean match = testDef.dir.equals("unified-test-format/" + dir) | ||
&& testDef.file.equals(file) |
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 first two parameters are File system related, maybe using File
or Path
and go about comparison based on those classes might be more robust and a /
dir suffix would not lead to unexpected behaviour.
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 think we should not allow both "/path" and "path". Using non-strings would increase boilerplate.
this.testDef.modifiers.addAll(this.modifiersToApply); | ||
} | ||
} | ||
return 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.
the core of the coding logic in this whole class. It is still not intuitive and takes quite some scratching hairs to figure out. Some complexities include:
- ensure
condition
is the very first fluent usage, if used; - matching includes both 'skip matching' and 'modifier matching';
- matching should be the step AFTER
condition
, somatchWasPerformed
variable is introduced.
From my understanding, the logic is a little bit too intertwining and unnatural. I think each of the three above concerns could have natural and elegant solution:
- if
condition
is the very first input if provided, we could keep it from fluent usage scope and allocate a constructor parameter for it; then no trouble at all and no further clumsy coding is needed - I highly recommend refactoring
modifier
to its own class; are they only relevant to reactive testing? Then maybe reactive unified tester is a better location; - the above code would be simplified so much. The confusing
shouldSkip
andmatchWasPerformed
could be deleted.
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 1, the constructors are not used directly. I am not sure how to interpret this, except to produce multiple methods (skipJira(string)
and skipJira(string, condition)
), but then the readability suffers when the API is used.
For 2, I am not sure if the suggestion is to move invocations to a separate file (I think it is better to show how we are handling a specific set of tests in one place), or to copy/extend TestApplicator (I would need to better understand the benefits of this).
For 3, I think this focuses on the internals, rather than the API surface, which is less important. For any API, the most important thing (in my opinion) is how easy it is to read, then how easy it is to use, and only then how easy it is to modify the internals (however, I do not see these internals as particularly complex).
/** | ||
* The test is skipped for an unknown reason. | ||
*/ | ||
public TestApplicator skipTodo(final String skip) { |
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.
skipTodo
is a little bit confusing. sounds like skipping todo
or no-op.
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.
Renamed
@@ -56,69 +305,210 @@ public Skip skipJira(final String skip) { | |||
* | |||
* @param skip reason for skipping the test | |||
*/ | |||
public Skip skipDeprecated(final String skip) { | |||
return new Skip(this, skip); | |||
public TestApplicator skipDeprecated(final String skip) { |
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 naming of skip
is not accurate. Guess the intention is reason
or suchlike. I would anticipate a boolean type parameter for skip
so when reading code logic, I need to translate skip
to skipReason
all the time, ending up with burden and pressure unnecessarily.
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 suppresses IntelliJ's parameter hinting. I have renamed it.
// retryable-writes | ||
|
||
def.skipJira("https://jira.mongodb.org/browse/JAVA-5125") | ||
.when(() -> isSharded() && serverVersionLessThan(5, 0)) |
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.
From my personal preference, I prefer the normal if block as following for it is the most natural, flexible and powerful:
if (isSharded() && serverVersionLessThan(5, 0)) {
def.skipJira()
.test(...)
...
}
Needless to say, the core code logic would be simplified to minimal for lots of details are simply for that when()
usage. Is it necessary? I doubt. From coding perspective, it looks elegant and fluent, but if we take code maintaining cost into consideration, it might not be worth while. My personal preference, of course.
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 API is, I think, a way of abstracting away "if" - that is, skip and file are just if statements. For example, assumeFalse
is another such API, which is only there to "abstract" away if statements. I do not think we should mix the approaches, without good reason. I originally had isSharded
as an instance method, and this was redundant with the static method, which was a downside, but here, the lambda ensures that we can use static methods. I think this approach produces a cleaner API.
import static org.junit.jupiter.api.Assumptions.assumeFalse; | ||
|
||
public final class UnifiedTestSkips { | ||
public static void doSkips(final TestDef def) { | ||
|
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.
From my personal perspective, centralizing skips has pros and cons. The previous way has its pros. For one thing they are predicatable and easy to locate. Now it becomes hard to filter which ones is relevant for some specific unified testing (the test dir might be a clue but my first reaction is that might not be enough and I might start doubting the benefit of centralizing skips here).
The only undeniable benefit is to dedup across sync and reactive skips. As long as that could be done (there might be easy way like moving common stuff into some parent class?), honestly I think such coding effort might become a future maintaining burden.
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 primary benefit is that one is able to more easily see what test-specific custom logic the driver has, without needing to check multiple files, many of which may not actually have their own custom logic.
It also incrementally moves us to having a single file for tests, rather than multiple files.
ClientSideOperationTimeoutTest
.