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

isapego 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 5e6a5e8b2b2 IGNITE-26843 C++ Client: Add compatibility test suite 
(#6856)
5e6a5e8b2b2 is described below

commit 5e6a5e8b2b23cb03cc046fc64d017d9abbd92d52
Author: Ed Rakhmankulov <[email protected]>
AuthorDate: Tue Oct 28 17:05:36 2025 +0300

    IGNITE-26843 C++ Client: Add compatibility test suite (#6856)
---
 modules/platforms/cpp/CMakeLists.txt               |   7 +-
 modules/platforms/cpp/DEVNOTES.md                  |   8 +
 .../cpp/tests/compatibility-tests/CMakeLists.txt   |  28 +++
 .../cpp/tests/compatibility-tests/basic_test.cpp   |  44 ++++
 .../ignite_xml_unit_test_result_printer.cpp        | 127 +++++++++++
 .../ignite_xml_unit_test_result_printer.h          |  57 +++++
 .../cpp/tests/compatibility-tests/main.cpp         | 236 +++++++++++++++++++++
 .../cpp/tests/test-common/detail/unix_process.h    |   1 +
 .../cpp/tests/test-common/ignite_runner.cpp        |  82 ++++++-
 .../cpp/tests/test-common/ignite_runner.h          |  16 ++
 .../platforms/cpp/tests/test-common/process.cpp    |   2 +-
 .../platforms/cpp/tests/test-common/test_utils.cpp |  32 +++
 .../platforms/cpp/tests/test-common/test_utils.h   |   8 +
 13 files changed, 638 insertions(+), 10 deletions(-)

diff --git a/modules/platforms/cpp/CMakeLists.txt 
b/modules/platforms/cpp/CMakeLists.txt
index 4fe5f899604..015c8e4c64b 100644
--- a/modules/platforms/cpp/CMakeLists.txt
+++ b/modules/platforms/cpp/CMakeLists.txt
@@ -35,6 +35,7 @@ option(ENABLE_CLIENT "Build Ignite.C++ Client module" ON)
 option(ENABLE_ODBC "Build Ignite ODBC driver module" OFF)
 option(ENABLE_PROTOCOL "Build Ignite Protocol library" ON)
 option(ENABLE_TESTS "Build Ignite.C++ tests" OFF)
+option(ENABLE_COMPATIBILITY_TESTS "Build Ignite.C++ compatibility tests" OFF)
 option(ENABLE_ADDRESS_SANITIZER "If address sanitizer is enabled" OFF)
 option(ENABLE_UB_SANITIZER "If undefined behavior sanitizer is enabled" OFF)
 option(WARNINGS_AS_ERRORS "Treat warning as errors" OFF)
@@ -144,7 +145,7 @@ endif()
 
 # Add integration tests.
 if (${ENABLE_TESTS})
-    if (${ENABLE_CLIENT} OR ${ENABLE_ODBC})
+    if (${ENABLE_CLIENT} OR ${ENABLE_ODBC} OR ${ENABLE_COMPATIBILITY_TESTS})
         add_subdirectory(tests/test-common)
     endif()
 
@@ -155,6 +156,10 @@ if (${ENABLE_TESTS})
     if (${ENABLE_ODBC})
         add_subdirectory(tests/odbc-test)
     endif()
+
+    if (${ENABLE_COMPATIBILITY_TESTS})
+        add_subdirectory(tests/compatibility-tests)
+    endif()
 endif()
 
 # Source code formatting with clang-format.
diff --git a/modules/platforms/cpp/DEVNOTES.md 
b/modules/platforms/cpp/DEVNOTES.md
index 1757984f981..24edc6e639c 100644
--- a/modules/platforms/cpp/DEVNOTES.md
+++ b/modules/platforms/cpp/DEVNOTES.md
@@ -186,3 +186,11 @@ To run a specific test:
 
 To debug or profile the Java side of the tests, run the 
`org.apache.ignite.internal.runner.app.PlatformTestNodeRunner`
 class in IDEA with a debugger or profiler, then run C++ tests as usual, 
optionally with debugger.
+
+
+### Running compatibility Tests
+
+To enable compatibility test pass `-DENABLE_COMPATIBILITY_TESTS=ON` to cmake 
build.
+If you are debugging then it is recommended to run one version at once by 
providing environment variable e.g 
`IGNITE_CPP_TESTS_COMPATIBILITY_VERSIONS_OVERRIDE=3.0.0`
+To start tests run in modules/platforms/cpp/tests/compatibility-tests
+`./cmake-build-debug/bin/ignite-client-compatibility-test`
\ No newline at end of file
diff --git a/modules/platforms/cpp/tests/compatibility-tests/CMakeLists.txt 
b/modules/platforms/cpp/tests/compatibility-tests/CMakeLists.txt
new file mode 100644
index 00000000000..4fe0aeed1ef
--- /dev/null
+++ b/modules/platforms/cpp/tests/compatibility-tests/CMakeLists.txt
@@ -0,0 +1,28 @@
+#
+# 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.
+#
+
+project(ignite-client-compatibility-test)
+
+set(TARGET ${PROJECT_NAME})
+
+set(SOURCES
+    main.cpp
+    ignite_xml_unit_test_result_printer.cpp
+    basic_test.cpp
+)
+
+ignite_test(${TARGET} SOURCES ${SOURCES} LIBS ignite-test-common 
ignite3-client)
diff --git a/modules/platforms/cpp/tests/compatibility-tests/basic_test.cpp 
b/modules/platforms/cpp/tests/compatibility-tests/basic_test.cpp
new file mode 100644
index 00000000000..670880c0610
--- /dev/null
+++ b/modules/platforms/cpp/tests/compatibility-tests/basic_test.cpp
@@ -0,0 +1,44 @@
+// 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.
+//
+
+#include "tests/client-test/ignite_runner_suite.h"
+
+using namespace ignite;
+
+class basic_test_ign_version : public ignite::ignite_runner_suite {
+private:
+    static ignite_client ConnectToCluster() {
+        ignite_client_configuration cfg{get_node_addrs()};
+        cfg.set_logger(get_logger());
+        return ignite_client::start(cfg, std::chrono::seconds(30));
+    }
+
+protected:
+    void SetUp() override {
+        m_client = ConnectToCluster();
+
+        std::cout << "CompatibilityServer version" << 
ignite_runner::COMPATIBILITY_VERSION << "\n";
+    }
+
+    ignite_client m_client;
+};
+
+
+TEST_F(basic_test_ign_version, get_cluster_nodes_successful) {
+    auto cluster_nodes = m_client.get_cluster_nodes();
+
+    ASSERT_GE(cluster_nodes.size(), 1);
+}
\ No newline at end of file
diff --git 
a/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.cpp
 
b/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.cpp
new file mode 100644
index 00000000000..53c5539cb6e
--- /dev/null
+++ 
b/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.cpp
@@ -0,0 +1,127 @@
+// 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.
+//
+
+#include "ignite_xml_unit_test_result_printer.h"
+
+namespace ignite::detail {
+
+ignite_xml_unit_test_result_printer::ignite_xml_unit_test_result_printer(
+    testing::TestEventListener *delegate, std::string version)
+        : m_delegate(delegate)
+        , m_version(std::move(version)){}
+
+void ignite_xml_unit_test_result_printer::OnTestProgramStart(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnTestProgramStart(unit_test);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestIterationStart(const 
::testing::UnitTest &unit_test, int iteration) {
+    m_delegate->OnTestIterationStart(unit_test, iteration);
+}
+
+void ignite_xml_unit_test_result_printer::OnEnvironmentsSetUpStart(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnEnvironmentsSetUpStart(unit_test);
+}
+
+void ignite_xml_unit_test_result_printer::OnEnvironmentsSetUpEnd(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnEnvironmentsSetUpEnd(unit_test);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestSuiteStart(const 
::testing::TestSuite &test_suite) {
+    m_delegate->OnTestSuiteStart(test_suite);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestSuiteEnd(const 
::testing::TestSuite &test_suite) {
+    m_delegate->OnTestSuiteEnd(test_suite);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestCaseStart(const 
::testing::TestCase &test_case) {
+    m_delegate->OnTestCaseStart(test_case);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestCaseEnd(const 
::testing::TestCase &test_case) {
+    m_delegate->OnTestCaseEnd(test_case);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestStart(const 
::testing::TestInfo &test_info) {
+    m_delegate->OnTestStart(test_info);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestDisabled(const 
testing::TestInfo &test_info) {
+    m_delegate->OnTestDisabled(test_info);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestPartResult(const 
::testing::TestPartResult &test_part_result) {
+    m_delegate->OnTestPartResult(test_part_result);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestEnd(const ::testing::TestInfo 
&test_info) {
+    m_delegate->OnTestEnd(test_info);
+}
+
+void ignite_xml_unit_test_result_printer::OnEnvironmentsTearDownStart(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnEnvironmentsTearDownStart(unit_test);
+}
+
+void ignite_xml_unit_test_result_printer::OnEnvironmentsTearDownEnd(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnEnvironmentsTearDownEnd(unit_test);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestIterationEnd(const 
::testing::UnitTest &unit_test, int iteration) {
+    for (int i = 0; i < unit_test.total_test_case_count(); ++i) {
+        // We are extracting test suite info to add version info
+        const testing::TestSuite *ts = unit_test.GetTestSuite(i);
+
+        // because underlying storage is std::string we able to override it 
content without changing length.
+        char *s = const_cast<char *>(ts->name());
+
+        std::string_view sw = s;
+
+        std::string_view suffix = "_ign_version";
+
+        if (sw.rfind(suffix) != sw.size() - suffix.size()) {
+            std::stringstream ss;
+            ss << "Expected test suite name to have suffix '"<< suffix <<"' 
but got [name = "<< sw << "]";
+            throw std::runtime_error(ss.str());
+        }
+
+        // it is possible to have more complex version text like 9.1.18-p3, 
etc.
+        if (m_version.size() >= suffix.size()) {
+            std::stringstream ss;
+            ss << "Expected version string to be shorter than a suffix but got 
"
+               << "[version = " << m_version << "; suf  fix = "<< suffix <<"]";
+            throw std::runtime_error(ss.str());
+        }
+
+        auto s_it = s + (sw.size() - suffix.size() + 1 /*skip leading '_'*/);
+        auto s_end = s + sw.size();
+        for (auto it = m_version.begin(); it != m_version.end(); ++it, ++s_it) 
{
+            char c = *it;
+            c = c == '.' ? '_' : c;// Teamcity treats '.' specifically.
+            *s_it = c;
+        }
+
+        while (s_it != s_end) {
+            *s_it = '_';
+            ++s_it;
+        }
+    }
+    m_delegate->OnTestIterationEnd(unit_test, iteration);
+}
+
+void ignite_xml_unit_test_result_printer::OnTestProgramEnd(const 
::testing::UnitTest &unit_test) {
+    m_delegate->OnTestProgramEnd(unit_test);
+}
+} // namespace ignite::detail
\ No newline at end of file
diff --git 
a/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.h
 
b/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.h
new file mode 100644
index 00000000000..8d6d68d85a4
--- /dev/null
+++ 
b/modules/platforms/cpp/tests/compatibility-tests/ignite_xml_unit_test_result_printer.h
@@ -0,0 +1,57 @@
+// 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.
+//
+
+#pragma once
+
+#include <gtest/gtest.h>
+
+namespace ignite::detail {
+/**
+ * Wrapper for default GTest event listener responsible for xml report.
+ * We override test suite name to include version information in order to 
distinguish results on TeamCity output.
+ * GTest do not provide any API to manipulate test information so some hacks 
were introduced
+ * at ignite::detail::ignite_xml_unit_test_result_printer::OnTestIterationEnd
+ * Name requirement for test suite is enforced: test suite should end with 
'_ign_version' which would be replaced
+ * with actual version in xml report.
+ */
+class ignite_xml_unit_test_result_printer : public 
::testing::EmptyTestEventListener {
+    TestEventListener *m_delegate;
+    std::string m_version;
+public:
+    ignite_xml_unit_test_result_printer(::testing::TestEventListener 
*delegate, std::string version);
+
+    ~ignite_xml_unit_test_result_printer() override {
+        delete m_delegate;
+    }
+
+    void OnTestProgramStart(const testing::UnitTest &) override;
+    void OnTestIterationStart(const testing::UnitTest &, int) override;
+    void OnEnvironmentsSetUpStart(const testing::UnitTest &) override;
+    void OnEnvironmentsSetUpEnd(const testing::UnitTest &) override;
+    void OnTestSuiteStart(const testing::TestSuite &) override;
+    void OnTestCaseStart(const testing::TestCase &) override;
+    void OnTestStart(const testing::TestInfo &) override;
+    void OnTestDisabled(const testing::TestInfo &) override;
+    void OnTestPartResult(const testing::TestPartResult &) override;
+    void OnTestEnd(const testing::TestInfo &) override;
+    void OnTestSuiteEnd(const testing::TestSuite &) override;
+    void OnTestCaseEnd(const testing::TestCase &) override;
+    void OnEnvironmentsTearDownStart(const testing::UnitTest &) override;
+    void OnEnvironmentsTearDownEnd(const testing::UnitTest &) override;
+    void OnTestIterationEnd(const testing::UnitTest &, int) override;
+    void OnTestProgramEnd(const testing::UnitTest &) override;
+};
+} // namespace ignite::detail
\ No newline at end of file
diff --git a/modules/platforms/cpp/tests/compatibility-tests/main.cpp 
b/modules/platforms/cpp/tests/compatibility-tests/main.cpp
new file mode 100644
index 00000000000..183bcdfc2c7
--- /dev/null
+++ b/modules/platforms/cpp/tests/compatibility-tests/main.cpp
@@ -0,0 +1,236 @@
+// 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.
+//
+
+#include "ignite_runner.h"
+#include "ignite_xml_unit_test_result_printer.h"
+#include "test_utils.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <csignal>
+#include <cstring>
+
+namespace {
+
+/** Shutdown handler that cleans up resources. */
+std::function<void(int)> shutdown_handler;
+
+/**
+ * Receives OS signal and handles it.
+ *
+ * @param signum Signal value.
+ */
+void signal_handler(int signum) {
+    shutdown_handler(signum);
+    signal(signum, SIG_DFL);
+    raise(signum);
+}
+
+} // namespace
+
+/**
+ * Sets process abortion (SIGABRT, SIGINT, SIGSEGV signals) handler.
+ *
+ * @param handler Abortion handler.
+ */
+void set_process_abort_handler(std::function<void(int)> handler) {
+    shutdown_handler = std::move(handler);
+
+    // Install signal handlers to clean up resources on early exit.
+    signal(SIGABRT, signal_handler);
+    signal(SIGINT, signal_handler);
+    signal(SIGSEGV, signal_handler);
+}
+
+using namespace ignite;
+
+const std::vector<std::string> DEFAULT_VERSIONS = {"9.1.0", "9.1.1", "9.1.2", 
"9.1.3", "9.1.4"};
+
+/**
+ * Structure to store argument values for automatic memory management.
+ */
+struct ArgumentValuesHolder {
+private:
+    int m_argc;
+    std::vector<std::string> m_vals;
+    char **m_argv;
+
+public:
+    ArgumentValuesHolder(int argc, std::vector<std::string> vals)
+        : m_argc(argc)
+        , m_vals(std::move(vals))
+        , m_argv(new char *[argc])
+    {
+
+        int idx = 0;
+        for (auto& val: m_vals) {
+            m_argv[idx++] = &val.front();
+        }
+    }
+
+    ~ArgumentValuesHolder() {
+        delete[] m_argv;
+    }
+
+    ArgumentValuesHolder(const ArgumentValuesHolder &other) = delete;
+
+    ArgumentValuesHolder(ArgumentValuesHolder &&other) = delete;
+
+    ArgumentValuesHolder &operator=(const ArgumentValuesHolder &other) = 
delete;
+
+    ArgumentValuesHolder &operator=(ArgumentValuesHolder &&other) = delete;
+
+    char** get_argv() {
+        return m_argv;
+    }
+};
+
+/**
+ * Creates modified copy of argument values in order to influence test 
framework behavior for different versions.
+ * @param argc Argument count.
+ * @param argv Argument values.
+ * @param version Compatibility version.
+ * @return An object which contains modified argument values.
+ */
+ArgumentValuesHolder override_xml_output_parameter(int argc, char **argv, 
const std::string &version) {
+    std::vector<std::string> vals;
+    vals.reserve(argc);
+    for (int i = 0; i < argc; ++i) {
+        std::string s = argv[i];
+
+        if (s.find("--gtest_output=xml:") == 0) {
+            if (auto pos = s.rfind(".xml"); pos == s.size() - 4) {
+                s.insert(pos, version);
+            }
+        }
+
+        vals.push_back(s);
+    }
+
+    return ArgumentValuesHolder{argc, std::move(vals)};
+}
+
+/**
+ * Replaces default xml printer with ours which adds version information to 
report.
+ * @param version Compatibility server version
+ */
+void replace_xml_print(const std::string &version) {
+    auto &test_event_listeners = 
::testing::UnitTest::GetInstance()->listeners();
+
+    ::testing::TestEventListener *xml = 
test_event_listeners.default_xml_generator();
+
+    if (xml) {
+        test_event_listeners.Release(xml);
+
+        test_event_listeners.Append(new 
detail::ignite_xml_unit_test_result_printer(xml, version));
+    }
+}
+
+int run_tests(int argc, char **argv, const std::string &version) {
+    // In case when RUN_ALL_TESTS() not started;
+    int res = 4;
+    try {
+        std::cout << "[**********] " << "Compatibility suite for version " << 
version << " started." << std::endl;
+
+        auto holder = override_xml_output_parameter(argc, argv, version);
+
+        ::testing::InitGoogleTest(&argc, holder.get_argv());
+
+        replace_xml_print(version);
+
+        res = RUN_ALL_TESTS();
+
+        std::cout << "[**********] " << "Compatibility suite for version " << 
version << " finished" << std::endl;
+    } catch (const std::exception &err) {
+        std::cout << "[" << version << "] Uncaught error: " << err.what() << 
std::endl;
+        res = std::min(res, 2);
+    } catch (...) {
+        std::cout << "[" << version << "] Unknown uncaught error" << std::endl;
+        res = std::min(res, 3);
+    }
+
+    return res;
+}
+
+int main(int argc, char **argv) {
+    auto ver_override = 
detail::get_env("IGNITE_CPP_TESTS_COMPATIBILITY_VERSIONS_OVERRIDE");
+
+    const auto &versions = ver_override
+    ? [&ver_override]() {
+        std::cout << "Parsing compatibility version override: " << 
*ver_override << "\n";
+
+        std::vector<std::string> v;
+        size_t l = 0;
+        size_t r;
+        do {
+            r = ver_override->find(',', l);
+            v.push_back(ver_override->substr(l, r - l));
+            l = r + 1;
+        } while (r != std::string::npos);
+
+        return v;
+    }()
+    : DEFAULT_VERSIONS;
+
+    int res = 0;
+    for (size_t i = 0; i < versions.size(); ++i) {
+        const std::string& version = versions[i];
+
+        ignite_runner runner{version};
+
+        set_process_abort_handler([&](int signal) {
+            std::cout << "Caught signal " << signal << " during tests" << 
std::endl;
+
+            runner.stop();
+        });
+
+        if (!check_test_node_connectable(std::chrono::seconds(5))) {
+            runner.start();
+            ensure_node_connectable(std::chrono::seconds(90));
+        }
+        int run_res;
+#ifdef _WIN32
+        run_res = run_tests(argc, argv, version);
+#else
+
+        // When debugging we most likely would choose one version
+        // For debug reasons last version will run in the same process
+        if (i == versions.size() - 1) {
+            run_res = run_tests(argc, argv, version);
+        } else {
+            // We fork because GTest initializes a lot of global variables 
which interfere with subsequent runs.
+            int pid = ::fork();
+
+            if (pid == -1) {
+                std::stringstream ss;
+                ss << "Can't fork process for version " << version;
+                throw std::runtime_error(ss.str());
+            }
+
+            if (pid == 0) {
+                run_res = run_tests(argc, argv, version);
+                break;
+            }
+
+            waitpid(pid, &run_res, 0);
+        }
+#endif
+        res = std::max(res, run_res);
+    }
+
+    return res;
+}
\ No newline at end of file
diff --git a/modules/platforms/cpp/tests/test-common/detail/unix_process.h 
b/modules/platforms/cpp/tests/test-common/detail/unix_process.h
index 7cfa5f5c79a..a94b18118fe 100644
--- a/modules/platforms/cpp/tests/test-common/detail/unix_process.h
+++ b/modules/platforms/cpp/tests/test-common/detail/unix_process.h
@@ -83,6 +83,7 @@ public:
             }
 
             std::vector<const char *> args;
+            args.reserve(m_args.size() + 2);
             args.push_back(m_command.c_str());
 
             for (auto &arg : m_args) {
diff --git a/modules/platforms/cpp/tests/test-common/ignite_runner.cpp 
b/modules/platforms/cpp/tests/test-common/ignite_runner.cpp
index 614c9dbdfae..925b115dc94 100644
--- a/modules/platforms/cpp/tests/test-common/ignite_runner.cpp
+++ b/modules/platforms/cpp/tests/test-common/ignite_runner.cpp
@@ -16,17 +16,31 @@
  */
 
 #include "ignite_runner.h"
+
+#include "ignite/client/ignite_client_configuration.h"
+#include "ignite_type.h"
 #include "test_utils.h"
 
 #include "ignite/common/detail/config.h"
 
 #include <filesystem>
-#include <iostream>
 #include <sstream>
 #include <stdexcept>
 
+#ifdef WIN32
+# include <Windows.h>
+#endif
+
 namespace {
 
+void set_environment_variable(const char* name, const char* value) {
+#ifdef WIN32
+    SetEnvironmentVariable(name, value);
+#else
+    setenv(name, value, 1);
+#endif
+}
+
 /**
  * System shell command string.
  */
@@ -34,19 +48,40 @@ constexpr std::string_view SYSTEM_SHELL = 
IGNITE_SWITCH_WIN_OTHER("cmd.exe", "/b
 constexpr std::string_view SYSTEM_SHELL_ARG_0 = IGNITE_SWITCH_WIN_OTHER("/c ", 
"-c");
 constexpr std::string_view GRADLEW_SCRIPT = 
IGNITE_SWITCH_WIN_OTHER("gradlew.bat", "./gradlew");
 
-const std::string SERVER_ADDRESS = "127.0.0.1";
+const char* IGNITE_CLUSTER_HOST = getenv("IGNITE_CLUSTER_HOST");
+const std::string SERVER_ADDRESS = IGNITE_CLUSTER_HOST ? IGNITE_CLUSTER_HOST : 
"127.0.0.1";
+
+const std::string ADDITIONAL_JVM_OPTIONS_ENV = "CPP_ADDITIONAL_JVM_OPTIONS";
 
 } // anonymous namespace
 
 namespace ignite {
 
+static int calc_node_port_offset(std::string_view version);
+
 std::vector<std::string> ignite_runner::SINGLE_NODE_ADDR = {SERVER_ADDRESS + 
":10942"};
 std::vector<std::string> ignite_runner::NODE_ADDRS = {SERVER_ADDRESS + 
":10942", SERVER_ADDRESS + ":10943"};
 std::vector<std::string> ignite_runner::SSL_NODE_ADDRS = {SERVER_ADDRESS + 
":10944"};
 std::vector<std::string> ignite_runner::SSL_NODE_CA_ADDRS = {SERVER_ADDRESS + 
":10945"};
+std::vector<std::string> ignite_runner::COMPATIBILITY_NODE_ADDRS = {};
+
+ignite_runner::ignite_runner(std::string_view version) : m_version(version) {
+    COMPATIBILITY_MODE = true;
+    COMPATIBILITY_NODE_ADDRS = {SERVER_ADDRESS + ":"
+        + std::to_string(ignite_client_configuration::DEFAULT_PORT + 
calc_node_port_offset(version))};
+    COMPATIBILITY_VERSION = version;
+}
 
 void ignite_runner::start() {
     std::string home = resolve_ignite_home();
+
+    std::string work_dir;
+    if (m_version) {
+        work_dir = resolve_temp_dir("IgniteCompatibility", 
*m_version).string();
+    } else {
+        work_dir = home;
+    }
+
     if (home.empty())
         throw std::runtime_error("Can not resolve Ignite home directory. Try 
setting IGNITE_HOME explicitly");
 
@@ -54,14 +89,37 @@ void ignite_runner::start() {
     args.emplace_back(SYSTEM_SHELL_ARG_0);
 
     std::string command{GRADLEW_SCRIPT};
-    command += " :ignite-runner:runnerPlatformTest"
-               " --no-daemon"
-               " -x compileJava"
-               " -x compileTestFixturesJava"
-               " -x compileIntegrationTestJava"
-               " -x compileTestJava";
+
+    if (m_version) {
+        command += " 
:ignite-compatibility-tests:runnerPlatformCompatibilityTest --parallel";
+    } else {
+        command += " :ignite-runner:runnerPlatformTest"
+                   " --no-daemon"
+                   " -x compileJava"
+                   " -x compileTestFixturesJava"
+                   " -x compileIntegrationTestJava"
+                   " -x compileTestJava";
+    }
+
+    if (auto additional_opts = detail::get_env(ADDITIONAL_JVM_OPTIONS_ENV)) {
+        command += " " + *additional_opts;
+    }
 
     args.emplace_back(command);
+    if (m_version) {
+        set_environment_variable("IGNITE_OLD_SERVER_VERSION", 
m_version->c_str());
+        set_environment_variable("IGNITE_OLD_SERVER_WORK_DIR", 
work_dir.c_str());
+
+        int node_port_offset = calc_node_port_offset(*m_version);
+
+        std::string port = std::to_string(3344 + 20000 + node_port_offset);
+        std::string http_port = std::to_string(10300 + node_port_offset);
+        std::string client_port = 
std::to_string(ignite_client_configuration::DEFAULT_PORT + node_port_offset);
+
+        set_environment_variable("IGNITE_OLD_SERVER_PORT", port.c_str());
+        set_environment_variable("IGNITE_OLD_SERVER_HTTP_PORT", 
http_port.c_str());
+        set_environment_variable("IGNITE_OLD_SERVER_CLIENT_PORT", 
client_port.c_str());
+    }
 
     m_process = CmdProcess::make(std::string(SYSTEM_SHELL), args, home);
     if (!m_process->start()) {
@@ -85,4 +143,12 @@ void ignite_runner::join(std::chrono::milliseconds timeout) 
{
         m_process->join(timeout);
 }
 
+static int calc_node_port_offset(std::string_view version) {
+    std::string port_shift;
+
+    std::copy_if(version.begin(), version.end(), 
std::back_inserter(port_shift), [](auto c) { return c >= '0' && c <= '9'; });
+
+    return stoi(port_shift);
+}
+
 } // namespace ignite
\ No newline at end of file
diff --git a/modules/platforms/cpp/tests/test-common/ignite_runner.h 
b/modules/platforms/cpp/tests/test-common/ignite_runner.h
index 37484821384..f05872ea986 100644
--- a/modules/platforms/cpp/tests/test-common/ignite_runner.h
+++ b/modules/platforms/cpp/tests/test-common/ignite_runner.h
@@ -39,6 +39,17 @@ public:
     static std::vector<std::string> NODE_ADDRS;
     static std::vector<std::string> SSL_NODE_ADDRS;
     static std::vector<std::string> SSL_NODE_CA_ADDRS;
+    static std::vector<std::string> COMPATIBILITY_NODE_ADDRS;
+    inline static bool COMPATIBILITY_MODE = false;
+    inline static std::string COMPATIBILITY_VERSION;
+
+    ignite_runner() = default;
+
+    /**
+     *
+     * @param version If present then should start server of that particular 
version otherwise current version.
+     */
+    ignite_runner(std::string_view version);
 
     /**
      * Destructor.
@@ -75,6 +86,10 @@ public:
      * @return Addresses.
      */
     static std::vector<std::string> get_node_addrs() {
+        if (COMPATIBILITY_MODE) {
+            return COMPATIBILITY_NODE_ADDRS;
+        }
+
         if (single_node_mode())
             return SINGLE_NODE_ADDR;
 
@@ -98,6 +113,7 @@ public:
 private:
     /** Underlying process. */
     std::unique_ptr<CmdProcess> m_process;
+    std::optional<std::string> m_version = std::nullopt;
 };
 
 } // namespace ignite
diff --git a/modules/platforms/cpp/tests/test-common/process.cpp 
b/modules/platforms/cpp/tests/test-common/process.cpp
index a562c35d534..04979eb1ee0 100644
--- a/modules/platforms/cpp/tests/test-common/process.cpp
+++ b/modules/platforms/cpp/tests/test-common/process.cpp
@@ -31,7 +31,7 @@ namespace ignite {
 
 std::unique_ptr<CmdProcess> CmdProcess::make(std::string command, 
std::vector<std::string> args, std::string workDir) {
 #ifdef _WIN32
-    return std::unique_ptr<CmdProcess>(new 
detail::WinProcess(std::move(command), std::move(args), std::move(workDir)));
+    return std::make_unique<detail::WinProcess>(std::move(command), 
std::move(args), std::move(workDir));
 #else
     return std::unique_ptr<CmdProcess>(
         new detail::UnixProcess(std::move(command), std::move(args), 
std::move(workDir)));
diff --git a/modules/platforms/cpp/tests/test-common/test_utils.cpp 
b/modules/platforms/cpp/tests/test-common/test_utils.cpp
index 790b4637286..620a98189a6 100644
--- a/modules/platforms/cpp/tests/test-common/test_utils.cpp
+++ b/modules/platforms/cpp/tests/test-common/test_utils.cpp
@@ -25,6 +25,7 @@
 #include <functional>
 #include <iostream>
 #include <thread>
+#include <random>
 
 namespace ignite {
 
@@ -99,6 +100,37 @@ std::filesystem::path resolve_test_dir() {
     throw ignite_error("Can not find a 'tests' directory in the current Ignite 
Home: " + home);
 }
 
+std::filesystem::path resolve_temp_dir(std::string_view subDir, 
std::string_view prefix) {
+    std::random_device rd;
+    std::mt19937 gen(rd());
+    std::uniform_int_distribution<> dis(0, 15);
+
+    std::stringstream ss;
+
+    if (!prefix.empty()) {
+        ss << prefix << "_";
+    }
+
+
+    for (int i = 0; i < 16; ++i) {
+        int num = dis(gen);
+
+        if (num <= 9) {
+            ss << num;
+        } else {
+            ss << static_cast<char>('a' + (num - 10));
+        }
+    }
+
+    auto path = std::filesystem::temp_directory_path();
+
+    if (!subDir.empty()) {
+        path /= subDir;
+    }
+
+    return path / ss.str();
+}
+
 bool check_test_node_connectable(std::chrono::seconds timeout) {
     try {
         ensure_node_connectable(timeout);
diff --git a/modules/platforms/cpp/tests/test-common/test_utils.h 
b/modules/platforms/cpp/tests/test-common/test_utils.h
index 88f57696421..9c661c3dae4 100644
--- a/modules/platforms/cpp/tests/test-common/test_utils.h
+++ b/modules/platforms/cpp/tests/test-common/test_utils.h
@@ -49,6 +49,14 @@ std::string resolve_ignite_home(const std::string &path = 
"");
  */
 std::filesystem::path resolve_test_dir();
 
+/**
+ * Generates a path in the temporary directory.
+ * @param subDir Optional subdirectory.
+ * @param prefix Optional prefix.
+ * @return Filesystem path to the generated temporary directory.
+ */
+std::filesystem::path resolve_temp_dir(std::string_view subDir = "", 
std::string_view prefix = "");
+
 /**
  * Check async operation result and propagate error to the promise if there is
  * any.

Reply via email to