Skip to content
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

Merged
merged 6 commits into from
Nov 20, 2024

Conversation

katcharov
Copy link
Contributor

  • Moves skips to a single file.
  • Cleans up skips. Adds explicit files, or full test names, to disambiguate. Expands on reasons for skips, or associates particular Jira tickets, based on blame and associated tickets/PRs. Some skips are still incomplete.
  • Expands API. Adds a mechanism to apply certain "modifiers" (ignoring extra events) for tests, instead of skipping them. This removes custom logic from test subclasses.
  • Leaves one unified test with custom logic, ClientSideOperationTimeoutTest.

@katcharov katcharov requested review from a team, rozza and NathanQingyangXu and removed request for a team and rozza November 4, 2024 18:14

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
Copy link
Contributor

@NathanQingyangXu NathanQingyangXu left a 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

Sorry, something went wrong.

/**
* Reactive only.
*/
WAIT_FOR_BATCH_CURSOR_CREATION,
Copy link
Contributor

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.

Copy link
Contributor Author

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;
Copy link
Contributor

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.

Copy link
Contributor Author

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)
Copy link
Contributor

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.

Copy link
Contributor Author

@katcharov katcharov Nov 14, 2024

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;
Copy link
Contributor

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:

  1. ensure condition is the very first fluent usage, if used;
  2. matching includes both 'skip matching' and 'modifier matching';
  3. matching should be the step AFTER condition, so matchWasPerformed 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:

  1. 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
  2. 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;
  3. the above code would be simplified so much. The confusing shouldSkip and matchWasPerformed could be deleted.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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.

Copy link
Contributor Author

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))
Copy link
Contributor

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.

Copy link
Contributor Author

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) {

Copy link
Contributor

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.

Copy link
Contributor Author

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.

@katcharov katcharov merged commit 90976e4 into main Nov 20, 2024
58 of 60 checks passed
@katcharov katcharov deleted the unified-test-skips-file branch November 20, 2024 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants