blob: 65bb61befed89b4fbbe3a960fa4e82928495828e [file]
// 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);
#ifndef _WIN32
signal(SIGPIPE, signal_handler);
#endif
}
using namespace ignite;
const std::vector<std::string> DEFAULT_VERSIONS = {"3.0.0", "3.1.0"};
/**
* 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;
}