Skip to content

Commit eeda23b

Browse files
Add FIT persistency scenarios: supported datatypes (#220)
* add persistency FIT tests for datatype support, default values, and reset-to-default * apply rustfmt formatting to persistency Rust scenarios * Updated datatypes and default values scenario * Added PersistencyScenario for kvsinstance * feat: add implementation for TestGetDefaultValue, TestSelectiveReset, and TestFullReset * Updated requirement details for test scenarios * Updated Default values assertion on logs * Add hash verification for all test scenarios of modified json
1 parent 8b3fe49 commit eeda23b

25 files changed

Lines changed: 3238 additions & 15 deletions

feature_integration_tests/test_cases/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ score_py_pytest(
4646
data = [
4747
"conftest.py",
4848
"fit_scenario.py",
49+
"persistency_scenario.py",
4950
"test_properties.py",
5051
"//feature_integration_tests/test_scenarios/rust:rust_test_scenarios",
5152
],
@@ -67,6 +68,7 @@ score_py_pytest(
6768
data = [
6869
"conftest.py",
6970
"fit_scenario.py",
71+
"persistency_scenario.py",
7072
"test_properties.py",
7173
"//feature_integration_tests/test_scenarios/cpp:cpp_test_scenarios",
7274
],

feature_integration_tests/test_cases/fit_scenario.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
# SPDX-License-Identifier: Apache-2.0
1212
# *******************************************************************************
1313
import shutil
14-
from collections.abc import Generator
1514
from pathlib import Path
15+
from typing import Generator
1616

1717
import pytest
1818
from testing_utils import (
@@ -64,7 +64,7 @@ def temp_dir_common(
6464

6565
class FitScenario(Scenario):
6666
"""
67-
CIT test scenario definition.
67+
FIT test scenario definition.
6868
"""
6969

7070
@pytest.fixture(scope="class")
@@ -90,10 +90,9 @@ def results(
9090
) -> ScenarioResult:
9191
result = self._run_command(command, execution_timeout, args, kwargs)
9292
success = result.return_code == ResultCode.SUCCESS and not result.hang
93-
expect_failure = self.expect_command_failure()
94-
if expect_failure and success:
93+
if self.expect_command_failure() and success:
9594
raise RuntimeError(f"Command execution succeeded unexpectedly: {result=}")
96-
if not expect_failure and not success:
95+
if not self.expect_command_failure() and not success:
9796
raise RuntimeError(f"Command execution failed unexpectedly: {result=}")
9897
return result
9998

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# *******************************************************************************
2+
# Copyright (c) 2026 Contributors to the Eclipse Foundation
3+
#
4+
# See the NOTICE file(s) distributed with this work for additional
5+
# information regarding copyright ownership.
6+
#
7+
# This program and the accompanying materials are made available under the
8+
# terms of the Apache License Version 2.0 which is available at
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# SPDX-License-Identifier: Apache-2.0
12+
# *******************************************************************************
13+
"""
14+
Helpers and base scenario class for persistency feature integration tests.
15+
16+
``create_kvs_defaults_file`` and ``read_kvs_snapshot`` provide the file-system
17+
operations that test methods use to set up and inspect KVS state.
18+
``PersistencyScenario`` is a :class:`FitScenario` subclass that supplies the
19+
shared ``temp_dir`` fixture so individual test classes do not have to duplicate it.
20+
"""
21+
22+
import json
23+
from collections.abc import Generator
24+
from pathlib import Path
25+
from zlib import adler32
26+
27+
import pytest
28+
from fit_scenario import FitScenario, temp_dir_common
29+
30+
31+
def create_kvs_defaults_file(dir_path: Path, instance_id: int, values: dict) -> Path:
32+
"""
33+
Create a KVS defaults JSON file and matching hash file at conventional paths.
34+
35+
KVS expects defaults at: {dir}/kvs_{instance_id}_default.json
36+
and the hash at: {dir}/kvs_{instance_id}_default.hash
37+
38+
The JSON format is: {"key": {"t": "type_tag", "v": value}, ...}
39+
The hash is adler32 of the JSON string, written as 4 big-endian bytes.
40+
41+
Parameters
42+
----------
43+
dir_path : Path
44+
Working directory for the KVS instance.
45+
instance_id : int
46+
KVS instance identifier.
47+
values : dict
48+
Mapping of key -> (type_tag, value), e.g. {"my_key": ("f64", 1.0)}.
49+
50+
Returns
51+
-------
52+
Path
53+
Path to the created JSON defaults file.
54+
"""
55+
json_path = dir_path / f"kvs_{instance_id}_default.json"
56+
hash_path = dir_path / f"kvs_{instance_id}_default.hash"
57+
58+
data = {key: {"t": type_tag, "v": val} for key, (type_tag, val) in values.items()}
59+
json_str = json.dumps(data)
60+
61+
json_path.write_text(json_str)
62+
hash_path.write_bytes(adler32(json_str.encode()).to_bytes(length=4, byteorder="big"))
63+
return json_path
64+
65+
66+
def read_kvs_snapshot(dir_path: Path, instance_id: int, snapshot_id: int = 0) -> dict:
67+
"""
68+
Read and parse the KVS snapshot JSON for a given instance.
69+
70+
Supports both the Rust/normalized envelope format {"t":"obj","v":{...}}
71+
and the raw C++ format {key: {...}}. Returns the inner key -> tagged-value mapping.
72+
73+
Parameters
74+
----------
75+
dir_path : Path
76+
Working directory containing the KVS snapshot files.
77+
instance_id : int
78+
KVS instance identifier used in the filename convention.
79+
snapshot_id : int, optional
80+
Snapshot sequence number (default 0).
81+
82+
Returns
83+
-------
84+
dict
85+
Mapping of key -> tagged-value dict, e.g. {"mykey": {"t": "f64", "v": 1.0}}.
86+
"""
87+
path = dir_path / f"kvs_{instance_id}_{snapshot_id}.json"
88+
data = json.loads(path.read_text())
89+
if isinstance(data, dict) and data.get("t") == "obj" and "v" in data:
90+
return data["v"]
91+
return data
92+
93+
94+
def verify_kvs_snapshot_hash(dir_path: Path, instance_id: int, snapshot_id: int = 0) -> None:
95+
"""
96+
Assert that the snapshot hash file content matches the Adler-32 of the JSON file.
97+
98+
After ``normalize_snapshot_file_to_rust_envelope`` rewrites the JSON, the
99+
companion ``.hash`` file must also be rewritten. This helper detects any
100+
mismatch between the two, catching stale hashes introduced by manual or
101+
tool-driven JSON modifications.
102+
103+
Parameters
104+
----------
105+
dir_path : Path
106+
Working directory containing the KVS snapshot files.
107+
instance_id : int
108+
KVS instance identifier used in the filename convention.
109+
snapshot_id : int, optional
110+
Snapshot sequence number (default 0).
111+
"""
112+
json_path = dir_path / f"kvs_{instance_id}_{snapshot_id}.json"
113+
hash_path = dir_path / f"kvs_{instance_id}_{snapshot_id}.hash"
114+
json_bytes = json_path.read_bytes()
115+
expected = adler32(json_bytes).to_bytes(4, byteorder="big")
116+
actual = hash_path.read_bytes()
117+
assert actual == expected, (
118+
f"Hash mismatch for kvs_{instance_id}_{snapshot_id}: "
119+
f"hash file contains {actual.hex()} but Adler-32 of the JSON is {expected.hex()}"
120+
)
121+
122+
123+
class PersistencyScenario(FitScenario):
124+
"""
125+
Base class for persistency feature integration tests.
126+
127+
Provides the ``temp_dir`` fixture shared by all persistency test classes,
128+
avoiding fixture duplication across subclasses.
129+
"""
130+
131+
@pytest.fixture(scope="class")
132+
def temp_dir(
133+
self,
134+
tmp_path_factory: pytest.TempPathFactory,
135+
version: str,
136+
) -> Generator[Path, None, None]:
137+
"""
138+
Provide a temporary working directory for the KVS instance.
139+
140+
The directory is named after the test class and parametrized version,
141+
and is automatically removed after the test class completes.
142+
143+
Parameters
144+
----------
145+
tmp_path_factory : pytest.TempPathFactory
146+
Built-in pytest factory for temporary directories.
147+
version : str
148+
Parametrized scenario version (``"rust"`` or ``"cpp"``).
149+
"""
150+
yield from temp_dir_common(tmp_path_factory, self.__class__.__name__, version)

0 commit comments

Comments
 (0)