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> — 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())
- ;
- }
-}