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

KAFKA-17587 Refactor test infrastructure #18602

Merged
merged 25 commits into from
Jan 24, 2025

Conversation

mumrah
Copy link
Member

@mumrah mumrah commented Jan 17, 2025

This patch reorganizes our test infrastructure into three Gradle modules:

":test-common:test-common-internal-api" is now a minimal dependency which exposes interfaces and annotations only. It has one project dependency on server-common to expose commonly used data classes (MetadataVersion, Feature, etc). Since this pulls in server-common, this module is Java 17+. It cannot be used by ":clients" or other Java 11 modules.

":test-common:test-common-util" includes the auto-quarantined JUnit extension. The @Flaky annotation has been moved here. Since this module has no project dependencies, we can add it to the Java 11 list so that ":clients" and others can utilize the @Flaky annotation

":test-common:test-common-runtime" now includes all of the test infrastructure code (TestKitNodes, etc). This module carries heavy dependencies (core, etc) and so it should not normally be included as a compile-time dependency.

In addition to this reorganization, this patch leverages JUnit SPI service discovery so that modules can utilize the integration test framework without depending on ":core". This will allow us to start moving integration tests out of core and into the appropriate sub-module. This is done by adding ":test-common:test-common-runtime" as a testRuntimeOnly dependency rather than as a testImplementation dependency. A trivial example was added to QuorumControllerTest to illustrate this.

This patch does not expose ClusterInstance through the SPI since it has many direct dependencies on core. A stripped-down version of ClusterInstance that can be used by non-core modules remains as future work.

@github-actions github-actions bot added core Kafka Broker tools kraft build Gradle build or GitHub Actions labels Jan 17, 2025
@github-actions github-actions bot added storage Pull requests that target the storage module tiered-storage Related to the Tiered Storage feature labels Jan 18, 2025
Copy link
Member

@chia7712 chia7712 left a comment

Choose a reason for hiding this comment

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

@mumrah thanks for this refactor. I run the command ./gradlew cleanTest test-common:test-common-runtime:test and it results in some error below:

Gradle Test Run :test-common:test-common-runtime:test > Gradle Test Executor 109 > TestKitNodeTest > testSecurityProtocol(SecurityProtocol) > initializationError FAILED
    java.lang.IllegalStateException: Please annotate test methods with @ClusterTemplate, @ClusterTest, or @ClusterTests when using the ClusterTestExtensions provider
        at org.apache.kafka.common.test.junit.ClusterTestExtensions.provideTestTemplateInvocationContexts(ClusterTestExtensions.java:147)
        at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:273)
        at java.base/java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1708)
        at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509)
        at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499)
        at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
        at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
        at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
        at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)

build.gradle Outdated Show resolved Hide resolved
@@ -79,7 +79,7 @@ a JUnit extension called `ClusterTestExtensions` which knows how to process thes
invocations. Test classes that wish to make use of these annotations need to explicitly register this extension:

```scala
import org.apache.kafka.common.test.api.ClusterTestExtensions
import org.apache.kafka.common.test.junit.ClusterTestExtensions

@ExtendWith(value = Array(classOf[ClusterTestExtensions]))
Copy link
Member

Choose a reason for hiding this comment

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

we don't need it, right?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. I'll update these docs.

import org.apache.kafka.common.config.SaslConfigs
import org.apache.kafka.common.config.internals.BrokerSecurityConfigs
import org.apache.kafka.common.message.SaslHandshakeRequestData
import org.apache.kafka.common.protocol.{ApiKeys, Errors}
import org.apache.kafka.common.requests.{ApiVersionsRequest, ApiVersionsResponse, SaslHandshakeRequest, SaslHandshakeResponse}
import org.apache.kafka.common.security.auth.SecurityProtocol
import org.apache.kafka.common.test.ClusterInstance
import org.apache.kafka.common.test.junit.ClusterTestExtensions
Copy link
Member

Choose a reason for hiding this comment

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

Should we remove @ExtendWith(value = Array(classOf[ClusterTestExtensions])) from this class?

Copy link
Member Author

Choose a reason for hiding this comment

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

Wow, good catch 😄 I left this one here intentionally to make sure it still worked if people explicitly add the annotation in the future

Copy link
Member Author

Choose a reason for hiding this comment

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

Well this was a bad choice for a test since this one is disabled. I tried it locally on another test and verified keeping the annotation is harmless

@mumrah
Copy link
Member Author

mumrah commented Jan 18, 2025

@chia7712 I think this commit should fix the initialization issue. Since SPI is being used, it loads the extension for all tests. We need some additional filtering to not apply the ClusterTest logic to regular tests.

@ijuma
Copy link
Member

ijuma commented Jan 18, 2025

Thanks for the PR. A couple of questions:

  1. Do we intend to have a common test module for clients and streams (aside from junit specific classes)?
  2. If we were to provide a test module for external usage, what would we call it?

These questions will help validate the names we have chosen.

@mumrah
Copy link
Member Author

mumrah commented Jan 18, 2025

@ijuma,

Do we intend to have a common test module for clients and streams (aside from junit specific classes)?

I think that would make sense. There's quite a bit of utility test code that could be kept at Java 11. In this case we might consider test-common-util instead of test-common-junit

If we were to provide a test module for external usage, what would we call it?

I was thinking testkit or test-kit (so kafka-testkit.jar or kafka-test-kit.jar). Are you thinking we go ahead and establish a public-friendly name for the test-common-api module?

@ijuma
Copy link
Member

ijuma commented Jan 18, 2025

Makes sense to use the testkit name for the external dependency and going with util for a more general module name that can include shared junit stuff but also other useful things needed by all test modules.

The only open question for me is the test-common-api name. Even though it makes sense, we typically use api for modules that expose apis externally (group-coordinator-api, storage-api, tools-api, etc.). In this case, it's a purely internal module, so I wonder if we can find a different name to describe it.

@mumrah
Copy link
Member Author

mumrah commented Jan 18, 2025

Hm, yea.. I see what you mean. Here are some ideas for alternatives to test-common-api

  • test-common-private-api
  • test-common-defs
  • test-common-lib
  • test-common-spi (sort of a misnomer)
  • test-common

@ijuma
Copy link
Member

ijuma commented Jan 18, 2025

Since you're calling one test-common-runtime, would test-common-compile be appropriate? Or perhaps test-common-internal-api (we already use internals in our package names). The latter could be used for any module that exposes internal apis, so perhaps it a reasonable pattern to establish.

@mumrah
Copy link
Member Author

mumrah commented Jan 18, 2025

I like "internal-api" 👍

build.gradle Outdated Show resolved Hide resolved
testImplementation project(':test-common:test-common-api')
testImplementation project(':test-common:test-common-runtime')
testImplementation project(':test-common:test-common-internal-api')
testImplementation project(':test-common:test-common-util')
Copy link
Member

Choose a reason for hiding this comment

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

It seems clients, core, and connect:runtime need this because they need to import Flaky. Maybe we can move Flaky to test-common:test-common-internal-api? test-common:test-common-internal-api can be under java 11 if we move server-common out of test-common:test-common-internal-api. test-common:test-common-internal-api is using enum classes of server-common so they can be replaced by string easily. With that change, we can move AutoQuarantinedTestFilter and QuarantinedPostDiscoveryFilter to test-common:test-common-runtime to remove the test-common:test-common-util also

Copy link
Member Author

Choose a reason for hiding this comment

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

Let me see if I understand your proposal

  • Refactor "test-common-internal-api" to not depend on ":server-common"
  • Move "test-common-internal-api" to Java 11
  • Move @Flaky to the "test-common-internal-api"
  • Remove "test-common-util"

I think this would make sense, except that Java 11 modules like ":clients" cannot bring a runtime dependency with a newer java version. I tried this and got:

* What went wrong:
Could not determine the dependencies of task ':clients:test'.
> Could not resolve all dependencies for configuration ':clients:testRuntimeClasspath'.
   > Could not resolve project :test-common:test-common-runtime.
     Required by:
         project :clients
      > Dependency resolution is looking for a library compatible with JVM runtime version 11, but 'project :test-common:test-common-runtime' is only compatible with JVM runtime version 17 or newer.

Do you know of a way around this?


Since test-common-internal-api is meant to be the abstraction for the test infrastructure offered by test-common-runtime, it doesn't make sense to include one without the other. It might be confusing for developers if @ClusterTest was available to import, but was not actually usable.

If we want the client integration tests to be moved out of core, we should create a ":clients:integration-tests" module that is Java 17 and move the tests there (similar to what streams does).

I expect "test-common-util" will grow to include helper/common test methods. We can probably combine the most of the various TestUtils into a single Java 11 utils class under this module.

Copy link
Member

Choose a reason for hiding this comment

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

Since test-common-internal-api is meant to be the abstraction for the test infrastructure offered by test-common-runtime, it doesn't make sense to include one without the other. It might be confusing for developers if @clustertest was available to import, but was not actually usable.

Yes, you're right. Let's keep the current structure.

@mumrah
Copy link
Member Author

mumrah commented Jan 22, 2025

@ijuma @chia7712 did you have any further comments on this PR?

@chia7712
Copy link
Member

@mumrah could you please fix the conflicts? Additionally, should we backport it to 4.0?

@mumrah
Copy link
Member Author

mumrah commented Jan 23, 2025

@chia7712 yea I can backport it. That will make it easier to backport test changes during the ongoing 4.0.0 release and down the road in 4.0.x

Copy link
Member

@chia7712 chia7712 left a comment

Choose a reason for hiding this comment

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

LGTM

@mumrah mumrah merged commit 8c0a0e0 into apache:trunk Jan 24, 2025
8 of 9 checks passed
mumrah added a commit to mumrah/kafka that referenced this pull request Jan 24, 2025
This patch reorganizes our test infrastructure into three Gradle modules:

":test-common:test-common-internal-api" is now a minimal dependency which exposes interfaces and annotations only. It has one project dependency on server-common to expose commonly used data classes (MetadataVersion, Feature, etc). Since this pulls in server-common, this module is Java 17+. It cannot be used by ":clients" or other Java 11 modules.

":test-common:test-common-util" includes the auto-quarantined JUnit extension. The @flaky annotation has been moved here. Since this module has no project dependencies, we can add it to the Java 11 list so that ":clients" and others can utilize the @flaky annotation

":test-common:test-common-runtime" now includes all of the test infrastructure code (TestKitNodes, etc). This module carries heavy dependencies (core, etc) and so it should not normally be included as a compile-time dependency.

In addition to this reorganization, this patch leverages JUnit SPI service discovery so that modules can utilize the integration test framework without depending on ":core". This will allow us to start moving integration tests out of core and into the appropriate sub-module. This is done by adding ":test-common:test-common-runtime" as a testRuntimeOnly dependency rather than as a testImplementation dependency. A trivial example was added to QuorumControllerTest to illustrate this.

Reviewers: Ismael Juma <[email protected]>, Chia-Ping Tsai <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
build Gradle build or GitHub Actions clients consumer core Kafka Broker kraft storage Pull requests that target the storage module tiered-storage Related to the Tiered Storage feature tools
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants