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

tkalkirill 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 745f0456c6a IGNITE-26110 Add unit tests for serialization 
compatibility of all Raft commands (#6744)
745f0456c6a is described below

commit 745f0456c6afb5fce32ca10e16807b03535d3425
Author: Kirill Tkalenko <tkalkir...@yandex.ru>
AuthorDate: Fri Oct 10 14:54:04 2025 +0300

    IGNITE-26110 Add unit tests for serialization compatibility of all Raft 
commands (#6744)
---
 modules/replicator/build.gradle                    |   1 +
 .../ReplicatorCommandsCompatibilityTest.java       | 126 +++++++++++++++++++++
 modules/transactions/build.gradle                  |   1 +
 .../tx/message/TxCommandsCompatibilityTest.java    | 100 ++++++++++++++++
 4 files changed, 228 insertions(+)

diff --git a/modules/replicator/build.gradle b/modules/replicator/build.gradle
index c54ad928f5c..1ab45a03a14 100644
--- a/modules/replicator/build.gradle
+++ b/modules/replicator/build.gradle
@@ -57,6 +57,7 @@ dependencies {
     integrationTestImplementation testFixtures(project(':ignite-metrics'))
     integrationTestImplementation testFixtures(project(':ignite-raft'))
 
+    testImplementation project(':ignite-network')
     testImplementation testFixtures(project(':ignite-core'))
     testImplementation testFixtures(project(':ignite-placement-driver-api'))
     testImplementation testFixtures(project(':ignite-failure-handler'))
diff --git 
a/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java
 
b/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java
new file mode 100644
index 00000000000..290180b1fef
--- /dev/null
+++ 
b/modules/replicator/src/test/java/org/apache/ignite/internal/replicator/ReplicatorCommandsCompatibilityTest.java
@@ -0,0 +1,126 @@
+/*
+ * 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.replicator;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Base64;
+import java.util.List;
+import java.util.UUID;
+import org.apache.ignite.internal.hlc.HybridTimestamp;
+import org.apache.ignite.internal.network.MessageSerializationRegistryImpl;
+import 
org.apache.ignite.internal.network.serialization.MessageSerializationRegistry;
+import org.apache.ignite.internal.raft.Command;
+import org.apache.ignite.internal.raft.Marshaller;
+import org.apache.ignite.internal.raft.util.ThreadLocalOptimizedMarshaller;
+import org.apache.ignite.internal.replicator.command.SafeTimeSyncCommand;
+import 
org.apache.ignite.internal.replicator.message.PrimaryReplicaChangeCommand;
+import org.apache.ignite.internal.replicator.message.ReplicaMessagesFactory;
+import 
org.apache.ignite.internal.replicator.message.ReplicaMessagesSerializationRegistryInitializer;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Compatibility testing for serialization/deserialization of replicator raft 
commands. It is verified that deserialization of commands that
+ * were created on earlier versions of the product will be error-free.
+ *
+ * <p>For MAC users with aarch64 architecture, you will need to add {@code || 
"aarch64".equals(arch)} to the
+ * {@code GridUnsafe#unaligned()} for the tests to pass. For more details, see
+ * <a 
href="https://lists.apache.org/thread/67coyvm8mo7106mkndt24yqwtbvb7590";>discussion</a>.</p>
+ *
+ * <p>To serialize commands, use {@link #serializeAll()} and insert the result 
into the appropriate tests.</p>
+ */
+public class ReplicatorCommandsCompatibilityTest extends 
BaseIgniteAbstractTest {
+    private final MessageSerializationRegistry registry = new 
MessageSerializationRegistryImpl();
+
+    private final Marshaller marshaller = new 
ThreadLocalOptimizedMarshaller(registry);
+
+    private final ReplicaMessagesFactory factory = new 
ReplicaMessagesFactory();
+
+    @BeforeEach
+    void setUp() {
+        new 
ReplicaMessagesSerializationRegistryInitializer().registerFactories(registry);
+    }
+
+    @Test
+    void testSafeTimeSyncCommand() {
+        SafeTimeSyncCommand command = decodeCommand("CSlH");
+
+        assertEquals(initiatorTime(), command.initiatorTime());
+    }
+
+    @Test
+    void testPrimaryReplicaChangeCommand() {
+        PrimaryReplicaChangeCommand command = 
decodeCommand("CSorAAAAAAAAAAAqAAAAAAAAAEUGbm9kZTE=");
+
+        assertEquals(42, command.leaseStartTime());
+        assertEquals(uuid(), command.primaryReplicaNodeId());
+        assertEquals("node1", command.primaryReplicaNodeName());
+    }
+
+    private static HybridTimestamp initiatorTime() {
+        return HybridTimestamp.hybridTimestamp(70);
+    }
+
+    private static UUID uuid() {
+        return new UUID(42, 69);
+    }
+
+    private <T extends Command> T deserializeCommand(byte[] bytes) {
+        return marshaller.unmarshall(bytes);
+    }
+
+    private <T extends Command> T decodeCommand(String base64) {
+        return deserializeCommand(Base64.getDecoder().decode(base64));
+    }
+
+    @SuppressWarnings("unused")
+    private void serializeAll() {
+        List<Command> commands = List.of(
+                createSafeTimeSyncCommand(),
+                createPrimaryReplicaChangeCommand()
+        );
+
+        for (Command c : commands) {
+            log.info(">>>>> Serialized command: [c={}, base64='{}']", 
c.getClass().getSimpleName(), encodeCommand(c));
+        }
+    }
+
+    private PrimaryReplicaChangeCommand createPrimaryReplicaChangeCommand() {
+        return factory.primaryReplicaChangeCommand()
+                .leaseStartTime(42)
+                .primaryReplicaNodeId(uuid())
+                .primaryReplicaNodeName("node1")
+                .build();
+    }
+
+    private SafeTimeSyncCommand createSafeTimeSyncCommand() {
+        return factory.safeTimeSyncCommand()
+                .initiatorTime(initiatorTime())
+                .build();
+    }
+
+    private byte[] serializeCommand(Command c) {
+        return marshaller.marshall(c);
+    }
+
+    private String encodeCommand(Command c) {
+        return Base64.getEncoder().encodeToString(serializeCommand(c));
+    }
+}
diff --git a/modules/transactions/build.gradle 
b/modules/transactions/build.gradle
index 76189930ad0..416d2640ff0 100644
--- a/modules/transactions/build.gradle
+++ b/modules/transactions/build.gradle
@@ -51,6 +51,7 @@ dependencies {
     implementation libs.fastutil.core
 
     testImplementation project(':ignite-core')
+    testImplementation project(':ignite-raft')
     testImplementation testFixtures(project(':ignite-core'))
     testImplementation testFixtures(project(':ignite-configuration'))
     testImplementation testFixtures(project(':ignite-configuration-system'))
diff --git 
a/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java
 
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java
new file mode 100644
index 00000000000..f5902a7655f
--- /dev/null
+++ 
b/modules/transactions/src/test/java/org/apache/ignite/internal/tx/message/TxCommandsCompatibilityTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.tx.message;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Base64;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.ignite.internal.network.MessageSerializationRegistryImpl;
+import 
org.apache.ignite.internal.network.serialization.MessageSerializationRegistry;
+import org.apache.ignite.internal.raft.Command;
+import org.apache.ignite.internal.raft.Marshaller;
+import org.apache.ignite.internal.raft.util.ThreadLocalOptimizedMarshaller;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Compatibility testing for serialization/deserialization of tx raft 
commands. It is verified that deserialization of commands that were
+ * created on earlier versions of the product will be error-free.
+ *
+ * <p>For MAC users with aarch64 architecture, you will need to add {@code || 
"aarch64".equals(arch)} to the
+ * {@code GridUnsafe#unaligned()} for the tests to pass. For more details, see
+ * <a 
href="https://lists.apache.org/thread/67coyvm8mo7106mkndt24yqwtbvb7590";>discussion</a>.</p>
+ *
+ * <p>To serialize commands, use {@link #serializeAll()} and insert the result 
into the appropriate tests.</p>
+ */
+public class TxCommandsCompatibilityTest extends BaseIgniteAbstractTest {
+    private final MessageSerializationRegistry registry = new 
MessageSerializationRegistryImpl();
+
+    private final Marshaller marshaller = new 
ThreadLocalOptimizedMarshaller(registry);
+
+    private final TxMessagesFactory factory = new TxMessagesFactory();
+
+    @BeforeEach
+    void setUp() {
+        new 
TxMessagesSerializationRegistryInitializer().registerFactories(registry);
+    }
+
+    @Test
+    void testVacuumTxStatesCommand() {
+        VacuumTxStatesCommand command = 
decodeCommand("Bg4CAAAAAAAAAAAqAAAAAAAAAEU=");
+
+        assertEquals(Set.of(uuid()), command.txIds());
+    }
+
+    private static UUID uuid() {
+        return new UUID(42, 69);
+    }
+
+    private <T extends Command> T deserializeCommand(byte[] bytes) {
+        return marshaller.unmarshall(bytes);
+    }
+
+    private <T extends Command> T decodeCommand(String base64) {
+        return deserializeCommand(Base64.getDecoder().decode(base64));
+    }
+
+    @SuppressWarnings("unused")
+    private void serializeAll() {
+        List<Command> commands = List.of(
+                createVacuumTxStatesCommand()
+        );
+
+        for (Command c : commands) {
+            log.info(">>>>> Serialized command: [c={}, base64='{}']", 
c.getClass().getSimpleName(), encodeCommand(c));
+        }
+    }
+
+    private VacuumTxStatesCommand createVacuumTxStatesCommand() {
+        return factory.vacuumTxStatesCommand()
+                .txIds(Set.of(uuid()))
+                .build();
+    }
+
+    private byte[] serializeCommand(Command c) {
+        return marshaller.marshall(c);
+    }
+
+    private String encodeCommand(Command c) {
+        return Base64.getEncoder().encodeToString(serializeCommand(c));
+    }
+}

Reply via email to