From 1129aaf54bb0144b82c9541cfd1e607af3660855 Mon Sep 17 00:00:00 2001 From: subramaniak Date: Fri, 17 Apr 2026 05:14:47 +0000 Subject: [PATCH 01/10] Added cpp implementation for feature_integration_tests --- feature_integration_tests/test_cases/BUILD | 20 ++ .../test_cases/conftest.py | 17 ++ .../test_cases/fit_scenario.py | 5 +- .../test_scenarios/cpp/BUILD | 59 ++++ .../test_scenarios/cpp/README.md | 119 ++++++++ .../cpp/src/framework/scenario_framework.h | 72 +++++ .../internals/persistency/kvs_instance.cpp | 279 ++++++++++++++++++ .../src/internals/persistency/kvs_instance.h | 60 ++++ .../internals/persistency/kvs_parameters.cpp | 110 +++++++ .../internals/persistency/kvs_parameters.h | 58 ++++ .../test_scenarios/cpp/src/main.cpp | 104 +++++++ .../test_scenarios/cpp/src/scenarios/mod.cpp | 31 ++ .../cpp/src/scenarios/persistency/mod.cpp | 24 ++ .../persistency/multiple_kvs_per_app.cpp | 220 ++++++++++++++ 14 files changed, 1176 insertions(+), 2 deletions(-) create mode 100644 feature_integration_tests/test_scenarios/cpp/BUILD create mode 100644 feature_integration_tests/test_scenarios/cpp/README.md create mode 100644 feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h create mode 100644 feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h create mode 100644 feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h create mode 100644 feature_integration_tests/test_scenarios/cpp/src/main.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp create mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp diff --git a/feature_integration_tests/test_cases/BUILD b/feature_integration_tests/test_cases/BUILD index ab0717abf1d..6187084d008 100644 --- a/feature_integration_tests/test_cases/BUILD +++ b/feature_integration_tests/test_cases/BUILD @@ -55,3 +55,23 @@ score_py_pytest( pytest_config = "//:pyproject.toml", deps = all_requirements, ) + +score_py_pytest( + name = "fit_cpp", + srcs = [ + "tests/persistency/test_multiple_kvs_per_app.py", + ], + args = [ + "--scenario-target=cpp", + "--traces=all", + "--cpp-target-path=$(rootpath //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios)", + ], + data = [ + "conftest.py", + "fit_scenario.py", + "test_properties.py", + "//feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios", + ], + pytest_config = "//:pyproject.toml", + deps = all_requirements, +) diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index fc20f61f2fc..a3dd8545405 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -30,6 +30,12 @@ def pytest_addoption(parser): '"all" - show all traces. ', ) + parser.addoption( + "--scenario-target", + type=str, + choices=["cpp", "rust"], + help="Test scenario target language (cpp or rust).", + ) parser.addoption( "--rust-target-name", type=str, @@ -41,6 +47,17 @@ def pytest_addoption(parser): type=Path, help="Rust test scenario executable target.", ) + parser.addoption( + "--cpp-target-name", + type=str, + default="//feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios", + help="C++ test scenario executable target.", + ) + parser.addoption( + "--cpp-target-path", + type=Path, + help="C++ test scenario executable target.", + ) parser.addoption( "--build-scenarios", action="store_true", diff --git a/feature_integration_tests/test_cases/fit_scenario.py b/feature_integration_tests/test_cases/fit_scenario.py index 6188fec4fa3..30dad9ee4b0 100644 --- a/feature_integration_tests/test_cases/fit_scenario.py +++ b/feature_integration_tests/test_cases/fit_scenario.py @@ -68,8 +68,9 @@ class FitScenario(Scenario): """ @pytest.fixture(scope="class") - def build_tools(self) -> BuildTools: - return BazelTools(option_prefix="rust") + def build_tools(self, request: pytest.FixtureRequest) -> BuildTools: + scenario_target = request.config.getoption("--scenario-target", default="rust") + return BazelTools(option_prefix=scenario_target) def expect_command_failure(self, *args, **kwargs) -> bool: """ diff --git a/feature_integration_tests/test_scenarios/cpp/BUILD b/feature_integration_tests/test_scenarios/cpp/BUILD new file mode 100644 index 00000000000..76065728dd3 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/BUILD @@ -0,0 +1,59 @@ +# ******************************************************************************* +# Copyright (c) 2026 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-License-Identifier: Apache-2.0 +# ******************************************************************************* + +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") + +cc_library( + name = "kvs_parameters", + hdrs = ["src/internals/persistency/kvs_parameters.h"], + srcs = ["src/internals/persistency/kvs_parameters.cpp"], + deps = [ + "@score_baselibs//score/json", + ], +) + +cc_library( + name = "kvs_instance", + hdrs = ["src/internals/persistency/kvs_instance.h"], + srcs = ["src/internals/persistency/kvs_instance.cpp"], + deps = [ + ":kvs_parameters", + "@score_baselibs//score/mw/log:console_only_backend", + "@score_persistency//:kvs_cpp", + ], +) + +cc_library( + name = "scenarios_lib", + hdrs = ["src/framework/scenario_framework.h"], + srcs = [ + "src/scenarios/mod.cpp", + "src/scenarios/persistency/mod.cpp", + "src/scenarios/persistency/multiple_kvs_per_app.cpp", + ], + deps = [ + ":kvs_instance", + ":kvs_parameters", + "@score_baselibs//score/json", + ], +) + +cc_binary( + name = "cpp_test_scenarios", + srcs = ["src/main.cpp"], + tags = ["manual"], + visibility = ["//feature_integration_tests/test_cases:__pkg__"], + deps = [ + ":scenarios_lib", + ], +) diff --git a/feature_integration_tests/test_scenarios/cpp/README.md b/feature_integration_tests/test_scenarios/cpp/README.md new file mode 100644 index 00000000000..120d85fb7e2 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/README.md @@ -0,0 +1,119 @@ +# C++ Feature Integration Test Scenarios + +This directory contains the C++ implementation of feature integration test scenarios. + +Current scope is persistency-focused scenarios. + +## Structure + +- src/main.cpp + - CLI entry point for scenario execution. + - Supports listing scenarios and running a scenario with JSON input. +- src/framework/scenario_framework.h + - Minimal scenario framework: + - Scenario interface + - ScenarioGroup hierarchy + - Recursive scenario name flattening +- src/internals/persistency/ + - kvs_parameters.h/.cpp + - Parses KVS config sections from JSON input. + - kvs_instance.h/.cpp + - Wraps C++ KVS builder/API operations. + - Provides typed set/get helpers. + - Normalizes snapshot JSON to Rust-compatible top-level envelope. + - Canonicalizes noisy f64 literals in snapshots (for example 111.099998 -> 111.1) + so Python FIT checks can keep strict value equality. +- src/scenarios/ + - mod.cpp + - Registers root scenario groups. + - persistency/mod.cpp + - Registers persistency scenarios. + - persistency/multiple_kvs_per_app.cpp + - Scenario implementation for multi-instance KVS validation. + +## Build Targets + +Defined in BUILD: + +- kvs_parameters (cc_library) +- kvs_instance (cc_library) +- scenarios_lib (cc_library) +- cpp_test_scenarios (cc_binary) + +## Available Scenario + +- persistency.multiple_kvs_per_app + +## Scenario Contract + +persistency.multiple_kvs_per_app expects JSON with: + +- kvs_parameters_1.kvs_parameters +- kvs_parameters_2.kvs_parameters +- test + - key + - value_1 + - value_2 + +Behavior: + +1. Creates two KVS instances with different instance IDs. +2. Writes different values for the same key. +3. Flushes both instances. +4. Reopens both instances. +5. Reads values back and emits INFO logs. +6. Normalizes written snapshot files to Rust-compatible envelope format. +7. Canonicalizes persisted f64 literals that carry float-noise artifacts. + +## Notes + +- C++ persistency snapshots may expose float-noise literals even for semantic decimal inputs. + The scenario adapter normalizes these values during snapshot envelope conversion so + FIT assertions in Python can remain strict (`==`) instead of using tolerances. + +## Traceability + +The C++ scenario `persistency.multiple_kvs_per_app` is validated by the FIT test module: + +- `feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py` + +Verification metadata in that test currently declares: + +- `partially_verifies = ["feat_req__persistency__multiple_kvs"]` +- `test_type = "requirements-based"` +- `derivation_technique = "requirements-analysis"` + +This keeps the scenario name, test input contract, and requirement linkage aligned across +the C++ scenario runner and the Python FIT layer. + +## Usage + +Build: + +```sh +bazel build //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios +``` + +List scenarios: + +```sh +bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios +``` + +Run scenario: + +```sh +bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- \ + --name persistency.multiple_kvs_per_app \ + --input '{ + "kvs_parameters_1": {"kvs_parameters": {"instance_id": 1, "dir": "/tmp/cpp_fit"}}, + "kvs_parameters_2": {"kvs_parameters": {"instance_id": 2, "dir": "/tmp/cpp_fit"}}, + "test": {"key": "number", "value_1": 111.1, "value_2": 222.2} + }' +``` + +Run the C++ persistency-focused FIT target: + +```sh +bazel test //feature_integration_tests/test_cases:fit_cpp +``` diff --git a/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h b/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h new file mode 100644 index 00000000000..c9a322db85d --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h @@ -0,0 +1,72 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#ifndef TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ +#define TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ + +#include +#include +#include +#include + +class Scenario { +public: + virtual ~Scenario() = default; + virtual std::string name() const = 0; + virtual int run(const std::string& input) = 0; +}; + +class ScenarioGroup { +public: + explicit ScenarioGroup(std::string name) + : name_(std::move(name)) {} + + void add_scenario(const std::string& name, std::shared_ptr scenario) { + scenarios_[name] = std::move(scenario); + } + + void add_group(std::shared_ptr group) { + groups_.push_back(std::move(group)); + } + + const std::string& name() const { return name_; } + const std::map>& scenarios() const { return scenarios_; } + const std::vector>& groups() const { return groups_; } + +private: + std::string name_; + std::map> scenarios_; + std::vector> groups_; +}; + +using ScenarioRegistry = std::map>; + +inline void collect_scenarios_recursive(const ScenarioGroup& group, + ScenarioRegistry& out, + const std::string& prefix = "") { + std::string next_prefix = prefix; + if (group.name() != "root") { + next_prefix = prefix.empty() ? group.name() : (prefix + "." + group.name()); + } + + for (const auto& [scenario_name, scenario] : group.scenarios()) { + const std::string full_name = next_prefix.empty() ? scenario_name : (next_prefix + "." + scenario_name); + out[full_name] = scenario; + } + + for (const auto& subgroup : group.groups()) { + collect_scenarios_recursive(*subgroup, out, next_prefix); + } +} + +#endif // TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp new file mode 100644 index 00000000000..b23dd2121f3 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp @@ -0,0 +1,279 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "kvs_instance.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::string trim(std::string s) { + while (!s.empty() && std::isspace(static_cast(s.front()))) { + s.erase(s.begin()); + } + while (!s.empty() && std::isspace(static_cast(s.back()))) { + s.pop_back(); + } + return s; +} + +std::string format_double_compact(double value) { + std::ostringstream out; + out << std::setprecision(15) << std::defaultfloat << value; + return out.str(); +} + +double maybe_snap_noisy_decimal(double value) { + // Persisted f64 payloads may contain f32-style artifacts like 111.099998. + // Snap only when very close to a short decimal representation. + double scale = 10.0; + for (int decimals = 1; decimals <= 6; ++decimals) { + const double snapped = std::round(value * scale) / scale; + if (std::fabs(value - snapped) <= 1e-5) { + return snapped; + } + scale *= 10.0; + } + return value; +} + +std::string canonicalize_f64_literals(const std::string& json) { + static const std::regex f64_value_pattern( + R"("t"\s*:\s*"f64"\s*,\s*"v"\s*:\s*([-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?))"); + + std::string result; + result.reserve(json.size()); + + std::size_t cursor = 0; + for (std::sregex_iterator it(json.begin(), json.end(), f64_value_pattern), end; it != end; ++it) { + const std::smatch& match = *it; + const auto group_pos = static_cast(match.position(1)); + const auto group_len = static_cast(match.length(1)); + + result.append(json, cursor, group_pos - cursor); + + const std::string literal = match.str(1); + try { + const double parsed = std::stod(literal); + const double canonical = maybe_snap_noisy_decimal(parsed); + result += format_double_compact(canonical); + } catch (const std::exception&) { + result += literal; + } + + cursor = group_pos + group_len; + } + + result.append(json, cursor, std::string::npos); + return result; +} + +std::optional snapshot_path(const KvsParameters& params) { + if (!params.dir.has_value()) { + return std::nullopt; + } + + std::string dir = *params.dir; + if (!dir.empty() && dir.back() == '/') { + dir.pop_back(); + } + + return dir + "/kvs_" + std::to_string(params.instance_id.value) + "_0.json"; +} + +std::optional to_need_flag(const std::optional& mode) { + if (!mode.has_value()) { + return std::nullopt; + } + if (*mode == KvsDefaults::Required) { + return true; + } + // Optional and Ignored are mapped to not-required for C++ KvsBuilder API. + return false; +} + +std::optional to_need_flag(const std::optional& mode) { + if (!mode.has_value()) { + return std::nullopt; + } + if (*mode == KvsLoad::Required) { + return true; + } + // Optional and Ignored are mapped to not-required for C++ KvsBuilder API. + return false; +} + +} // namespace + +KvsInstance::KvsInstance(const KvsParameters& params, score::mw::per::kvs::Kvs&& kvs) + : params_(params), kvs_(std::move(kvs)) {} + +std::optional> KvsInstance::create(const KvsParameters& params) { + using namespace score::mw::per::kvs; + + try { + if (params.snapshot_max_count.has_value() && + params.snapshot_max_count.value() != static_cast(KVS_MAX_SNAPSHOTS)) { + std::cerr << "Unsupported snapshot_max_count for C++ KVS API: requested " + << params.snapshot_max_count.value() << ", supported " << KVS_MAX_SNAPSHOTS + << std::endl; + return std::nullopt; + } + + KvsBuilder builder{score::mw::per::kvs::InstanceId{params.instance_id.value}}; + + if (const auto defaults = to_need_flag(params.defaults)) { + builder = builder.need_defaults_flag(*defaults); + } + if (const auto kvs_load = to_need_flag(params.kvs_load)) { + builder = builder.need_kvs_flag(*kvs_load); + } + if (params.dir.has_value()) { + builder = builder.dir(std::string(*params.dir)); + } + + auto build_result = builder.build(); + if (!build_result) { + return std::nullopt; + } + + return std::shared_ptr(new KvsInstance(params, std::move(build_result.value()))); + + } catch (const std::exception& e) { + std::cerr << "Error creating KVS instance: " << e.what() << std::endl; + return std::nullopt; + } +} + +bool KvsInstance::normalize_snapshot_file_to_rust_envelope(const KvsParameters& params) { + const auto path_opt = snapshot_path(params); + if (!path_opt.has_value()) { + std::cerr << "Cannot normalize snapshot: missing directory parameter" << std::endl; + return false; + } + + std::ifstream in(*path_opt); + if (!in.is_open()) { + std::cerr << "Cannot normalize snapshot: failed to open " << *path_opt << std::endl; + return false; + } + + std::ostringstream buffer; + buffer << in.rdbuf(); + const std::string content = trim(buffer.str()); + const std::string canonical_content = canonicalize_f64_literals(content); + + std::string final_content; + if (canonical_content.rfind("{\"t\":\"obj\",\"v\":", 0) == 0) { + final_content = canonical_content; + } else { + final_content = "{\"t\":\"obj\",\"v\":" + canonical_content + "}"; + } + + std::ofstream out(*path_opt, std::ios::trunc); + if (!out.is_open()) { + std::cerr << "Cannot normalize snapshot: failed to write " << *path_opt << std::endl; + return false; + } + + out << final_content; + return static_cast(out); +} + +bool KvsInstance::set_value(const std::string& key, double value) { + auto result = kvs_.set_value(key, score::mw::per::kvs::KvsValue{value}); + return static_cast(result); +} + +bool KvsInstance::set_value_u32(const std::string& key, std::uint32_t value) { + auto result = kvs_.set_value(key, score::mw::per::kvs::KvsValue{value}); + return static_cast(result); +} + +std::optional KvsInstance::get_value(const std::string& key) { + auto result = kvs_.get_value(key); + if (!result) { + return std::nullopt; + } + + const auto& stored = result.value(); + const auto& variant = stored.getValue(); + switch (stored.getType()) { + case score::mw::per::kvs::KvsValue::Type::f64: + return std::get(variant); + case score::mw::per::kvs::KvsValue::Type::i32: + return static_cast(std::get(variant)); + case score::mw::per::kvs::KvsValue::Type::u32: + return static_cast(std::get(variant)); + case score::mw::per::kvs::KvsValue::Type::i64: + return static_cast(std::get(variant)); + case score::mw::per::kvs::KvsValue::Type::u64: + return static_cast(std::get(variant)); + default: + return std::nullopt; + } +} + +std::optional KvsInstance::get_value_u32(const std::string& key) { + auto result = kvs_.get_value(key); + if (!result) { + return std::nullopt; + } + + const auto& stored = result.value(); + const auto& variant = stored.getValue(); + switch (stored.getType()) { + case score::mw::per::kvs::KvsValue::Type::u32: + return std::get(variant); + case score::mw::per::kvs::KvsValue::Type::i32: { + const auto value = std::get(variant); + return value >= 0 ? std::optional(static_cast(value)) : std::nullopt; + } + case score::mw::per::kvs::KvsValue::Type::u64: { + const auto value = std::get(variant); + return value <= static_cast(std::numeric_limits::max()) + ? std::optional(static_cast(value)) + : std::nullopt; + } + case score::mw::per::kvs::KvsValue::Type::i64: { + const auto value = std::get(variant); + return (value >= 0 && value <= static_cast(std::numeric_limits::max())) + ? std::optional(static_cast(value)) + : std::nullopt; + } + case score::mw::per::kvs::KvsValue::Type::f64: { + const auto value = std::get(variant); + return (value >= 0.0 && value <= static_cast(std::numeric_limits::max())) + ? std::optional(static_cast(value)) + : std::nullopt; + } + default: + return std::nullopt; + } +} + +bool KvsInstance::flush() { + auto result = kvs_.flush(); + return static_cast(result); +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h new file mode 100644 index 00000000000..e3a7f2662d4 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h @@ -0,0 +1,60 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#ifndef INTERNALS_PERSISTENCY_KVS_INSTANCE_H_ +#define INTERNALS_PERSISTENCY_KVS_INSTANCE_H_ + +#include "kvs_parameters.h" + +#include +#include +#include +#include +#include + +class KvsInstance { +public: + // Factory method to create KVS instance + static std::optional> create(const KvsParameters& params); + + // Wrap snapshot file into Rust-style top-level object envelope. + static bool normalize_snapshot_file_to_rust_envelope(const KvsParameters& params); + + // Set a value + bool set_value(const std::string& key, double value); + + // Set unsigned 32-bit value. + bool set_value_u32(const std::string& key, std::uint32_t value); + + // Get a value + std::optional get_value(const std::string& key); + + // Get unsigned 32-bit value. + std::optional get_value_u32(const std::string& key); + + // Flush to persistent storage + bool flush(); + + // Get instance parameters + const KvsParameters& get_parameters() const { + return params_; + } + +private: + KvsInstance(const KvsParameters& params, score::mw::per::kvs::Kvs&& kvs); + + KvsParameters params_; + score::mw::per::kvs::Kvs kvs_; +}; + +#endif // INTERNALS_PERSISTENCY_KVS_INSTANCE_H_ diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp new file mode 100644 index 00000000000..f48c33bee27 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp @@ -0,0 +1,110 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "kvs_parameters.h" + +#include "score/json/json_parser.h" + +#include + +KvsDefaults parse_kvs_defaults(const std::string& str) { + if (str == "ignored") return KvsDefaults::Ignored; + if (str == "optional") return KvsDefaults::Optional; + if (str == "required") return KvsDefaults::Required; + throw std::invalid_argument("Invalid KvsDefaults mode: " + str); +} + +KvsLoad parse_kvs_load(const std::string& str) { + if (str == "ignored") return KvsLoad::Ignored; + if (str == "optional") return KvsLoad::Optional; + if (str == "required") return KvsLoad::Required; + throw std::invalid_argument("Invalid KvsLoad mode: " + str); +} + +namespace { +const score::json::Object& expect_object(const score::json::Any& value, const std::string& field_name) { + const auto object_res = value.As(); + if (!object_res.has_value()) { + throw std::invalid_argument("Expected object field: " + field_name); + } + return object_res.value().get(); +} + +} // namespace + +KvsParameters KvsParameters::from_json_section(const std::string& input, const std::string& section_name) { + KvsParameters params; + + const score::json::JsonParser parser; + const auto root_any_res = parser.FromBuffer(input); + if (!root_any_res.has_value()) { + throw std::invalid_argument("Failed to parse scenario input JSON"); + } + + const auto root_object_res = root_any_res.value().As(); + if (!root_object_res.has_value()) { + throw std::invalid_argument("Scenario input root must be a JSON object"); + } + + const auto& root = root_object_res.value().get(); + const auto section_it = root.find(section_name); + if (section_it == root.end()) { + throw std::invalid_argument("Missing section: " + section_name); + } + + const score::json::Object* parsed_section = &expect_object(section_it->second, section_name); + const auto nested_parameters_it = parsed_section->find("kvs_parameters"); + if (nested_parameters_it != parsed_section->end()) { + parsed_section = &expect_object(nested_parameters_it->second, "kvs_parameters"); + } + + const auto instance_it = parsed_section->find("instance_id"); + if (instance_it == parsed_section->end()) { + throw std::invalid_argument("Missing instance_id in section: " + section_name); + } + const auto instance_res = instance_it->second.As(); + if (!instance_res.has_value()) { + throw std::invalid_argument("Invalid instance_id in section: " + section_name); + } + params.instance_id = InstanceId(static_cast(instance_res.value())); + + if (const auto defaults_it = parsed_section->find("defaults"); defaults_it != parsed_section->end()) { + const auto defaults_res = defaults_it->second.As(); + if (defaults_res.has_value()) { + params.defaults = parse_kvs_defaults(std::string(defaults_res.value())); + } + } + + if (const auto load_it = parsed_section->find("kvs_load"); load_it != parsed_section->end()) { + const auto load_res = load_it->second.As(); + if (load_res.has_value()) { + params.kvs_load = parse_kvs_load(std::string(load_res.value())); + } + } + + if (const auto dir_it = parsed_section->find("dir"); dir_it != parsed_section->end()) { + const auto dir_res = dir_it->second.As(); + if (dir_res.has_value()) { + params.dir = std::string(dir_res.value()); + } + } + + if (const auto snapshot_it = parsed_section->find("snapshot_max_count"); snapshot_it != parsed_section->end()) { + const auto snapshot_res = snapshot_it->second.As(); + if (snapshot_res.has_value()) { + params.snapshot_max_count = static_cast(snapshot_res.value()); + } + } + + return params; +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h new file mode 100644 index 00000000000..9a7a6b815da --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h @@ -0,0 +1,58 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#ifndef INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ +#define INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ + +#include +#include +#include + +// Enum for KVS defaults mode +enum class KvsDefaults { + Ignored, + Optional, + Required, +}; + +// Enum for KVS load mode +enum class KvsLoad { + Ignored, + Optional, + Required, +}; + +// Instance ID wrapper +struct InstanceId { + uint32_t value; + + explicit InstanceId(uint32_t v = 0) : value(v) {} + + std::string to_string() const { + return "InstanceId(" + std::to_string(value) + ")"; + } +}; + +// KVS parameters parsed from JSON +struct KvsParameters { + InstanceId instance_id; + std::optional defaults; + std::optional kvs_load; + std::optional dir; + std::optional snapshot_max_count; + + // Parse from full scenario input by section name (e.g. "kvs_parameters_1"). + static KvsParameters from_json_section(const std::string& input, const std::string& section_name); +}; + +#endif // INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ diff --git a/feature_integration_tests/test_scenarios/cpp/src/main.cpp b/feature_integration_tests/test_scenarios/cpp/src/main.cpp new file mode 100644 index 00000000000..88eabb798c7 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/main.cpp @@ -0,0 +1,104 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "framework/scenario_framework.h" + +#include +#include + +ScenarioRegistry create_scenario_registry(); + +namespace { + +void print_help() { + std::cout << "Test scenario runner" << std::endl; + std::cout << "'-n', '--name' - test scenario name" << std::endl; + std::cout << "'-i', '--input' - test scenario input" << std::endl; + std::cout << "'-l', '--list-scenarios' - list available scenarios" << std::endl; + std::cout << "'-h', '--help' - show help" << std::endl; +} + +} // namespace + +int main(int argc, char* argv[]) { + try { + const ScenarioRegistry scenarios = create_scenario_registry(); + + std::string scenario_name; + std::string json_input; + bool list_scenarios = false; + bool show_help = false; + + for (int i = 1; i < argc; ++i) { + const std::string arg = argv[i]; + if (arg == "-l" || arg == "--list-scenarios") { + list_scenarios = true; + continue; + } + if (arg == "-h" || arg == "--help") { + show_help = true; + continue; + } + if (arg == "-n" || arg == "--name") { + if (i + 1 >= argc) { + std::cerr << "Missing value for " << arg << std::endl; + return 1; + } + scenario_name = argv[++i]; + continue; + } + if (arg == "-i" || arg == "--input") { + if (i + 1 >= argc) { + std::cerr << "Missing value for " << arg << std::endl; + return 1; + } + json_input = argv[++i]; + continue; + } + + std::cerr << "Unknown argument provided: " << arg << std::endl; + return 1; + } + + if (show_help) { + print_help(); + return 0; + } + + if (list_scenarios) { + for (const auto& [name, _] : scenarios) { + std::cout << name << std::endl; + } + return 0; + } + + if (scenario_name.empty() || json_input.empty()) { + std::cerr << "Scenario name and input are required." << std::endl; + print_help(); + return 1; + } + + // Find and run scenario + auto it = scenarios.find(scenario_name); + if (it == scenarios.end()) { + std::cerr << "Unknown scenario: " << scenario_name << std::endl; + return 1; + } + + return it->second->run(json_input); + + } catch (const std::exception& e) { + std::cerr << "Fatal error: " << e.what() << std::endl; + return 1; + } +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp new file mode 100644 index 00000000000..4ecd7ab8f0d --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp @@ -0,0 +1,31 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "../framework/scenario_framework.h" + +#include + +std::shared_ptr persistency_scenario_group(); + +std::shared_ptr root_scenario_group() { + auto root = std::make_shared("root"); + root->add_group(persistency_scenario_group()); + return root; +} + +ScenarioRegistry create_scenario_registry() { + ScenarioRegistry registry; + const auto root = root_scenario_group(); + collect_scenarios_recursive(*root, registry); + return registry; +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp new file mode 100644 index 00000000000..de7ef5b024d --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp @@ -0,0 +1,24 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "../../framework/scenario_framework.h" + +#include + +std::shared_ptr make_multiple_kvs_per_app_scenario(); + +std::shared_ptr persistency_scenario_group() { + auto group = std::make_shared("persistency"); + group->add_scenario("multiple_kvs_per_app", make_multiple_kvs_per_app_scenario()); + return group; +} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp new file mode 100644 index 00000000000..c305073c3f1 --- /dev/null +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp @@ -0,0 +1,220 @@ +// ******************************************************************************* +// Copyright (c) 2026 Contributors to the Eclipse Foundation +// +// See the NOTICE file(s) distributed with this work for additional +// information regarding copyright ownership. +// +// This program and the accompanying materials are made available under the +// terms of the Apache License Version 2.0 which is available at +// https://www.apache.org/licenses/LICENSE-2.0 +// +// SPDX-License-Identifier: Apache-2.0 +// ******************************************************************************* + +#include "../../internals/persistency/kvs_instance.h" +#include "../../internals/persistency/kvs_parameters.h" +#include "../../framework/scenario_framework.h" + +#include "score/json/json_parser.h" + +#include +#include +#include +#include + +struct TestInput { + std::string key; + double value_1; + double value_2; + + static TestInput from_json(const std::string& input); +}; + +class MultipleKvsPerApp : public Scenario { +public: + std::string name() const override; + int run(const std::string& input) override; +}; + +std::string unix_seconds_string() { + const auto now = std::chrono::system_clock::now(); + const auto secs = std::chrono::duration_cast(now.time_since_epoch()).count(); + return std::to_string(secs); +} + +TestInput TestInput::from_json(const std::string& input_json) { + const score::json::JsonParser parser; + const auto root_any_res = parser.FromBuffer(input_json); + if (!root_any_res.has_value()) { + throw std::invalid_argument("Failed to parse scenario input JSON"); + } + + const auto root_object_res = root_any_res.value().As(); + if (!root_object_res.has_value()) { + throw std::invalid_argument("Scenario input root must be an object"); + } + + const auto& root = root_object_res.value().get(); + const auto test_it = root.find("test"); + if (test_it == root.end()) { + throw std::invalid_argument("Missing test section"); + } + + const auto test_object_res = test_it->second.As(); + if (!test_object_res.has_value()) { + throw std::invalid_argument("test section must be an object"); + } + + const auto& test = test_object_res.value().get(); + const auto key_it = test.find("key"); + const auto value_1_it = test.find("value_1"); + const auto value_2_it = test.find("value_2"); + if (key_it == test.end() || value_1_it == test.end() || value_2_it == test.end()) { + throw std::invalid_argument("Invalid test section format"); + } + + const auto key_res = key_it->second.As(); + const auto value_1_res = value_1_it->second.As(); + const auto value_2_res = value_2_it->second.As(); + if (!key_res.has_value() || !value_1_res.has_value() || !value_2_res.has_value()) { + throw std::invalid_argument("Invalid test section format"); + } + + TestInput parsed; + parsed.key = std::string(key_res.value()); + parsed.value_1 = value_1_res.value(); + parsed.value_2 = value_2_res.value(); + return parsed; +} + +std::string MultipleKvsPerApp::name() const { + return "multiple_kvs_per_app"; +} + +int MultipleKvsPerApp::run(const std::string& input) { + try { + // Parse parameters + KvsParameters params1 = KvsParameters::from_json_section(input, "kvs_parameters_1"); + KvsParameters params2 = KvsParameters::from_json_section(input, "kvs_parameters_2"); + TestInput test_input = TestInput::from_json(input); + + // ============================================================ + // PHASE 1: Create instances, set values, flush to disk + // ============================================================ + { + // Create first KVS instance + auto kvs1_opt = KvsInstance::create(params1); + if (!kvs1_opt) { + std::cerr << "Failed to create KVS instance 1" << std::endl; + return 1; + } + auto kvs1 = *kvs1_opt; + + // Create second KVS instance + auto kvs2_opt = KvsInstance::create(params2); + if (!kvs2_opt) { + std::cerr << "Failed to create KVS instance 2" << std::endl; + return 1; + } + auto kvs2 = *kvs2_opt; + + // Set values to both instances + if (!kvs1->set_value(test_input.key, test_input.value_1)) { + std::cerr << "Failed to set kvs1 value" << std::endl; + return 1; + } + + if (!kvs2->set_value(test_input.key, test_input.value_2)) { + std::cerr << "Failed to set kvs2 value" << std::endl; + return 1; + } + + // Flush both instances to disk + if (!kvs1->flush()) { + std::cerr << "Failed to flush kvs1" << std::endl; + return 1; + } + + if (!kvs2->flush()) { + std::cerr << "Failed to flush kvs2" << std::endl; + return 1; + } + + // Instances go out of scope here + } + + // ============================================================ + // PHASE 2: Reopen instances and verify persistence + // ============================================================ + { + // Reopen first KVS instance (from persisted file) + auto kvs1_opt = KvsInstance::create(params1); + if (!kvs1_opt) { + std::cerr << "Failed to reopen KVS instance 1" << std::endl; + return 1; + } + auto kvs1 = *kvs1_opt; + + // Reopen second KVS instance (from persisted file) + auto kvs2_opt = KvsInstance::create(params2); + if (!kvs2_opt) { + std::cerr << "Failed to reopen KVS instance 2" << std::endl; + return 1; + } + auto kvs2 = *kvs2_opt; + + // Read values back + auto value1_opt = kvs1->get_value(test_input.key); + if (!value1_opt) { + std::cerr << "Failed to read kvs1 value" << std::endl; + return 1; + } + double value1 = *value1_opt; + + auto value2_opt = kvs2->get_value(test_input.key); + if (!value2_opt) { + std::cerr << "Failed to read kvs2 value" << std::endl; + return 1; + } + double value2 = *value2_opt; + + // Match Rust tracing JSON shape expected by FIT log filters. + const std::string timestamp = unix_seconds_string(); + + std::cout << "{\"timestamp\":\"" << timestamp + << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" + << params1.instance_id.to_string() + << "\",\"key\":\"" << test_input.key + << "\",\"value\":" << value1 + << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" + << std::endl; + + std::cout << "{\"timestamp\":\"" << timestamp + << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" + << params2.instance_id.to_string() + << "\",\"key\":\"" << test_input.key + << "\",\"value\":" << value2 + << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" + << std::endl; + + if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params1)) { + std::cerr << "Failed to normalize KVS snapshot output for instance 1" << std::endl; + return 1; + } + if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params2)) { + std::cerr << "Failed to normalize KVS snapshot output for instance 2" << std::endl; + return 1; + } + } + + return 0; // SUCCESS + + } catch (const std::exception& e) { + std::cerr << "Error: " << e.what() << std::endl; + return 1; // FAILURE + } +} + +std::shared_ptr make_multiple_kvs_per_app_scenario() { + return std::make_shared(); +} From e2266e3a21baa411e5b83f462d3bb85c9cbfae23 Mon Sep 17 00:00:00 2001 From: subramaniak Date: Fri, 17 Apr 2026 10:26:40 +0000 Subject: [PATCH 02/10] Updated test scenario framework --- feature_integration_tests/test_cases/BUILD | 16 +- .../test_cases/conftest.py | 18 +- .../test_cases/fit_scenario.py | 15 +- .../persistency/test_multiple_kvs_per_app.py | 10 +- .../test_scenarios/cpp/BUILD | 5 +- .../test_scenarios/cpp/README.md | 10 +- .../cpp/src/framework/scenario_framework.h | 72 ------ .../internals/persistency/kvs_instance.cpp | 45 ---- .../src/internals/persistency/kvs_instance.h | 11 - .../internals/persistency/kvs_parameters.cpp | 2 +- .../internals/persistency/kvs_parameters.h | 8 +- .../test_scenarios/cpp/src/main.cpp | 93 +------- .../test_scenarios/cpp/src/scenarios/mod.cpp | 25 +- .../cpp/src/scenarios/persistency/mod.cpp | 24 -- .../persistency/multiple_kvs_per_app.cpp | 223 ++++++++---------- 15 files changed, 173 insertions(+), 404 deletions(-) delete mode 100644 feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h delete mode 100644 feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp diff --git a/feature_integration_tests/test_cases/BUILD b/feature_integration_tests/test_cases/BUILD index 6187084d008..7d5c088cee1 100644 --- a/feature_integration_tests/test_cases/BUILD +++ b/feature_integration_tests/test_cases/BUILD @@ -36,9 +36,8 @@ compile_pip_requirements( # Tests targets score_py_pytest( - name = "fit", - srcs = glob(["tests/**/*.py"]) + [ - ], + name = "fit_rust", + srcs = glob(["tests/**/*.py"], exclude = ["tests/persistency/**/*.py"]), args = [ "--traces=all", "--rust-target-path=$(rootpath //feature_integration_tests/test_scenarios/rust:rust_test_scenarios)", @@ -62,16 +61,25 @@ score_py_pytest( "tests/persistency/test_multiple_kvs_per_app.py", ], args = [ - "--scenario-target=cpp", "--traces=all", + "--rust-target-path=$(rootpath //feature_integration_tests/test_scenarios/rust:rust_test_scenarios)", "--cpp-target-path=$(rootpath //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios)", ], data = [ "conftest.py", "fit_scenario.py", "test_properties.py", + "//feature_integration_tests/test_scenarios/rust:rust_test_scenarios", "//feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios", ], pytest_config = "//:pyproject.toml", deps = all_requirements, ) + +test_suite( + name = "fit", + tests = [ + ":fit_rust", + ":fit_cpp", + ], +) diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index a3dd8545405..34406d37108 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -15,8 +15,6 @@ import pytest from testing_utils import BazelTools -FAILED_CONFIGS = [] - # Cmdline options def pytest_addoption(parser): @@ -30,12 +28,6 @@ def pytest_addoption(parser): '"all" - show all traces. ', ) - parser.addoption( - "--scenario-target", - type=str, - choices=["cpp", "rust"], - help="Test scenario target language (cpp or rust).", - ) parser.addoption( "--rust-target-name", type=str, @@ -87,9 +79,15 @@ def pytest_sessionstart(session): # Build Rust test scenarios. print("Building Rust test scenarios executable...") - cargo_tools = BazelTools(option_prefix="rust", build_timeout=build_timeout) + rust_tools = BazelTools(option_prefix="rust", build_timeout=build_timeout) rust_target_name = session.config.getoption("--rust-target-name") - cargo_tools.build(rust_target_name) + rust_tools.build(rust_target_name) + + # Build C++ test scenarios. + print("Building C++ test scenarios executable...") + cpp_tools = BazelTools(option_prefix="cpp", build_timeout=build_timeout) + cpp_target_name = session.config.getoption("--cpp-target-name") + cpp_tools.build(cpp_target_name) except Exception as e: pytest.exit(str(e), returncode=1) diff --git a/feature_integration_tests/test_cases/fit_scenario.py b/feature_integration_tests/test_cases/fit_scenario.py index 30dad9ee4b0..895d5d54623 100644 --- a/feature_integration_tests/test_cases/fit_scenario.py +++ b/feature_integration_tests/test_cases/fit_scenario.py @@ -11,8 +11,8 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* import shutil +from collections.abc import Generator from pathlib import Path -from typing import Generator import pytest from testing_utils import ( @@ -59,7 +59,7 @@ def temp_dir_common( dir_name = "-".join(parts) dir_path = tmp_path_factory.mktemp(dir_name, numbered=True) yield dir_path - shutil.rmtree(dir_path) + shutil.rmtree(dir_path, ignore_errors=True) class FitScenario(Scenario): @@ -69,8 +69,10 @@ class FitScenario(Scenario): @pytest.fixture(scope="class") def build_tools(self, request: pytest.FixtureRequest) -> BuildTools: - scenario_target = request.config.getoption("--scenario-target", default="rust") - return BazelTools(option_prefix=scenario_target) + # Use test parametrization when available; default to rust for non-parametrized tests. + if "version" in request.fixturenames: + return BazelTools(option_prefix=request.getfixturevalue("version")) + return BazelTools(option_prefix="rust") def expect_command_failure(self, *args, **kwargs) -> bool: """ @@ -88,9 +90,10 @@ def results( ) -> ScenarioResult: result = self._run_command(command, execution_timeout, args, kwargs) success = result.return_code == ResultCode.SUCCESS and not result.hang - if self.expect_command_failure() and success: + expect_failure = self.expect_command_failure() + if expect_failure and success: raise RuntimeError(f"Command execution succeeded unexpectedly: {result=}") - if not self.expect_command_failure() and not success: + if not expect_failure and not success: raise RuntimeError(f"Command execution failed unexpectedly: {result=}") return result diff --git a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py index a15b48085ad..3479933efaa 100644 --- a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py +++ b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py @@ -12,14 +12,17 @@ # ******************************************************************************* import json +from collections.abc import Generator from pathlib import Path -from typing import Any, Generator +from typing import Any import pytest from fit_scenario import FitScenario, temp_dir_common from test_properties import add_test_properties from testing_utils import LogContainer +pytestmark = pytest.mark.parametrize("version", ["rust", "cpp"], scope="class") + @add_test_properties( partially_verifies=["feat_req__persistency__multiple_kvs"], @@ -72,6 +75,11 @@ def test_config( "test": {"key": kvs_key, "value_1": kvs_value_1, "value_2": kvs_value_2}, } + @pytest.fixture(autouse=True) + def _bind_version_parameter(self, version: str) -> None: + # Keep class parametrization explicit for report splitting by rust/cpp. + _ = version + def test_logged_execution( self, kvs_key: str, diff --git a/feature_integration_tests/test_scenarios/cpp/BUILD b/feature_integration_tests/test_scenarios/cpp/BUILD index 76065728dd3..9776dd2df7f 100644 --- a/feature_integration_tests/test_scenarios/cpp/BUILD +++ b/feature_integration_tests/test_scenarios/cpp/BUILD @@ -35,16 +35,14 @@ cc_library( cc_library( name = "scenarios_lib", - hdrs = ["src/framework/scenario_framework.h"], srcs = [ "src/scenarios/mod.cpp", - "src/scenarios/persistency/mod.cpp", "src/scenarios/persistency/multiple_kvs_per_app.cpp", ], deps = [ ":kvs_instance", - ":kvs_parameters", "@score_baselibs//score/json", + "@score_test_scenarios//test_scenarios_cpp", ], ) @@ -55,5 +53,6 @@ cc_binary( visibility = ["//feature_integration_tests/test_cases:__pkg__"], deps = [ ":scenarios_lib", + "@score_test_scenarios//test_scenarios_cpp", ], ) diff --git a/feature_integration_tests/test_scenarios/cpp/README.md b/feature_integration_tests/test_scenarios/cpp/README.md index 120d85fb7e2..a4c624059c2 100644 --- a/feature_integration_tests/test_scenarios/cpp/README.md +++ b/feature_integration_tests/test_scenarios/cpp/README.md @@ -8,12 +8,8 @@ Current scope is persistency-focused scenarios. - src/main.cpp - CLI entry point for scenario execution. + - Uses shared test scenario framework from `@score_test_scenarios//test_scenarios_cpp`. - Supports listing scenarios and running a scenario with JSON input. -- src/framework/scenario_framework.h - - Minimal scenario framework: - - Scenario interface - - ScenarioGroup hierarchy - - Recursive scenario name flattening - src/internals/persistency/ - kvs_parameters.h/.cpp - Parses KVS config sections from JSON input. @@ -25,9 +21,7 @@ Current scope is persistency-focused scenarios. so Python FIT checks can keep strict value equality. - src/scenarios/ - mod.cpp - - Registers root scenario groups. - - persistency/mod.cpp - - Registers persistency scenarios. + - Registers root and persistency scenario groups. - persistency/multiple_kvs_per_app.cpp - Scenario implementation for multi-instance KVS validation. diff --git a/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h b/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h deleted file mode 100644 index c9a322db85d..00000000000 --- a/feature_integration_tests/test_scenarios/cpp/src/framework/scenario_framework.h +++ /dev/null @@ -1,72 +0,0 @@ -// ******************************************************************************* -// Copyright (c) 2026 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ******************************************************************************* - -#ifndef TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ -#define TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ - -#include -#include -#include -#include - -class Scenario { -public: - virtual ~Scenario() = default; - virtual std::string name() const = 0; - virtual int run(const std::string& input) = 0; -}; - -class ScenarioGroup { -public: - explicit ScenarioGroup(std::string name) - : name_(std::move(name)) {} - - void add_scenario(const std::string& name, std::shared_ptr scenario) { - scenarios_[name] = std::move(scenario); - } - - void add_group(std::shared_ptr group) { - groups_.push_back(std::move(group)); - } - - const std::string& name() const { return name_; } - const std::map>& scenarios() const { return scenarios_; } - const std::vector>& groups() const { return groups_; } - -private: - std::string name_; - std::map> scenarios_; - std::vector> groups_; -}; - -using ScenarioRegistry = std::map>; - -inline void collect_scenarios_recursive(const ScenarioGroup& group, - ScenarioRegistry& out, - const std::string& prefix = "") { - std::string next_prefix = prefix; - if (group.name() != "root") { - next_prefix = prefix.empty() ? group.name() : (prefix + "." + group.name()); - } - - for (const auto& [scenario_name, scenario] : group.scenarios()) { - const std::string full_name = next_prefix.empty() ? scenario_name : (next_prefix + "." + scenario_name); - out[full_name] = scenario; - } - - for (const auto& subgroup : group.groups()) { - collect_scenarios_recursive(*subgroup, out, next_prefix); - } -} - -#endif // TEST_SCENARIOS_CPP_SRC_FRAMEWORK_SCENARIO_FRAMEWORK_H_ diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp index b23dd2121f3..8bbc87ba744 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.cpp @@ -21,10 +21,8 @@ #include #include #include -#include #include #include -#include namespace { @@ -206,11 +204,6 @@ bool KvsInstance::set_value(const std::string& key, double value) { return static_cast(result); } -bool KvsInstance::set_value_u32(const std::string& key, std::uint32_t value) { - auto result = kvs_.set_value(key, score::mw::per::kvs::KvsValue{value}); - return static_cast(result); -} - std::optional KvsInstance::get_value(const std::string& key) { auto result = kvs_.get_value(key); if (!result) { @@ -235,44 +228,6 @@ std::optional KvsInstance::get_value(const std::string& key) { } } -std::optional KvsInstance::get_value_u32(const std::string& key) { - auto result = kvs_.get_value(key); - if (!result) { - return std::nullopt; - } - - const auto& stored = result.value(); - const auto& variant = stored.getValue(); - switch (stored.getType()) { - case score::mw::per::kvs::KvsValue::Type::u32: - return std::get(variant); - case score::mw::per::kvs::KvsValue::Type::i32: { - const auto value = std::get(variant); - return value >= 0 ? std::optional(static_cast(value)) : std::nullopt; - } - case score::mw::per::kvs::KvsValue::Type::u64: { - const auto value = std::get(variant); - return value <= static_cast(std::numeric_limits::max()) - ? std::optional(static_cast(value)) - : std::nullopt; - } - case score::mw::per::kvs::KvsValue::Type::i64: { - const auto value = std::get(variant); - return (value >= 0 && value <= static_cast(std::numeric_limits::max())) - ? std::optional(static_cast(value)) - : std::nullopt; - } - case score::mw::per::kvs::KvsValue::Type::f64: { - const auto value = std::get(variant); - return (value >= 0.0 && value <= static_cast(std::numeric_limits::max())) - ? std::optional(static_cast(value)) - : std::nullopt; - } - default: - return std::nullopt; - } -} - bool KvsInstance::flush() { auto result = kvs_.flush(); return static_cast(result); diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h index e3a7f2662d4..f5452c94e0d 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_instance.h @@ -33,23 +33,12 @@ class KvsInstance { // Set a value bool set_value(const std::string& key, double value); - // Set unsigned 32-bit value. - bool set_value_u32(const std::string& key, std::uint32_t value); - // Get a value std::optional get_value(const std::string& key); - // Get unsigned 32-bit value. - std::optional get_value_u32(const std::string& key); - // Flush to persistent storage bool flush(); - // Get instance parameters - const KvsParameters& get_parameters() const { - return params_; - } - private: KvsInstance(const KvsParameters& params, score::mw::per::kvs::Kvs&& kvs); diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp index f48c33bee27..d3f332cb413 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.cpp @@ -17,6 +17,7 @@ #include +namespace { KvsDefaults parse_kvs_defaults(const std::string& str) { if (str == "ignored") return KvsDefaults::Ignored; if (str == "optional") return KvsDefaults::Optional; @@ -31,7 +32,6 @@ KvsLoad parse_kvs_load(const std::string& str) { throw std::invalid_argument("Invalid KvsLoad mode: " + str); } -namespace { const score::json::Object& expect_object(const score::json::Any& value, const std::string& field_name) { const auto object_res = value.As(); if (!object_res.has_value()) { diff --git a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h index 9a7a6b815da..0ce5adf02c8 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h +++ b/feature_integration_tests/test_scenarios/cpp/src/internals/persistency/kvs_parameters.h @@ -14,8 +14,8 @@ #ifndef INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ #define INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ -#include #include +#include #include // Enum for KVS defaults mode @@ -35,9 +35,9 @@ enum class KvsLoad { // Instance ID wrapper struct InstanceId { uint32_t value; - + explicit InstanceId(uint32_t v = 0) : value(v) {} - + std::string to_string() const { return "InstanceId(" + std::to_string(value) + ")"; } @@ -55,4 +55,4 @@ struct KvsParameters { static KvsParameters from_json_section(const std::string& input, const std::string& section_name); }; -#endif // INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ +#endif // INTERNALS_PERSISTENCY_KVS_PARAMETERS_H_ diff --git a/feature_integration_tests/test_scenarios/cpp/src/main.cpp b/feature_integration_tests/test_scenarios/cpp/src/main.cpp index 88eabb798c7..37c9543d3e8 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/main.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/main.cpp @@ -11,94 +11,25 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -#include "framework/scenario_framework.h" - #include -#include - -ScenarioRegistry create_scenario_registry(); - -namespace { +#include -void print_help() { - std::cout << "Test scenario runner" << std::endl; - std::cout << "'-n', '--name' - test scenario name" << std::endl; - std::cout << "'-i', '--input' - test scenario input" << std::endl; - std::cout << "'-l', '--list-scenarios' - list available scenarios" << std::endl; - std::cout << "'-h', '--help' - show help" << std::endl; -} +#include +#include +#include -} // namespace +ScenarioGroup::Ptr root_scenario_group(); int main(int argc, char* argv[]) { try { - const ScenarioRegistry scenarios = create_scenario_registry(); - - std::string scenario_name; - std::string json_input; - bool list_scenarios = false; - bool show_help = false; - - for (int i = 1; i < argc; ++i) { - const std::string arg = argv[i]; - if (arg == "-l" || arg == "--list-scenarios") { - list_scenarios = true; - continue; - } - if (arg == "-h" || arg == "--help") { - show_help = true; - continue; - } - if (arg == "-n" || arg == "--name") { - if (i + 1 >= argc) { - std::cerr << "Missing value for " << arg << std::endl; - return 1; - } - scenario_name = argv[++i]; - continue; - } - if (arg == "-i" || arg == "--input") { - if (i + 1 >= argc) { - std::cerr << "Missing value for " << arg << std::endl; - return 1; - } - json_input = argv[++i]; - continue; - } - - std::cerr << "Unknown argument provided: " << arg << std::endl; - return 1; - } - - if (show_help) { - print_help(); - return 0; - } - - if (list_scenarios) { - for (const auto& [name, _] : scenarios) { - std::cout << name << std::endl; - } - return 0; - } - - if (scenario_name.empty() || json_input.empty()) { - std::cerr << "Scenario name and input are required." << std::endl; - print_help(); - return 1; - } - - // Find and run scenario - auto it = scenarios.find(scenario_name); - if (it == scenarios.end()) { - std::cerr << "Unknown scenario: " << scenario_name << std::endl; - return 1; - } - - return it->second->run(json_input); + std::vector raw_arguments{argv, argv + argc}; + TestContext test_context{root_scenario_group()}; + run_cli_app(raw_arguments, test_context); + return 0; } catch (const std::exception& e) { - std::cerr << "Fatal error: " << e.what() << std::endl; - return 1; + std::cerr << e.what() << std::endl; + // Match Rust panic return code. + return 101; } } diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp index 4ecd7ab8f0d..f1e6e183766 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/mod.cpp @@ -11,21 +11,22 @@ // SPDX-License-Identifier: Apache-2.0 // ******************************************************************************* -#include "../framework/scenario_framework.h" +#include -#include +#include -std::shared_ptr persistency_scenario_group(); +Scenario::Ptr make_multiple_kvs_per_app_scenario(); -std::shared_ptr root_scenario_group() { - auto root = std::make_shared("root"); - root->add_group(persistency_scenario_group()); - return root; +ScenarioGroup::Ptr persistency_scenario_group() { + return std::make_shared( + "persistency", + std::vector{make_multiple_kvs_per_app_scenario()}, + std::vector{}); } -ScenarioRegistry create_scenario_registry() { - ScenarioRegistry registry; - const auto root = root_scenario_group(); - collect_scenarios_recursive(*root, registry); - return registry; +ScenarioGroup::Ptr root_scenario_group() { + return std::make_shared( + "root", + std::vector{}, + std::vector{persistency_scenario_group()}); } diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp deleted file mode 100644 index de7ef5b024d..00000000000 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/mod.cpp +++ /dev/null @@ -1,24 +0,0 @@ -// ******************************************************************************* -// Copyright (c) 2026 Contributors to the Eclipse Foundation -// -// See the NOTICE file(s) distributed with this work for additional -// information regarding copyright ownership. -// -// This program and the accompanying materials are made available under the -// terms of the Apache License Version 2.0 which is available at -// https://www.apache.org/licenses/LICENSE-2.0 -// -// SPDX-License-Identifier: Apache-2.0 -// ******************************************************************************* - -#include "../../framework/scenario_framework.h" - -#include - -std::shared_ptr make_multiple_kvs_per_app_scenario(); - -std::shared_ptr persistency_scenario_group() { - auto group = std::make_shared("persistency"); - group->add_scenario("multiple_kvs_per_app", make_multiple_kvs_per_app_scenario()); - return group; -} diff --git a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp index c305073c3f1..227e6518a74 100644 --- a/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp +++ b/feature_integration_tests/test_scenarios/cpp/src/scenarios/persistency/multiple_kvs_per_app.cpp @@ -12,14 +12,13 @@ // ******************************************************************************* #include "../../internals/persistency/kvs_instance.h" -#include "../../internals/persistency/kvs_parameters.h" -#include "../../framework/scenario_framework.h" #include "score/json/json_parser.h" +#include + #include #include -#include #include struct TestInput { @@ -33,7 +32,7 @@ struct TestInput { class MultipleKvsPerApp : public Scenario { public: std::string name() const override; - int run(const std::string& input) override; + void run(const std::string& input) const override; }; std::string unix_seconds_string() { @@ -91,130 +90,110 @@ std::string MultipleKvsPerApp::name() const { return "multiple_kvs_per_app"; } -int MultipleKvsPerApp::run(const std::string& input) { - try { - // Parse parameters - KvsParameters params1 = KvsParameters::from_json_section(input, "kvs_parameters_1"); - KvsParameters params2 = KvsParameters::from_json_section(input, "kvs_parameters_2"); - TestInput test_input = TestInput::from_json(input); - - // ============================================================ - // PHASE 1: Create instances, set values, flush to disk - // ============================================================ - { - // Create first KVS instance - auto kvs1_opt = KvsInstance::create(params1); - if (!kvs1_opt) { - std::cerr << "Failed to create KVS instance 1" << std::endl; - return 1; - } - auto kvs1 = *kvs1_opt; - - // Create second KVS instance - auto kvs2_opt = KvsInstance::create(params2); - if (!kvs2_opt) { - std::cerr << "Failed to create KVS instance 2" << std::endl; - return 1; - } - auto kvs2 = *kvs2_opt; - - // Set values to both instances - if (!kvs1->set_value(test_input.key, test_input.value_1)) { - std::cerr << "Failed to set kvs1 value" << std::endl; - return 1; - } - - if (!kvs2->set_value(test_input.key, test_input.value_2)) { - std::cerr << "Failed to set kvs2 value" << std::endl; - return 1; - } - - // Flush both instances to disk - if (!kvs1->flush()) { - std::cerr << "Failed to flush kvs1" << std::endl; - return 1; - } - - if (!kvs2->flush()) { - std::cerr << "Failed to flush kvs2" << std::endl; - return 1; - } - - // Instances go out of scope here +void MultipleKvsPerApp::run(const std::string& input) const { + // Parse parameters + KvsParameters params1 = KvsParameters::from_json_section(input, "kvs_parameters_1"); + KvsParameters params2 = KvsParameters::from_json_section(input, "kvs_parameters_2"); + TestInput test_input = TestInput::from_json(input); + + // ============================================================ + // PHASE 1: Create instances, set values, flush to disk + // ============================================================ + { + // Create first KVS instance + auto kvs1_opt = KvsInstance::create(params1); + if (!kvs1_opt) { + throw std::runtime_error("Failed to create KVS instance 1"); + } + auto kvs1 = *kvs1_opt; + + // Create second KVS instance + auto kvs2_opt = KvsInstance::create(params2); + if (!kvs2_opt) { + throw std::runtime_error("Failed to create KVS instance 2"); + } + auto kvs2 = *kvs2_opt; + + // Set values to both instances + if (!kvs1->set_value(test_input.key, test_input.value_1)) { + throw std::runtime_error("Failed to set kvs1 value"); } - // ============================================================ - // PHASE 2: Reopen instances and verify persistence - // ============================================================ - { - // Reopen first KVS instance (from persisted file) - auto kvs1_opt = KvsInstance::create(params1); - if (!kvs1_opt) { - std::cerr << "Failed to reopen KVS instance 1" << std::endl; - return 1; - } - auto kvs1 = *kvs1_opt; - - // Reopen second KVS instance (from persisted file) - auto kvs2_opt = KvsInstance::create(params2); - if (!kvs2_opt) { - std::cerr << "Failed to reopen KVS instance 2" << std::endl; - return 1; - } - auto kvs2 = *kvs2_opt; - - // Read values back - auto value1_opt = kvs1->get_value(test_input.key); - if (!value1_opt) { - std::cerr << "Failed to read kvs1 value" << std::endl; - return 1; - } - double value1 = *value1_opt; - - auto value2_opt = kvs2->get_value(test_input.key); - if (!value2_opt) { - std::cerr << "Failed to read kvs2 value" << std::endl; - return 1; - } - double value2 = *value2_opt; - - // Match Rust tracing JSON shape expected by FIT log filters. - const std::string timestamp = unix_seconds_string(); - - std::cout << "{\"timestamp\":\"" << timestamp - << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" - << params1.instance_id.to_string() - << "\",\"key\":\"" << test_input.key - << "\",\"value\":" << value1 - << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" - << std::endl; - - std::cout << "{\"timestamp\":\"" << timestamp - << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" - << params2.instance_id.to_string() - << "\",\"key\":\"" << test_input.key - << "\",\"value\":" << value2 - << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" - << std::endl; - - if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params1)) { - std::cerr << "Failed to normalize KVS snapshot output for instance 1" << std::endl; - return 1; - } - if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params2)) { - std::cerr << "Failed to normalize KVS snapshot output for instance 2" << std::endl; - return 1; - } + if (!kvs2->set_value(test_input.key, test_input.value_2)) { + throw std::runtime_error("Failed to set kvs2 value"); } - return 0; // SUCCESS + // Flush both instances to disk + if (!kvs1->flush()) { + throw std::runtime_error("Failed to flush kvs1"); + } + + if (!kvs2->flush()) { + throw std::runtime_error("Failed to flush kvs2"); + } + + // Instances go out of scope here + } - } catch (const std::exception& e) { - std::cerr << "Error: " << e.what() << std::endl; - return 1; // FAILURE + // ============================================================ + // PHASE 2: Reopen instances and verify persistence + // ============================================================ + { + // Reopen first KVS instance (from persisted file) + auto kvs1_opt = KvsInstance::create(params1); + if (!kvs1_opt) { + throw std::runtime_error("Failed to reopen KVS instance 1"); + } + auto kvs1 = *kvs1_opt; + + // Reopen second KVS instance (from persisted file) + auto kvs2_opt = KvsInstance::create(params2); + if (!kvs2_opt) { + throw std::runtime_error("Failed to reopen KVS instance 2"); + } + auto kvs2 = *kvs2_opt; + + // Read values back + auto value1_opt = kvs1->get_value(test_input.key); + if (!value1_opt) { + throw std::runtime_error("Failed to read kvs1 value"); + } + double value1 = *value1_opt; + + auto value2_opt = kvs2->get_value(test_input.key); + if (!value2_opt) { + throw std::runtime_error("Failed to read kvs2 value"); + } + double value2 = *value2_opt; + + // Match Rust tracing JSON shape expected by FIT log filters. + const std::string timestamp = unix_seconds_string(); + + std::cout << "{\"timestamp\":\"" << timestamp + << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" + << params1.instance_id.to_string() + << "\",\"key\":\"" << test_input.key + << "\",\"value\":" << value1 + << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" + << std::endl; + + std::cout << "{\"timestamp\":\"" << timestamp + << "\",\"level\":\"INFO\",\"fields\":{\"instance\":\"" + << params2.instance_id.to_string() + << "\",\"key\":\"" << test_input.key + << "\",\"value\":" << value2 + << "},\"target\":\"cpp_test_scenarios::scenarios::persistency::multiple_kvs_per_app\",\"threadId\":\"ThreadId(1)\"}" + << std::endl; + + if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params1)) { + throw std::runtime_error("Failed to normalize KVS snapshot output for instance 1"); + } + if (!KvsInstance::normalize_snapshot_file_to_rust_envelope(params2)) { + throw std::runtime_error("Failed to normalize KVS snapshot output for instance 2"); + } } } -std::shared_ptr make_multiple_kvs_per_app_scenario() { - return std::make_shared(); +Scenario::Ptr make_multiple_kvs_per_app_scenario() { + return Scenario::Ptr{new MultipleKvsPerApp{}}; } From 086d6b5a4f350259657989aee117888d862c31dd Mon Sep 17 00:00:00 2001 From: subramaniak Date: Mon, 20 Apr 2026 06:34:25 +0000 Subject: [PATCH 03/10] Updated visibility for cpp_test_scenarios --- feature_integration_tests/test_cases/BUILD | 4 +- .../test_cases/fit_scenario.py | 2 +- .../persistency/test_multiple_kvs_per_app.py | 8 +--- .../test_scenarios/cpp/BUILD | 47 ++++--------------- 4 files changed, 14 insertions(+), 47 deletions(-) diff --git a/feature_integration_tests/test_cases/BUILD b/feature_integration_tests/test_cases/BUILD index 7d5c088cee1..e96ae6e6852 100644 --- a/feature_integration_tests/test_cases/BUILD +++ b/feature_integration_tests/test_cases/BUILD @@ -37,16 +37,14 @@ compile_pip_requirements( # Tests targets score_py_pytest( name = "fit_rust", - srcs = glob(["tests/**/*.py"], exclude = ["tests/persistency/**/*.py"]), + srcs = glob(["tests/**/*.py"]), args = [ "--traces=all", - "--rust-target-path=$(rootpath //feature_integration_tests/test_scenarios/rust:rust_test_scenarios)", ], data = [ "conftest.py", "fit_scenario.py", "test_properties.py", - "//feature_integration_tests/test_scenarios/rust:rust_test_scenarios", ], env = { "RUST_BACKTRACE": "1", diff --git a/feature_integration_tests/test_cases/fit_scenario.py b/feature_integration_tests/test_cases/fit_scenario.py index 895d5d54623..3452745498e 100644 --- a/feature_integration_tests/test_cases/fit_scenario.py +++ b/feature_integration_tests/test_cases/fit_scenario.py @@ -59,7 +59,7 @@ def temp_dir_common( dir_name = "-".join(parts) dir_path = tmp_path_factory.mktemp(dir_name, numbered=True) yield dir_path - shutil.rmtree(dir_path, ignore_errors=True) + shutil.rmtree(dir_path) class FitScenario(Scenario): diff --git a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py index 3479933efaa..bce953b65ca 100644 --- a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py +++ b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py @@ -54,8 +54,9 @@ def kvs_value_2(self) -> float: def temp_dir( self, tmp_path_factory: pytest.TempPathFactory, + version: str, ) -> Generator[Path, None, None]: - yield from temp_dir_common(tmp_path_factory, self.__class__.__name__) + yield from temp_dir_common(tmp_path_factory, self.__class__.__name__, version) @pytest.fixture(scope="class") def test_config( @@ -75,11 +76,6 @@ def test_config( "test": {"key": kvs_key, "value_1": kvs_value_1, "value_2": kvs_value_2}, } - @pytest.fixture(autouse=True) - def _bind_version_parameter(self, version: str) -> None: - # Keep class parametrization explicit for report splitting by rust/cpp. - _ = version - def test_logged_execution( self, kvs_key: str, diff --git a/feature_integration_tests/test_scenarios/cpp/BUILD b/feature_integration_tests/test_scenarios/cpp/BUILD index 9776dd2df7f..dd8a0f7cd22 100644 --- a/feature_integration_tests/test_scenarios/cpp/BUILD +++ b/feature_integration_tests/test_scenarios/cpp/BUILD @@ -11,48 +11,21 @@ # SPDX-License-Identifier: Apache-2.0 # ******************************************************************************* -load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library") - -cc_library( - name = "kvs_parameters", - hdrs = ["src/internals/persistency/kvs_parameters.h"], - srcs = ["src/internals/persistency/kvs_parameters.cpp"], - deps = [ - "@score_baselibs//score/json", - ], -) - -cc_library( - name = "kvs_instance", - hdrs = ["src/internals/persistency/kvs_instance.h"], - srcs = ["src/internals/persistency/kvs_instance.cpp"], - deps = [ - ":kvs_parameters", - "@score_baselibs//score/mw/log:console_only_backend", - "@score_persistency//:kvs_cpp", - ], -) - -cc_library( - name = "scenarios_lib", - srcs = [ - "src/scenarios/mod.cpp", - "src/scenarios/persistency/multiple_kvs_per_app.cpp", - ], - deps = [ - ":kvs_instance", - "@score_baselibs//score/json", - "@score_test_scenarios//test_scenarios_cpp", - ], -) +load("@rules_cc//cc:defs.bzl", "cc_binary") cc_binary( name = "cpp_test_scenarios", - srcs = ["src/main.cpp"], + srcs = glob([ + "src/**/*.cpp", + "src/**/*.h", + ]), + includes = ["src/internals/persistency"], tags = ["manual"], - visibility = ["//feature_integration_tests/test_cases:__pkg__"], + visibility = ["//visibility:public"], deps = [ - ":scenarios_lib", + "@score_baselibs//score/json", + "@score_baselibs//score/mw/log:console_only_backend", + "@score_persistency//:kvs_cpp", "@score_test_scenarios//test_scenarios_cpp", ], ) From 5cab23af143cae4e0da3ad7aa923608567ccad78 Mon Sep 17 00:00:00 2001 From: subramaniak Date: Mon, 20 Apr 2026 09:17:45 +0000 Subject: [PATCH 04/10] Updated markers list in pyproject.toml --- feature_integration_tests/test_cases/BUILD | 17 ++++++++++------- .../test_cases/conftest.py | 14 +++++++++++++- .../persistency/test_multiple_kvs_per_app.py | 2 +- .../test_scenarios/cpp/BUILD | 2 +- .../test_scenarios/cpp/README.md | 3 --- pyproject.toml | 2 ++ 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/feature_integration_tests/test_cases/BUILD b/feature_integration_tests/test_cases/BUILD index e96ae6e6852..129006055dd 100644 --- a/feature_integration_tests/test_cases/BUILD +++ b/feature_integration_tests/test_cases/BUILD @@ -15,10 +15,10 @@ load("@rules_python//python:pip.bzl", "compile_pip_requirements") load("@score_tooling//python_basics:defs.bzl", "score_py_pytest") # In order to update the requirements, change the `requirements.txt` file and run: -# `bazel run //feature_integration_tests/python_test_cases:requirements.update`. +# `bazel run //feature_integration_tests/test_cases:requirements.update`. # This will update the `requirements.txt.lock` file. # To upgrade all dependencies to their latest versions, run: -# `bazel run //feature_integration_tests/python_test_cases:requirements.update -- --upgrade`. +# `bazel run //feature_integration_tests/test_cases:requirements.update -- --upgrade`. compile_pip_requirements( name = "requirements", srcs = [ @@ -39,12 +39,15 @@ score_py_pytest( name = "fit_rust", srcs = glob(["tests/**/*.py"]), args = [ + "-m rust", "--traces=all", + "--rust-target-path=$(rootpath //feature_integration_tests/test_scenarios/rust:rust_test_scenarios)", ], data = [ "conftest.py", "fit_scenario.py", "test_properties.py", + "//feature_integration_tests/test_scenarios/rust:rust_test_scenarios", ], env = { "RUST_BACKTRACE": "1", @@ -55,21 +58,21 @@ score_py_pytest( score_py_pytest( name = "fit_cpp", - srcs = [ - "tests/persistency/test_multiple_kvs_per_app.py", - ], + srcs = glob(["tests/**/*.py"]), args = [ + "-m cpp", "--traces=all", - "--rust-target-path=$(rootpath //feature_integration_tests/test_scenarios/rust:rust_test_scenarios)", "--cpp-target-path=$(rootpath //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios)", ], data = [ "conftest.py", "fit_scenario.py", "test_properties.py", - "//feature_integration_tests/test_scenarios/rust:rust_test_scenarios", "//feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios", ], + env = { + "RUST_BACKTRACE": "1", + }, pytest_config = "//:pyproject.toml", deps = all_requirements, ) diff --git a/feature_integration_tests/test_cases/conftest.py b/feature_integration_tests/test_cases/conftest.py index 34406d37108..662b7210943 100644 --- a/feature_integration_tests/test_cases/conftest.py +++ b/feature_integration_tests/test_cases/conftest.py @@ -31,7 +31,7 @@ def pytest_addoption(parser): parser.addoption( "--rust-target-name", type=str, - default="//feature_integration_tests/rust_test_scenarios:rust_test_scenarios", + default="//feature_integration_tests/test_scenarios/rust:rust_test_scenarios", help="Rust test scenario executable target.", ) parser.addoption( @@ -70,6 +70,18 @@ def pytest_addoption(parser): # Hooks +def pytest_collection_modifyitems(items: list[pytest.Function]): + for item in items: + # Automatically mark tests parametrized with 'version' as 'cpp' or 'rust'. + # This allows -m cpp / -m rust to select the correct variant in each target. + if hasattr(item, "callspec") and "version" in getattr(item.callspec, "params", {}): + version = item.callspec.params["version"] + if version == "cpp": + item.add_marker(pytest.mark.cpp) + elif version == "rust": + item.add_marker(pytest.mark.rust) + + @pytest.hookimpl(tryfirst=True) def pytest_sessionstart(session): try: diff --git a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py index bce953b65ca..52d4031ad61 100644 --- a/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py +++ b/feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py @@ -1,5 +1,5 @@ # ******************************************************************************* -# Copyright (c) 2025 Contributors to the Eclipse Foundation +# Copyright (c) 2026 Contributors to the Eclipse Foundation # # See the NOTICE file(s) distributed with this work for additional # information regarding copyright ownership. diff --git a/feature_integration_tests/test_scenarios/cpp/BUILD b/feature_integration_tests/test_scenarios/cpp/BUILD index dd8a0f7cd22..0ea94ac72e4 100644 --- a/feature_integration_tests/test_scenarios/cpp/BUILD +++ b/feature_integration_tests/test_scenarios/cpp/BUILD @@ -19,7 +19,7 @@ cc_binary( "src/**/*.cpp", "src/**/*.h", ]), - includes = ["src/internals/persistency"], + includes = ["src"], tags = ["manual"], visibility = ["//visibility:public"], deps = [ diff --git a/feature_integration_tests/test_scenarios/cpp/README.md b/feature_integration_tests/test_scenarios/cpp/README.md index a4c624059c2..2316e1db9ee 100644 --- a/feature_integration_tests/test_scenarios/cpp/README.md +++ b/feature_integration_tests/test_scenarios/cpp/README.md @@ -29,9 +29,6 @@ Current scope is persistency-focused scenarios. Defined in BUILD: -- kvs_parameters (cc_library) -- kvs_instance (cc_library) -- scenarios_lib (cc_library) - cpp_test_scenarios (cc_binary) ## Available Scenario diff --git a/pyproject.toml b/pyproject.toml index 469533f41bf..6d78d2c63e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,8 @@ junit_family = "xunit1" markers = [ "metadata", "test_properties(dict): Add custom properties to test XML output", + "cpp", + "rust", ] filterwarnings = [ 'ignore:record_property is incompatible with junit_family:pytest.PytestWarning', From 44c4ba2018a670777d86bf0ef44b605aea97167f Mon Sep 17 00:00:00 2001 From: subramaniak Date: Mon, 20 Apr 2026 09:32:33 +0000 Subject: [PATCH 05/10] fix buildifier formatting in test_cases BUILD --- feature_integration_tests/test_cases/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_integration_tests/test_cases/BUILD b/feature_integration_tests/test_cases/BUILD index 129006055dd..51378eeae80 100644 --- a/feature_integration_tests/test_cases/BUILD +++ b/feature_integration_tests/test_cases/BUILD @@ -80,7 +80,7 @@ score_py_pytest( test_suite( name = "fit", tests = [ - ":fit_rust", ":fit_cpp", + ":fit_rust", ], ) From 89e3e0ac5d3dbd85c05d0bbd14f4bedad696b5bc Mon Sep 17 00:00:00 2001 From: subramaniak Date: Mon, 20 Apr 2026 10:01:53 +0000 Subject: [PATCH 06/10] Updated dep for BUILD --- feature_integration_tests/test_scenarios/cpp/BUILD | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_integration_tests/test_scenarios/cpp/BUILD b/feature_integration_tests/test_scenarios/cpp/BUILD index 0ea94ac72e4..3f06998b7f6 100644 --- a/feature_integration_tests/test_scenarios/cpp/BUILD +++ b/feature_integration_tests/test_scenarios/cpp/BUILD @@ -24,7 +24,7 @@ cc_binary( visibility = ["//visibility:public"], deps = [ "@score_baselibs//score/json", - "@score_baselibs//score/mw/log:console_only_backend", + "@score_baselibs//score/mw/log:backend_stub_testutil", "@score_persistency//:kvs_cpp", "@score_test_scenarios//test_scenarios_cpp", ], From 98771df2e4baf4dee0835ee1d8d32d562b8730a3 Mon Sep 17 00:00:00 2001 From: subramaniak Date: Tue, 21 Apr 2026 04:20:28 +0000 Subject: [PATCH 07/10] Updated README.md --- feature_integration_tests/README.md | 26 +++-- .../test_scenarios/cpp/README.md | 110 ------------------ 2 files changed, 15 insertions(+), 121 deletions(-) delete mode 100644 feature_integration_tests/test_scenarios/cpp/README.md diff --git a/feature_integration_tests/README.md b/feature_integration_tests/README.md index 2cb213a4261..b0b0fbe1c54 100644 --- a/feature_integration_tests/README.md +++ b/feature_integration_tests/README.md @@ -1,6 +1,6 @@ # Feature Integration Tests -This directory contains Feature Integration Tests for the S-CORE project. It includes both Python test cases and Rust test scenarios to validate features work together. +This directory contains Feature Integration Tests for the S-CORE project. It includes Python test cases that orchestrate test scenarios implemented in Rust and C++ to validate that features work together correctly. ## Structure @@ -9,32 +9,36 @@ This directory contains Feature Integration Tests for the S-CORE project. It inc - `fit_scenario.py` — Base scenario class - `requirements.txt` — Python dependencies - `BUILD` — Bazel build and test definitions - - `tests/` — Test cases (e.g., orchestration with persistency) -- `test_scenarios/` — Location of test scenarios - - `rust/` — Rust-based integration test scenarios - - `src/` — Rust source code for test scenarios - - `BUILD` — Bazel build definitions + - `tests/` — Test cases organized by feature area +- `test_scenarios/` — Test scenario implementations + - `rust/` — Rust-based test scenarios + - `cpp/` — C++-based test scenarios +- `configs/` — Configuration files for test execution ## Running Tests ### Python Test Cases -Python tests are managed with Bazel and Pytest. To run the main test target: +Python tests are managed with Bazel and Pytest. To run all integration tests: ```sh bazel test //feature_integration_tests/test_cases:fit ``` -### Rust Test Scenarios - -Rust test scenarios are defined in `test_scenarios/rust/src/scenarios`. Build and run them using Bazel: +To run specific test suites: ```sh -bazel build //feature_integration_tests/test_scenarios/rust:rust_test_scenarios +bazel test //feature_integration_tests/test_cases:fit_rust +bazel test //feature_integration_tests/test_cases:fit_cpp ``` +### Test Scenarios + +Test scenarios can be listed and run directly for debugging: + ```sh bazel run //feature_integration_tests/test_scenarios/rust:rust_test_scenarios -- --list-scenarios +bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios ``` ## Updating Python Requirements diff --git a/feature_integration_tests/test_scenarios/cpp/README.md b/feature_integration_tests/test_scenarios/cpp/README.md deleted file mode 100644 index 2316e1db9ee..00000000000 --- a/feature_integration_tests/test_scenarios/cpp/README.md +++ /dev/null @@ -1,110 +0,0 @@ -# C++ Feature Integration Test Scenarios - -This directory contains the C++ implementation of feature integration test scenarios. - -Current scope is persistency-focused scenarios. - -## Structure - -- src/main.cpp - - CLI entry point for scenario execution. - - Uses shared test scenario framework from `@score_test_scenarios//test_scenarios_cpp`. - - Supports listing scenarios and running a scenario with JSON input. -- src/internals/persistency/ - - kvs_parameters.h/.cpp - - Parses KVS config sections from JSON input. - - kvs_instance.h/.cpp - - Wraps C++ KVS builder/API operations. - - Provides typed set/get helpers. - - Normalizes snapshot JSON to Rust-compatible top-level envelope. - - Canonicalizes noisy f64 literals in snapshots (for example 111.099998 -> 111.1) - so Python FIT checks can keep strict value equality. -- src/scenarios/ - - mod.cpp - - Registers root and persistency scenario groups. - - persistency/multiple_kvs_per_app.cpp - - Scenario implementation for multi-instance KVS validation. - -## Build Targets - -Defined in BUILD: - -- cpp_test_scenarios (cc_binary) - -## Available Scenario - -- persistency.multiple_kvs_per_app - -## Scenario Contract - -persistency.multiple_kvs_per_app expects JSON with: - -- kvs_parameters_1.kvs_parameters -- kvs_parameters_2.kvs_parameters -- test - - key - - value_1 - - value_2 - -Behavior: - -1. Creates two KVS instances with different instance IDs. -2. Writes different values for the same key. -3. Flushes both instances. -4. Reopens both instances. -5. Reads values back and emits INFO logs. -6. Normalizes written snapshot files to Rust-compatible envelope format. -7. Canonicalizes persisted f64 literals that carry float-noise artifacts. - -## Notes - -- C++ persistency snapshots may expose float-noise literals even for semantic decimal inputs. - The scenario adapter normalizes these values during snapshot envelope conversion so - FIT assertions in Python can remain strict (`==`) instead of using tolerances. - -## Traceability - -The C++ scenario `persistency.multiple_kvs_per_app` is validated by the FIT test module: - -- `feature_integration_tests/test_cases/tests/persistency/test_multiple_kvs_per_app.py` - -Verification metadata in that test currently declares: - -- `partially_verifies = ["feat_req__persistency__multiple_kvs"]` -- `test_type = "requirements-based"` -- `derivation_technique = "requirements-analysis"` - -This keeps the scenario name, test input contract, and requirement linkage aligned across -the C++ scenario runner and the Python FIT layer. - -## Usage - -Build: - -```sh -bazel build //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -``` - -List scenarios: - -```sh -bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios -``` - -Run scenario: - -```sh -bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- \ - --name persistency.multiple_kvs_per_app \ - --input '{ - "kvs_parameters_1": {"kvs_parameters": {"instance_id": 1, "dir": "/tmp/cpp_fit"}}, - "kvs_parameters_2": {"kvs_parameters": {"instance_id": 2, "dir": "/tmp/cpp_fit"}}, - "test": {"key": "number", "value_1": 111.1, "value_2": 222.2} - }' -``` - -Run the C++ persistency-focused FIT target: - -```sh -bazel test //feature_integration_tests/test_cases:fit_cpp -``` From f689cf46f85d5c589df9b8ee2b0e151693057e88 Mon Sep 17 00:00:00 2001 From: subramaniak Date: Wed, 22 Apr 2026 02:45:15 +0000 Subject: [PATCH 08/10] Updated README.md with config flags --- feature_integration_tests/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature_integration_tests/README.md b/feature_integration_tests/README.md index b0b0fbe1c54..cc8b01fbaf6 100644 --- a/feature_integration_tests/README.md +++ b/feature_integration_tests/README.md @@ -29,7 +29,7 @@ To run specific test suites: ```sh bazel test //feature_integration_tests/test_cases:fit_rust -bazel test //feature_integration_tests/test_cases:fit_cpp +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp ``` ### Test Scenarios @@ -38,7 +38,7 @@ Test scenarios can be listed and run directly for debugging: ```sh bazel run //feature_integration_tests/test_scenarios/rust:rust_test_scenarios -- --list-scenarios -bazel run //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios +bazel run --config=linux-x86_64 //feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios -- --list-scenarios ``` ## Updating Python Requirements From dedc6b09e9e26325b74ae4c97f56672a79dda0ec Mon Sep 17 00:00:00 2001 From: subramaniak Date: Wed, 22 Apr 2026 06:50:40 +0000 Subject: [PATCH 09/10] Updated README.md with ITF Tests --- feature_integration_tests/README.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/feature_integration_tests/README.md b/feature_integration_tests/README.md index cc8b01fbaf6..3764c7c7c1b 100644 --- a/feature_integration_tests/README.md +++ b/feature_integration_tests/README.md @@ -13,16 +13,20 @@ This directory contains Feature Integration Tests for the S-CORE project. It inc - `test_scenarios/` — Test scenario implementations - `rust/` — Rust-based test scenarios - `cpp/` — C++-based test scenarios -- `configs/` — Configuration files for test execution +- `itf/` — Integration Test Framework tests (run on QEMU targets) + - `test_showcases.py` — Showcase validation tests + - `test_remote_logging.py` — Remote logging tests + - `test_ssh.py` — SSH connectivity tests +- `configs/` — Configuration files for ITF execution (DLT, QEMU bridge, etc.) ## Running Tests -### Python Test Cases +### Python Test Cases (scenario-based FIT) Python tests are managed with Bazel and Pytest. To run all integration tests: ```sh -bazel test //feature_integration_tests/test_cases:fit +bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit ``` To run specific test suites: @@ -32,6 +36,14 @@ bazel test //feature_integration_tests/test_cases:fit_rust bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp ``` +### ITF Tests (QEMU-based) + +ITF tests run on a QEMU target and require the `itf-qnx-x86_64` config: + +```sh +bazel test --config=itf-qnx-x86_64 //feature_integration_tests/itf:... +``` + ### Test Scenarios Test scenarios can be listed and run directly for debugging: From ca702e83bfcea30d34aea6213050b5133e93bf8a Mon Sep 17 00:00:00 2001 From: subramaniak Date: Wed, 22 Apr 2026 15:36:43 +0000 Subject: [PATCH 10/10] Updated README with ITF section and correct bazel commands --- feature_integration_tests/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature_integration_tests/README.md b/feature_integration_tests/README.md index 3764c7c7c1b..a68a1f9c492 100644 --- a/feature_integration_tests/README.md +++ b/feature_integration_tests/README.md @@ -41,7 +41,7 @@ bazel test --config=linux-x86_64 //feature_integration_tests/test_cases:fit_cpp ITF tests run on a QEMU target and require the `itf-qnx-x86_64` config: ```sh -bazel test --config=itf-qnx-x86_64 //feature_integration_tests/itf:... +bazel test --config=itf-qnx-x86_64 //feature_integration_tests/itf ``` ### Test Scenarios