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

Support Cartesian products of environments in MultiEnvTestEngine #7816

Open
wants to merge 40 commits into
base: main
Choose a base branch
from

Conversation

colan-dremio
Copy link

@colan-dremio colan-dremio commented Dec 6, 2023

Currently, MultiEnvTestEngine only supports a single MultiEnvTestExtension at a time. This adds support for multiple MultiEnvTestExtensions. It will perform a Cartesian product of the registered environment IDs.

For example, it would become possible to test multiple Nessie client versions with multiple Nessie server versions through a combination of OlderNessieClientsExtension and OlderNessieServersExtension. With nessie.versions property set to 1,2, the following test environments would run:

  • [client:1][server:1]
  • [client:1][server:2]
  • [client:2][server:1]
  • [client:2][server:2]

This is also enables downstream projects to use their own implementations of MultiEnvTestExtension. For example, a downstream project could test multiple Nessie versions against multiple types of object storage.

Future work: Does not yet support complex @Nested tests. Two tests are added but @Disabled for future development, see onlyNestedTest and cartesianNestedTest.

@colan-dremio colan-dremio marked this pull request as draft December 6, 2023 20:04
Copy link
Member

@dimas-b dimas-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your contribution @colan-dremio !

This is just a preliminary quick review from my side :)

* </ul>
* will result in the following IDs:
* <ul>
* <li>[engine:nessie-multi-env][segmentA:1][segmentB:1][segmentC:1]</li>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe MultiEnvTestFilter needs to be updated to check whether all multi-env ID segments are recognised... Currently it checks that any match.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't mind doing this, but I'm unsure if it is necessary. This would couple the cartesian product logic to the filter. The MultiEnvTestEngine is responsible for expanding the environments. Perhaps other types of expansion would be desirable in the future? From the filter's perspective, it seems reasonable to include a test as long as we see at least one correct segment type. This shouldn't happen in practice, so either way is fine with me.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the time, I was not correct above about the expansion logic, but now that it is fixed it should hold.

# Conflicts:
#	compatibility/common/src/test/java/org/projectnessie/tools/compatibility/internal/TestNessieCompatibilityExtensions.java
# Conflicts:
#	testing/multi-env-test-engine/src/test/java/org/projectnessie/junit/engine/TestMultiEnvTestEngine.java
@colan-dremio colan-dremio marked this pull request as ready for review May 16, 2024 00:39
@colan-dremio
Copy link
Author

Note: I am developing on a Mac and could not validate TestNessieCompatibilityExtensions::upgrade or TestNessieCompatibilityExtensions::tooManyExtensions locally.

this.delegate = delegate;
this.environment = environment;
this.environmentNames = environmentNames;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why rename? The String is opaque in this context, does not convey anything about names, IMHO.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On review, I meant environmentIds not names, updated accordingly. The goal was to give a bit of clarity into what the value holds. If we want to treat it as opaque, I can rename to suffix instead.


More generally, I've found that there is some ambiguity in naming. This is a multiple environment test engine, what is an environment? Take the example OlderNessieServersExtension (S1, S2) and OlderNessieClientsExtension (C1) which will run:

  • S1, C1
  • S2, C1

There are a number of concepts that need names:

  1. Each multienv extension (e.g. OlderNessieServersExtension)
  2. Each segement type (e.g. nessie-server-version)
  3. Each "environment id" within an extension (e.g. S1 and S2 individually)
  4. Each unique combination of environment ids on a test execution (e.g. [S1, C1])

If we go from the name here, it seems #4 is meant to be "environment"

Possible names to make this work:

  1. Dimension
  2. Dimension type
  3. Dimension element
  4. Environment

e.g.

public interface MultiEnvTestExtension extends Extension {
  String dimensionType();
  List<String> allDimensionElements(ConfigurationParameters configuration);
  default int dimensionPriority() {
    return 0;
  }
}

Thoughts?

Copy link
Member

@dimas-b dimas-b May 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This particular class is meant to make multi-env testa distinguishable in tools that do not understand Junit5 UniqueId (i.e. those that deal with test class/method names only). environmentIds works in this case, but I still think the rename is unnecessary in this case as the old environment works just as well :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. Dimention - SGTM (but let's wait a bit with renaming :) )
2. Dimention type - I prefer segment (current) as it related to JUnit5 ID segments.
3. Dimention elements - Maybe Category? (in the statistical sense)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keeping environment as # 4 is fine with me. I don't think "category" should be used for elements, "category" is synonymous with dimension type / segment type. Also, I don't think we should use "segment" alone because a UniqueId segment is the pair of segment type + segment value.

It looks like we should choose

  • One of (dimension | segment | category) for # 2
  • Whether or not to use "type" as a suffix for # 2. I'd avoid "segment" alone, but could see "category" or "category type" working fine.
  • One of (value | element | ID) for # 3

My vote at the moment is "dimension", "type", and "value":

public @interface MultiEnvDimensionType {}
public interface MultiEnvTestExtension extends Extension {
  List<String> allDimensionValues(ConfigurationParameters configuration);
  int segmentPriority()
}

For now, I've reverted these back to environment and will see what the rename would look like in practice.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proposed naming: colan-dremio@e4625b7

There are some subtle advantages here. For example in this block, it can be ambiguous about whether currentSegmentTypes/Values is referring to MultiEnv segments or all JUnit segments (engine, class, nested-class, method too). I had considered currentMultiEnvSegmentTypes/Values to clarify this. Now that it is currentDimensionTypes/Values, the ambiguity is also resolved.

List<TestDescriptor> newLeafNodes = new ArrayList<>();
for (TestDescriptor parentNode : latestLeafNodes) {
for (String environmentId :
multiEnvTestExtension.allEnvironmentIds(configurationParameters)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, I find this method of constructing a Cartesian product of environments a bit obscure. Why not simply have nested loops in one method (without calling out to this helper class)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved the tree construction logic directly into the engine implementation as part of fixing the updated cartesian test. The main annoyance is that the tree of nodes (represented by recursive child references) sits independently of the UniqueIds of those nodes. My new implementation still maintains a cache of known nodes to keep lookup simple, but should be simpler overall.

Copy link
Member

@dimas-b dimas-b left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only a partial review this time, but I think we're getting close to the end :)

@@ -22,13 +22,13 @@
/**
* Interface for JUnit5 test extensions that require running the same suite of tests in multiple
* executions environments. For example, running the same tests for multiple versions of a Nessie
* Client.
* Client.<br>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just <p>? cf.

* <p>Use the functions that return {@link Stream}s in the builders that implement {@link

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.filter(MultiEnvTestExtension.class::isAssignableFrom)
.flatMap(registry::stream);
return r;
return findMultiEnvTestExtensionsOn(testClass).flatMap(registry::stream);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use it in registerExtensions too?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -51,19 +51,6 @@ public Stream<MultiEnvTestExtension> stream() {
}

public Stream<? extends MultiEnvTestExtension> stream(Class<?> testClass) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stream() (no args) is unused now.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, my IDE seems to be confused about this one. Removed.

}

/** Immutable key of segment types for the intermediate cartesian product tree. */
private static class SegmentTypes {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not @Value.Immutable? It would be possible to add default helper methods for .root() and .append(String child) even if SegmentTypes were an interface.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 166 to 167
putTestIntoParent(
testDescriptor, nodeAtCurrentPosition, environmentNames, discoveryRequest);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might still be preferable to use the old resolveSelectors(...) method... How does the new code behave when (e.g. IDE) selects only one test method for execution? For example, run the whole test suite, but then select only one test in one particular environment for debugging in IntelliJ.

In this PR in my env. that will run the test method in all environments, before the PR, only in one env (as it should).

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this back. It had some interesting side effects where resolution would discover children that do not apply. With some post-filtering, it looks like this works better.

Added tests to cover these scenarios.

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.

2 participants