This is an automated email from the ASF dual-hosted git repository.

ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new 4192dfc4aa1 IGNITE-26511 Improve Java API compatibility check (#6733)
4192dfc4aa1 is described below

commit 4192dfc4aa1215d9ddb4ecd839841b351ffc251a
Author: Vadim Pakhnushev <[email protected]>
AuthorDate: Mon Oct 13 10:35:44 2025 +0300

    IGNITE-26511 Improve Java API compatibility check (#6733)
---
 modules/compatibility-tests/build.gradle           |   1 +
 .../ignite/internal/ItApiCompatibilityTest.java    |  50 ----------
 .../ignite/internal/ApiCompatibilityTest.java      |  98 +++++++++++++++++++
 .../ignite/internal/OpenApiCompatibilityTest.java  |   7 +-
 .../ignite/internal/CompatibilityTestBase.java     |   4 +-
 .../org/apache/ignite/internal/IgniteVersions.java |  25 ++++-
 .../org/apache/ignite/internal/RunnerNode.java     |  30 ++----
 .../api/ApiCompatibilityExtension.java             |  68 --------------
 .../compatibility/api/ApiCompatibilityTest.java    |  77 ---------------
 .../api/ApiCompatibilityTestInvocationContext.java |  43 ---------
 .../compatibility/api/CompatibilityChecker.java    |  27 ++++--
 .../compatibility/api/CompatibilityExtension.java  |  55 -----------
 .../compatibility/api/CompatibilityInput.java      |  85 +++++++++++++----
 .../compatibility/api/CompatibilityOutput.java     |  50 ----------
 .../internal/compatibility/api/MethodProvider.java | 104 ---------------------
 .../compatibility/api/TestNameFormatter.java       |  41 --------
 16 files changed, 218 insertions(+), 547 deletions(-)

diff --git a/modules/compatibility-tests/build.gradle 
b/modules/compatibility-tests/build.gradle
index 73179666392..2609769194e 100644
--- a/modules/compatibility-tests/build.gradle
+++ b/modules/compatibility-tests/build.gradle
@@ -33,6 +33,7 @@ repositories {
 
 dependencies {
     testImplementation libs.swagger.parser
+    testImplementation project(':ignite-core')
 
     integrationTestImplementation libs.netty.common
     integrationTestImplementation libs.netty.buffer
diff --git 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/ItApiCompatibilityTest.java
 
b/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/ItApiCompatibilityTest.java
deleted file mode 100644
index 954a305fa3c..00000000000
--- 
a/modules/compatibility-tests/src/integrationTest/java/org/apache/ignite/internal/ItApiCompatibilityTest.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal;
-
-import org.apache.ignite.internal.compatibility.api.ApiCompatibilityTest;
-import org.apache.ignite.internal.compatibility.api.CompatibilityOutput;
-
-class ItApiCompatibilityTest {
-
-    // TODO resolve or explain exclusions 
https://issues.apache.org/jira/browse/IGNITE-26365
-    @ApiCompatibilityTest(
-            exclude = ""
-                    + "org.apache.ignite.Ignite#clusterNodes();" // deprecated
-                    + "org.apache.ignite.Ignite#clusterNodesAsync();" // 
deprecated
-                    + 
"org.apache.ignite.catalog.IgniteCatalog#dropTable(java.lang.String);" // 
method abstract now default
-                    + 
"org.apache.ignite.catalog.IgniteCatalog#dropTableAsync(java.lang.String);" // 
method abstract now default
-                    + 
"org.apache.ignite.catalog.IgniteCatalog#tableDefinition(java.lang.String);" // 
method abstract now default
-                    + 
"org.apache.ignite.catalog.IgniteCatalog#tableDefinitionAsync(java.lang.String);"
 // method abstract now default
-                    + "org.apache.ignite.compute.ColocatedJobTarget;" // 
method return type changed
-                    + "org.apache.ignite.compute.TableJobTarget;" // method 
return type changed
-                    + "org.apache.ignite.lang.ColumnNotFoundException;" // 
deprecated
-                    + "org.apache.ignite.lang.IndexAlreadyExistsException;" // 
deprecated
-                    + "org.apache.ignite.lang.IndexNotFoundException;" // 
deprecated
-                    + "org.apache.ignite.lang.TableAlreadyExistsException;" // 
deprecated
-                    + "org.apache.ignite.lang.TableNotFoundException;" // 
constructor removed
-                    + "org.apache.ignite.lang.util.IgniteNameUtils;" // 
methods removed, less accessible
-                    + "org.apache.ignite.sql.IgniteSql;" // method abstract 
now default
-                    + "org.apache.ignite.table.DataStreamerTarget;" // method 
abstract now default
-                    + "org.apache.ignite.table.IgniteTables;" // method 
abstract now default
-                    + "org.apache.ignite.table.QualifiedName;" // now final, 
serializable
-                    + "org.apache.ignite.table.Table;" // method abstract now 
default
-    )
-    void testApiModule(CompatibilityOutput output) {}
-
-}
diff --git 
a/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/ApiCompatibilityTest.java
 
b/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/ApiCompatibilityTest.java
new file mode 100644
index 00000000000..70ee1d49470
--- /dev/null
+++ 
b/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/ApiCompatibilityTest.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import org.apache.ignite.internal.compatibility.api.CompatibilityChecker;
+import org.apache.ignite.internal.properties.IgniteProperties;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+class ApiCompatibilityTest {
+    // Change to true to test compatibility between consecutive old versions
+    private static final boolean INCLUDE_HISTORIC_DATA = false;
+
+    // Map from old version to the list of excludes for the consecutive 
version.
+    // Read this as "what is broken after this version"
+    private static final Map<String, List<String>> EXCLUDES_PER_VERSION = 
Map.of(
+            "3.0.0", List.of(
+                    "org.apache.ignite.Ignite#clusterNodes()", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.Ignite#clusterNodesAsync()", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.catalog.IgniteCatalog#dropTable(java.lang.String)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.catalog.IgniteCatalog#dropTableAsync(java.lang.String)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.catalog.IgniteCatalog#tableDefinition(java.lang.String)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.catalog.IgniteCatalog#tableDefinitionAsync(java.lang.String)",
 // METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.compute.ColocatedJobTarget#tableName()", // 
METHOD_RETURN_TYPE_CHANGED
+                    "org.apache.ignite.compute.TableJobTarget#tableName()", // 
METHOD_RETURN_TYPE_CHANGED
+                    
"org.apache.ignite.lang.ColumnNotFoundException#ColumnNotFoundException(*, *, 
*)", // CONSTRUCTOR_REMOVED
+                    
"org.apache.ignite.lang.IndexAlreadyExistsException#IndexAlreadyExistsException(*,
 *)", // CONSTRUCTOR_REMOVED
+                    
"org.apache.ignite.lang.IndexNotFoundException#IndexNotFoundException(*, *)", 
// CONSTRUCTOR_REMOVED
+                    
"org.apache.ignite.lang.TableAlreadyExistsException#TableAlreadyExistsException(*,
 *)", // CONSTRUCTOR_REMOVED
+                    
"org.apache.ignite.lang.TableNotFoundException#TableNotFoundException(*, *)", 
// CONSTRUCTOR_REMOVED
+                    
"org.apache.ignite.lang.util.IgniteNameUtils#canonicalOrSimpleName(java.lang.String)",
 // METHOD_REMOVED
+                    
"org.apache.ignite.lang.util.IgniteNameUtils#parseSimpleName(java.lang.String)",
 // METHOD_REMOVED
+                    
"org.apache.ignite.lang.util.IgniteNameUtils#identifierExtend(int)", // 
METHOD_LESS_ACCESSIBLE
+                    
"org.apache.ignite.lang.util.IgniteNameUtils#identifierStart(int)", // 
METHOD_LESS_ACCESSIBLE
+                    
"org.apache.ignite.lang.util.IgniteNameUtils#quote(java.lang.String)", // 
METHOD_LESS_ACCESSIBLE
+                    "org.apache.ignite.sql.IgniteSql#executeBatch(*, *, *)", 
// METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.sql.IgniteSql#executeBatch(*, *)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.sql.IgniteSql#executeBatchAsync(*, *, 
*)", // METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.sql.IgniteSql#executeBatchAsync(*, *)", 
// METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.table.DataStreamerTarget#streamData(*, 
*, *, *, *, *, *)", // METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.table.IgniteTables#table(java.lang.String)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    
"org.apache.ignite.table.IgniteTables#tableAsync(java.lang.String)", // 
METHOD_ABSTRACT_NOW_DEFAULT
+                    "org.apache.ignite.table.QualifiedName", // CLASS_NOW_FINAL
+                    "org.apache.ignite.table.Table#name()" // 
METHOD_ABSTRACT_NOW_DEFAULT
+            )
+    );
+
+    @ParameterizedTest(name = "Old version: {0}, new version: {1}")
+    @MethodSource("versions")
+    void testApiModule(String oldVersion, String newVersion) {
+        CompatibilityChecker.builder()
+                .module("ignite-api")
+                .oldVersion(oldVersion)
+                .newVersion(newVersion)
+                .exclude(EXCLUDES_PER_VERSION.get(oldVersion))
+                .check();
+    }
+
+    private static List<Arguments> versions() {
+        List<Arguments> result = new ArrayList<>();
+
+        List<String> versions = new 
ArrayList<>(IgniteVersions.INSTANCE.versions().keySet());
+
+        if (INCLUDE_HISTORIC_DATA) {
+            for (int i = 1; i < versions.size(); i++) {
+                String oldVersion = versions.get(i - 1);
+                String newVersion = versions.get(i);
+                result.add(Arguments.of(oldVersion, newVersion));
+            }
+        }
+
+        // Current against latest released
+        String oldVersion = versions.get(versions.size() - 1);
+        String newVersion = IgniteProperties.get(IgniteProperties.VERSION);
+        result.add(Arguments.of(oldVersion, newVersion));
+
+        return result;
+    }
+}
diff --git 
a/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/OpenApiCompatibilityTest.java
 
b/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/OpenApiCompatibilityTest.java
index 6e9ef737920..deb3382d8ac 100644
--- 
a/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/OpenApiCompatibilityTest.java
+++ 
b/modules/compatibility-tests/src/test/java/org/apache/ignite/internal/OpenApiCompatibilityTest.java
@@ -25,8 +25,7 @@ import static org.hamcrest.Matchers.notNullValue;
 import io.swagger.v3.oas.models.OpenAPI;
 import io.swagger.v3.parser.OpenAPIV3Parser;
 import java.net.URL;
-import java.util.stream.Stream;
-import org.apache.ignite.internal.IgniteVersions.Version;
+import java.util.Set;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.MethodSource;
 
@@ -34,8 +33,8 @@ import org.junit.jupiter.params.provider.MethodSource;
  * Compares OpenAPI specs from previous versions with the current one.
  */
 class OpenApiCompatibilityTest {
-    private static Stream<String> versions() {
-        return 
IgniteVersions.INSTANCE.versions().stream().map(Version::version);
+    private static Set<String> versions() {
+        return IgniteVersions.INSTANCE.versions().keySet();
     }
 
     @ParameterizedTest
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
index df9c6480571..875c31205ef 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/CompatibilityTestBase.java
@@ -39,7 +39,6 @@ import java.util.stream.Collectors;
 import org.apache.ignite.Ignite;
 import org.apache.ignite.InitParametersBuilder;
 import org.apache.ignite.client.IgniteClient;
-import org.apache.ignite.internal.IgniteVersions.Version;
 import org.apache.ignite.internal.app.IgniteImpl;
 import org.apache.ignite.internal.distributionzones.rebalance.RebalanceUtil;
 import 
org.apache.ignite.internal.distributionzones.rebalance.ZoneRebalanceUtil;
@@ -238,8 +237,7 @@ public abstract class CompatibilityTestBase extends 
BaseIgniteAbstractTest {
      */
     public static List<String> baseVersions(String... skipVersions) {
         Set<String> skipSet = 
Arrays.stream(skipVersions).collect(Collectors.toSet());
-        return IgniteVersions.INSTANCE.versions().stream()
-                .map(Version::version)
+        return IgniteVersions.INSTANCE.versions().keySet().stream()
                 .filter(Predicate.not(skipSet::contains))
                 .collect(Collectors.toList());
     }
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteVersions.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteVersions.java
index 448ce7bb870..d85ffabfb0a 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteVersions.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/IgniteVersions.java
@@ -17,12 +17,15 @@
 
 package org.apache.ignite.internal;
 
+import static java.util.stream.Collectors.toMap;
+
 import com.fasterxml.jackson.annotation.JsonCreator;
 import com.fasterxml.jackson.annotation.JsonProperty;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Function;
 import org.apache.ignite.internal.logger.Loggers;
 
 /**
@@ -36,7 +39,7 @@ public class IgniteVersions {
     private List<String> artifacts;
     private Map<String, String> configOverrides;
     private Map<String, String> storageProfilesOverrides;
-    private List<Version> versions;
+    private Map<String, Version> versions;
 
     public IgniteVersions() {
     }
@@ -59,7 +62,7 @@ public class IgniteVersions {
         this.artifacts = artifacts;
         this.configOverrides = configOverrides;
         this.storageProfilesOverrides = storageProfilesOverrides;
-        this.versions = versions;
+        this.versions = versions.stream().collect(toMap(Version::version, 
Function.identity()));
     }
 
     public List<String> artifacts() {
@@ -74,10 +77,26 @@ public class IgniteVersions {
         return storageProfilesOverrides;
     }
 
-    public List<Version> versions() {
+    public Map<String, Version> versions() {
         return versions;
     }
 
+    /**
+     * Gets a value from a version data using specified accessor if the 
version exists, otherwise returns default value provided by the
+     * supplier using this instance.
+     *
+     * @param version Version to get data for.
+     * @param accessor Function to get the value from a version data.
+     * @param defaultSupplier Default value function.
+     * @param <T> Value type.
+     * @return Retrieved value.
+     */
+    public <T> T getOrDefault(String version, Function<Version, T> accessor, 
Function<IgniteVersions, T> defaultSupplier) {
+        Version versionData = versions.get(version);
+        T result = versionData != null ? accessor.apply(versionData) : null;
+        return result != null ? result : defaultSupplier.apply(this);
+    }
+
     /**
      * Represents a particular Ignite version with optional node config 
overrides.
      */
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
index bc1ab74e5a4..22e331f00f2 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/RunnerNode.java
@@ -29,7 +29,6 @@ import java.nio.file.Path;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Supplier;
-import java.util.stream.Collectors;
 import org.apache.ignite.internal.IgniteVersions.Version;
 import org.apache.ignite.internal.app.IgniteRunner;
 import org.apache.ignite.internal.logger.IgniteLogger;
@@ -39,11 +38,6 @@ import org.apache.ignite.internal.logger.Loggers;
  * Represents the Ignite node running in the external process.
  */
 public class RunnerNode {
-    private static final Map<String, String> DEFAULTS = 
IgniteVersions.INSTANCE.configOverrides();
-    private static final Map<String, String> STORAGE_PROFILES = 
IgniteVersions.INSTANCE.storageProfilesOverrides();
-    private static final Map<String, Map<String, String>> DEFAULTS_PER_VERSION 
= getTestDefaultsPerVersion();
-    private static final Map<String, Map<String, String>> 
STORAGE_PROFILES_PER_VERSION = getStorageProfilesPerVersion();
-
     private final Process process;
 
     private final String nodeName;
@@ -84,13 +78,11 @@ public class RunnerNode {
 
         boolean useTestDefaults = true;
         if (useTestDefaults) {
-            Map<String, String> defaultsPerVersion = 
DEFAULTS_PER_VERSION.get(igniteVersion);
-            Map<String, String> storageProfilesPerVersion = 
STORAGE_PROFILES_PER_VERSION.get(igniteVersion);
             writeConfigurationFileApplyingTestDefaults(
                     nodeConfig,
                     configPath,
-                    defaultsPerVersion != null ? defaultsPerVersion : DEFAULTS,
-                    storageProfilesPerVersion != null ? 
storageProfilesPerVersion : STORAGE_PROFILES
+                    getDefaults(igniteVersion),
+                    getStorageProfiles(igniteVersion)
             );
         } else {
             writeConfigurationFile(nodeConfig, configPath);
@@ -150,22 +142,12 @@ public class RunnerNode {
         return nodeName;
     }
 
-    private static Map<String, Map<String, String>> 
getTestDefaultsPerVersion() {
-        return IgniteVersions.INSTANCE.versions().stream()
-                .filter(version -> version.configOverrides() != null)
-                .collect(Collectors.toMap(
-                        Version::version,
-                        Version::configOverrides
-                ));
+    private static Map<String, String> getDefaults(String version) {
+        return IgniteVersions.INSTANCE.getOrDefault(version, 
Version::configOverrides, IgniteVersions::configOverrides);
     }
 
-    private static Map<String, Map<String, String>> 
getStorageProfilesPerVersion() {
-        return IgniteVersions.INSTANCE.versions().stream()
-                .filter(version -> version.storageProfilesOverrides() != null)
-                .collect(Collectors.toMap(
-                        Version::version,
-                        Version::storageProfilesOverrides
-                ));
+    private static Map<String, String> getStorageProfiles(String version) {
+        return IgniteVersions.INSTANCE.getOrDefault(version, 
Version::storageProfilesOverrides, IgniteVersions::storageProfilesOverrides);
     }
 
     @SuppressWarnings("UseOfProcessBuilder")
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityExtension.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityExtension.java
deleted file mode 100644
index c9c71c86000..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityExtension.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import static java.util.stream.Collectors.toList;
-import static 
org.apache.ignite.internal.compatibility.api.MethodProvider.looksLikeMethodName;
-import static 
org.junit.platform.commons.support.AnnotationSupport.findAnnotation;
-import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.stream.Stream;
-import org.apache.ignite.internal.IgniteVersions;
-import org.apache.ignite.internal.IgniteVersions.Version;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
-import org.junit.jupiter.api.extension.TestTemplateInvocationContextProvider;
-
-class ApiCompatibilityExtension implements 
TestTemplateInvocationContextProvider {
-    private static final String[] MODULES_ALL;
-
-    @Override
-    public boolean supportsTestTemplate(ExtensionContext context) {
-        return isAnnotated(context.getTestMethod(), 
ApiCompatibilityTest.class);
-    }
-
-    @Override
-    public Stream<TestTemplateInvocationContext> 
provideTestTemplateInvocationContexts(ExtensionContext context) {
-        ApiCompatibilityTest a = 
findAnnotation(context.getRequiredTestMethod(), 
ApiCompatibilityTest.class).orElseThrow();
-
-        List<String> oldVersions;
-        if (a.oldVersions().length == 0) {
-            oldVersions = 
IgniteVersions.INSTANCE.versions().stream().map(Version::version).distinct().collect(toList());
-        } else if (a.oldVersions().length == 1 && 
looksLikeMethodName(a.oldVersions()[0])) {
-            oldVersions = MethodProvider.provideArguments(context, 
a.oldVersions()[0]);
-        } else {
-            oldVersions = Arrays.asList(a.oldVersions());
-        }
-
-        // to reduce test time - need to resolve dependencies paths here once 
for all modules
-        String[] modules = a.modules().length == 0 ? MODULES_ALL : a.modules();
-
-        return Arrays.stream(modules)
-                .flatMap(module -> oldVersions.stream().map(v -> new 
CompatibilityInput(module, v, a)))
-                .map(input -> new ApiCompatibilityTestInvocationContext(input, 
new TestNameFormatter(context, input)));
-    }
-
-    static {
-        MODULES_ALL = new String[] { // could be resolved by gradle or moved 
to public annotation after stabilization
-                "ignite-api"
-        };
-    }
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTest.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTest.java
deleted file mode 100644
index 7d0cedfdb07..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTest.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-import org.junit.jupiter.api.TestTemplate;
-import org.junit.jupiter.api.extension.ExtendWith;
-
-/**
- * {@code @ApiCompatibilityTest} is used to signal that the annotated method 
is a API compatibility test between two versions of a module.
- *
- * <p>This annotation is somewhat similar to {@code @ParameterizedTest}, as in 
it also can run test multiple times per module.
- *
- * <p>Methods annotated with this annotation should not be annotated with 
{@code Test}.
- */
-@Target(ElementType.METHOD)
-@Retention(RetentionPolicy.RUNTIME)
-@TestTemplate
-@ExtendWith(ApiCompatibilityExtension.class)
-public @interface ApiCompatibilityTest {
-
-    /**
-     * Module old versions to check compatibility against.
-     *
-     * <p>If empty, uses {@code versions.json}.
-     *
-     * <p>If single value is given and it looks like a static factory method, 
this method is used to provide versions.
-     * Factory methods in external classes must be referenced by
-     * <em>fully qualified method name</em> &mdash; for example,
-     * {@code "com.example.StringsProviders#blankStrings"} or
-     * {@code "com.example.TopLevelClass$NestedClass#classMethod"} for a 
factory
-     * method in a static nested class.
-     */
-    String[] oldVersions() default {};
-
-    /**
-     * Module new version to check compatibility for. If empty, current 
version is used.
-     */
-    String newVersion() default "";
-
-    /**
-     * List of modules to check. If empty, all modules are checked.
-     */
-    String[] modules() default { "ignite-api" };
-
-    /**
-     * Semicolon separated list of elements to exclude in the form
-     * {@code package.Class#classMember}, * can be used as wildcard. 
Annotations
-     * are given as FQN starting with @.
-     * <br>Examples:<br>
-     * {@code 
mypackage;my.Class;other.Class#method(int,long);foo.Class#field;@my.Annotation}.
-     */
-    String exclude() default "";
-
-    /**
-     * Exit with an error if any incompatibility is detected.
-     */
-    boolean errorOnIncompatibility() default true;
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTestInvocationContext.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTestInvocationContext.java
deleted file mode 100644
index f86426a40aa..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/ApiCompatibilityTestInvocationContext.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import java.util.List;
-import org.junit.jupiter.api.extension.Extension;
-import org.junit.jupiter.api.extension.TestTemplateInvocationContext;
-
-class ApiCompatibilityTestInvocationContext implements 
TestTemplateInvocationContext {
-
-    private final CompatibilityInput input;
-    private final TestNameFormatter formatter;
-
-    ApiCompatibilityTestInvocationContext(CompatibilityInput input, 
TestNameFormatter formatter) {
-        this.input = input;
-        this.formatter = formatter;
-    }
-
-    @Override
-    public String getDisplayName(int invocationIndex) {
-        return formatter.format(invocationIndex);
-    }
-
-    @Override
-    public List<Extension> getAdditionalExtensions() {
-        return List.of(new CompatibilityExtension(input));
-    }
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityChecker.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityChecker.java
index f3c4135b0ae..e8b0e3a285b 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityChecker.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityChecker.java
@@ -42,14 +42,28 @@ import java.util.List;
 import org.apache.ignite.internal.Dependencies;
 import org.apache.ignite.internal.util.ArrayUtils;
 
-class CompatibilityChecker {
+/**
+ * Wrapper for the API comparator.
+ */
+public class CompatibilityChecker {
 
     /**
-     * Runs japicmp with given input and returns the output.
+     * Constructs new builder for checker parameters.
+     *
+     * @return New builder.
+     */
+    public static CompatibilityInput.Builder builder() {
+        return new CompatibilityInput.Builder();
+    }
+
+    /**
+     * Checks API compatibility for given input parameters.
      *
      * @see <a href="https://siom79.github.io/japicmp/CliTool.html";>japicmp 
options</a>
+     *
+     * @param input Input parameters.
      */
-    static CompatibilityOutput check(CompatibilityInput input) {
+    public static void check(CompatibilityInput input) {
         String[] args = {
                 "--old", Dependencies.path(input.oldVersionNotation(), false, 
false),
                 "--new", Dependencies.path(input.newVersionNotation(), false, 
input.currentVersion()),
@@ -70,12 +84,7 @@ class CompatibilityChecker {
         Options options = new CliParser().parse(args);
         JarArchiveComparator jarArchiveComparator = new 
JarArchiveComparator(JarArchiveComparatorOptions.of(options));
         List<JApiClass> javaApiClasses = 
jarArchiveComparator.compare(options.getOldArchives(), 
options.getNewArchives());
-        return new CompatibilityOutput(options, javaApiClasses, 
jarArchiveComparator);
-    }
-
-    static void generateOutput(CompatibilityOutput output) {
-        generateOutput(output.options(), output.javaApiClasses(), 
output.jarArchiveComparator());
-        // use custom output generator to throw exceptions and list of 
incompatibilities
+        generateOutput(options, javaApiClasses, jarArchiveComparator);
     }
 
     /**
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityExtension.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityExtension.java
deleted file mode 100644
index 84fa9b28c49..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityExtension.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import org.junit.jupiter.api.extension.BeforeEachCallback;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
-import org.junit.jupiter.api.extension.ParameterContext;
-import org.junit.jupiter.api.extension.ParameterResolutionException;
-import org.junit.jupiter.api.extension.ParameterResolver;
-
-class CompatibilityExtension implements BeforeEachCallback, ParameterResolver {
-    private static final Namespace NAMESPACE = 
Namespace.create(CompatibilityExtension.class);
-    private static final String OUTPUT_KEY = "compatibilityOutput";
-    private final CompatibilityInput input;
-
-    CompatibilityExtension(CompatibilityInput input) {
-        this.input = input;
-    }
-
-    @Override
-    public void beforeEach(ExtensionContext context) {
-        CompatibilityOutput c = CompatibilityChecker.check(input);
-        context.getStore(NAMESPACE).put(OUTPUT_KEY, c);
-        CompatibilityChecker.generateOutput(c);
-    }
-
-    @Override
-    public boolean supportsParameter(ParameterContext parameterContext, 
ExtensionContext context)
-            throws ParameterResolutionException {
-        Class<?> type = parameterContext.getParameter().getType();
-        return type == CompatibilityOutput.class;
-    }
-
-    @Override
-    public Object resolveParameter(ParameterContext parameterContext, 
ExtensionContext context)
-            throws ParameterResolutionException {
-        return context.getStore(NAMESPACE).get(OUTPUT_KEY, 
CompatibilityOutput.class);
-    }
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityInput.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityInput.java
index d2c8f7de1b0..22b3b2e5688 100644
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityInput.java
+++ 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityInput.java
@@ -17,11 +17,13 @@
 
 package org.apache.ignite.internal.compatibility.api;
 
-import static org.apache.ignite.internal.util.StringUtils.nullOrEmpty;
-
+import java.util.List;
 import org.apache.ignite.internal.properties.IgniteProperties;
 
-class CompatibilityInput {
+/**
+ * Parameters for checking API compatibility.
+ */
+public class CompatibilityInput {
     private final String module;
     private final String oldVersion;
     private final String newVersion;
@@ -29,27 +31,26 @@ class CompatibilityInput {
     private final boolean errorOnIncompatibility;
     private final boolean currentVersion;
 
-    CompatibilityInput(String module, String oldVersion, ApiCompatibilityTest 
annotation) {
+    private CompatibilityInput(
+            String module,
+            String oldVersion,
+            String newVersion,
+            String exclude,
+            boolean errorOnIncompatibility,
+            boolean currentVersion
+    ) {
         this.module = module;
         this.oldVersion = oldVersion;
-        currentVersion = nullOrEmpty(annotation.newVersion());
-        this.newVersion = currentVersion ? 
IgniteProperties.get(IgniteProperties.VERSION) : annotation.newVersion();
-        this.exclude = annotation.exclude();
-        this.errorOnIncompatibility = annotation.errorOnIncompatibility();
+        this.newVersion = newVersion;
+        this.exclude = exclude;
+        this.errorOnIncompatibility = errorOnIncompatibility;
+        this.currentVersion = currentVersion;
     }
 
     String module() {
         return module;
     }
 
-    String oldVersion() {
-        return oldVersion;
-    }
-
-    String newVersion() {
-        return newVersion;
-    }
-
     String oldVersionNotation() {
         return "org.apache.ignite:" + module + ":" + oldVersion;
     }
@@ -69,4 +70,56 @@ class CompatibilityInput {
     boolean currentVersion() {
         return currentVersion;
     }
+
+    /**
+     * Builder class for input parameters.
+     */
+    public static class Builder {
+        private String module;
+        private String oldVersion;
+        private String newVersion;
+        private List<String> excludes;
+        private boolean errorOnIncompatibility = true;
+
+        public Builder module(String module) {
+            this.module = module;
+            return this;
+        }
+
+        public Builder oldVersion(String oldVersion) {
+            this.oldVersion = oldVersion;
+            return this;
+        }
+
+        public Builder newVersion(String newVersion) {
+            this.newVersion = newVersion;
+            return this;
+        }
+
+        public Builder exclude(List<String> excludes) {
+            this.excludes = excludes;
+            return this;
+        }
+
+        public Builder errorOnIncompatibility(boolean errorOnIncompatibility) {
+            this.errorOnIncompatibility = errorOnIncompatibility;
+            return this;
+        }
+
+        /**
+         * Constructs input parameters object.
+         *
+         * @return Input parameters object.
+         */
+        public CompatibilityInput build() {
+            boolean isCurrentVersion = 
IgniteProperties.get(IgniteProperties.VERSION).equals(newVersion);
+            String exclude = excludes != null ? String.join(";", excludes) : 
"";
+
+            return new CompatibilityInput(module, oldVersion, newVersion, 
exclude, errorOnIncompatibility, isCurrentVersion);
+        }
+
+        public void check() {
+            CompatibilityChecker.check(build());
+        }
+    }
 }
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityOutput.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityOutput.java
deleted file mode 100644
index f29419acab4..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/CompatibilityOutput.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import japicmp.cmp.JarArchiveComparator;
-import japicmp.config.Options;
-import japicmp.model.JApiClass;
-import java.util.List;
-
-/**
- * Japicmp comparison output. Can be handy for custom {@link 
japicmp.output.OutputGenerator}
- */
-public class CompatibilityOutput {
-    private final Options options;
-    private final List<JApiClass> javaApiClasses;
-    private final JarArchiveComparator jarArchiveComparator;
-
-    CompatibilityOutput(Options options, List<JApiClass> javaApiClasses, 
JarArchiveComparator jarArchiveComparator) {
-        this.options = options;
-        this.javaApiClasses = javaApiClasses;
-        this.jarArchiveComparator = jarArchiveComparator;
-    }
-
-    Options options() {
-        return options;
-    }
-
-    List<JApiClass> javaApiClasses() {
-        return javaApiClasses;
-    }
-
-    JarArchiveComparator jarArchiveComparator() {
-        return jarArchiveComparator;
-    }
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/MethodProvider.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/MethodProvider.java
deleted file mode 100644
index 77bd367fd4d..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/MethodProvider.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import static java.lang.String.format;
-import static 
org.junit.platform.commons.util.CollectionUtils.isConvertibleToStream;
-
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-import org.jetbrains.annotations.Nullable;
-import org.junit.jupiter.api.extension.ExtensionContext;
-import org.junit.platform.commons.util.ClassLoaderUtils;
-import org.junit.platform.commons.util.CollectionUtils;
-import org.junit.platform.commons.util.Preconditions;
-import org.junit.platform.commons.util.ReflectionUtils;
-
-/**
- * Simplified version of {@link 
org.junit.jupiter.params.provider.MethodArgumentsProvider}.
- */
-class MethodProvider {
-    private static final Predicate<Method> isFactoryMethod =
-            method -> isConvertibleToStream(method.getReturnType());
-
-    static <T> List<T> provideArguments(ExtensionContext context, String 
methodName) {
-        Class<?> testClass = context.getRequiredTestClass();
-        Optional<Method> testMethod = context.getTestMethod();
-        Object testInstance = context.getTestInstance().orElse(null);
-
-        return Stream.of(methodName)
-                .map(factoryMethodName -> findFactoryMethod(testClass, 
testMethod, factoryMethodName))
-                .map(factoryMethod -> 
context.getExecutableInvoker().invoke(factoryMethod, testInstance))
-                .flatMap(CollectionUtils::toStream)
-                .map(o -> (T) o)
-                .collect(Collectors.toList());
-    }
-
-    static boolean looksLikeMethodName(String factoryMethodName) {
-        if (factoryMethodName.contains("#")) {
-            return true;
-        }
-        if (factoryMethodName.matches("^[a-zA-Z0-9_]*$")) {
-            return true;
-        }
-        return false;
-    }
-
-    private static Method findFactoryMethod(Class<?> testClass, 
Optional<Method> testMethod, String factoryMethodName) {
-        String originalFactoryMethodName = factoryMethodName;
-
-        // Convert local factory method name to fully qualified method name.
-        if (looksLikeMethodName(factoryMethodName) && 
!factoryMethodName.contains("#")) {
-            factoryMethodName = testClass.getName() + "#" + factoryMethodName;
-        }
-
-        Method factoryMethod = 
findFactoryMethodByFullyQualifiedName(testClass, testMethod, factoryMethodName);
-
-        Preconditions.condition(isFactoryMethod.test(factoryMethod), () -> 
format(
-                "Could not find valid factory method [%s] for test class [%s] 
but found the following invalid candidate: %s",
-                originalFactoryMethodName, testClass.getName(), 
factoryMethod));
-
-        return factoryMethod;
-    }
-
-    private static @Nullable Method findFactoryMethodByFullyQualifiedName(
-            Class<?> testClass,
-            Optional<Method> testMethod,
-            String fullyQualifiedMethodName
-    ) {
-        String[] methodParts = 
ReflectionUtils.parseFullyQualifiedMethodName(fullyQualifiedMethodName);
-        String className = methodParts[0];
-        String methodName = methodParts[1];
-        String methodParameters = methodParts[2];
-        ClassLoader classLoader = ClassLoaderUtils.getClassLoader(testClass);
-        Class<?> clazz = ReflectionUtils.loadRequiredClass(className, 
classLoader);
-
-        // Attempt to find an exact match first.
-        Method factoryMethod = ReflectionUtils.findMethod(clazz, methodName, 
methodParameters).orElse(null);
-
-        if (factoryMethod != null) {
-            return factoryMethod;
-        }
-
-        return null;
-    }
-}
diff --git 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/TestNameFormatter.java
 
b/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/TestNameFormatter.java
deleted file mode 100644
index 4d9c9a63679..00000000000
--- 
a/modules/compatibility-tests/src/testFixtures/java/org/apache/ignite/internal/compatibility/api/TestNameFormatter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.ignite.internal.compatibility.api;
-
-import org.junit.jupiter.api.extension.ExtensionContext;
-
-class TestNameFormatter {
-    private static final String PATTERN = "[{module}] of '{newVersion}' with 
previous '{oldVersion}'";
-    private final String displayName;
-    private final CompatibilityInput input;
-
-    TestNameFormatter(ExtensionContext extensionContext, CompatibilityInput 
input) {
-        this.displayName = extensionContext.getDisplayName();
-        this.input = input;
-    }
-
-    String format(int invocationIndex) {
-        return PATTERN
-                .replace("{displayName}", displayName)
-                .replace("{index}", String.valueOf(invocationIndex))
-                .replace("{module}", input.module())
-                .replace("{oldVersion}", input.oldVersion())
-                .replace("{newVersion}", input.newVersion())
-                ;
-    }
-}

Reply via email to