Skip to content

feat: add TimeDaemon CIT with pip hub infrastructure - #122

Open
gordon9901 wants to merge 14 commits into
eclipse-score:mainfrom
gordon9901:ecarx_time_daemon_cit
Open

feat: add TimeDaemon CIT with pip hub infrastructure#122
gordon9901 wants to merge 14 commits into
eclipse-score:mainfrom
gordon9901:ecarx_time_daemon_cit

Conversation

@gordon9901

@gordon9901 gordon9901 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This PR adds TimeDaemon component integration tests with pip hub infrastructure (part 1 of 3):

  • Add three independent pip hubs for TimeDaemon, TimeSlave, and MIT test suites
  • Add score_test_scenarios framework dependency with compatibility patch for score_baselibs 0.2.7
  • Add TimeDaemon CIT covering daemon lifecycle, control flow, IPC, SVT, and verification pipeline
  • Fix visibility for shm_ptp_engine and svt_handler to allow access from test scenarios

Part of #56

test QNX

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown

License Check Results

🚀 The license check job ran with the Bazel command:

bazel run //:license-check

Status: ⚠️ Needs Review

Click to expand output
[License Check Output]
Extracting Bazel installation...
Starting local Bazel server (8.6.0) and connecting to it...
INFO: Invocation ID: 2f5b7cd7-0e10-47f6-bbdd-e7a574e21f87
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
WARNING: For repository 'platforms', the root module requires module version platforms@1.0.0, but got platforms@1.1.0 in the resolved dependency graph. Please update the version in your MODULE.bazel or set --check_direct_dependencies=off
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Computing main repo mapping: 
Loading: 
Loading: 3 packages loaded
Analyzing: target //:license-check (4 packages loaded, 0 targets configured)
Analyzing: target //:license-check (4 packages loaded, 0 targets configured)

Analyzing: target //:license-check (69 packages loaded, 10 targets configured)

Analyzing: target //:license-check (110 packages loaded, 378 targets configured)

Analyzing: target //:license-check (158 packages loaded, 3169 targets configured)

Analyzing: target //:license-check (160 packages loaded, 6249 targets configured)

Analyzing: target //:license-check (161 packages loaded, 8516 targets configured)

Analyzing: target //:license-check (172 packages loaded, 9417 targets configured)

Analyzing: target //:license-check (172 packages loaded, 9417 targets configured)

Analyzing: target //:license-check (172 packages loaded, 9417 targets configured)

Analyzing: target //:license-check (172 packages loaded, 9417 targets configured)

Analyzing: target //:license-check (176 packages loaded, 11429 targets configured)

INFO: Analyzed target //:license-check (177 packages loaded, 11555 targets configured).
[11 / 16] Creating runfiles tree bazel-out/k8-opt-exec-ST-d57f47055a04/bin/external/score_tooling+/dash/tool/formatters/dash_format_converter.runfiles [for tool]; 0s local
[12 / 16] JavaToolchainCompileClasses external/rules_java+/toolchains/platformclasspath_classes; 0s disk-cache, processwrapper-sandbox ... (2 actions running)
INFO: From Generating Dash formatted dependency file ...:
INFO: Successfully converted 2 packages from Cargo.lock to bazel-out/k8-fastbuild/bin/formatted.txt
[14 / 16] JavaToolchainCompileBootClasspath external/rules_java+/toolchains/platformclasspath.jar; 0s disk-cache, processwrapper-sandbox
INFO: Found 1 target...
Target //:license.check.license_check up-to-date:
  bazel-bin/license.check.license_check
  bazel-bin/license.check.license_check.jar
INFO: Elapsed time: 30.296s, Critical Path: 2.64s
INFO: 16 processes: 12 internal, 3 processwrapper-sandbox, 1 worker.
INFO: Build completed successfully, 16 total actions
INFO: Running command line: bazel-bin/license.check.license_check ./formatted.txt <args omitted>
usage: org.eclipse.dash.licenses.cli.Main [-batch <int>] [-cd <url>]
       [-confidence <int>] [-ef <url>] [-excludeSources <sources>] [-help] [-lic
       <url>] [-project <shortname>] [-repo <url>] [-review] [-summary <file>]
       [-timeout <seconds>] [-token <token>]

@github-actions

Copy link
Copy Markdown

The created documentation from the pull request is available at: docu-html

Comment thread score/time_daemon/src/ptp_machine/shm/details/BUILD Outdated
Comment thread score/tests/patches/score_test_scenarios_json_writer.patch Outdated
Comment thread score/time_daemon/tests/component_tests/tests/control_flow/test_ptp_divider.py Outdated
Comment thread score/time_daemon/tests/integration_tests/.gitkeep Outdated
Comment thread MODULE.bazel Outdated
Comment thread MODULE.bazel
Comment thread score/time_daemon/tests/integration_tests/requirements.txt Outdated
Comment thread score/time_daemon/tests/test_scenarios/cpp/src/main.cpp Outdated
Comment thread score/time_daemon/tests/integration_tests/conftest.py Outdated
Comment thread score/time_daemon/tests/test_scenarios/cpp/BUILD Outdated
Comment thread MODULE.bazel

@BjoernAtBosch BjoernAtBosch Aug 28, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't understand this: What is the benefit of using the combination of the pytest/testing_utils plus C++ code over using plain gtest/gmock? At the moment it looks to me making things more complicated and harder to understand.

Imho, for all the component internal testing, I would use gtest. That doesn't mean that everything tested with gtest is automatically a unit test (in the strict sense), it can be also some sort of integration tests - i.e. test which integrate multiple classes and test them together.

Then on the level of testing the whole component as a binary - TimeDaemon in this case - I'd use some pytests framework or ITF to stimulate and test the binary from "outside". And next step would be testing of multiple binaries "playing together".

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, fair point. You're right that ptp_divider and the single-validator scenarios were duplicating gtest coverage with unnecessary indirection — I've removed them in the latest push.

The CIT layer now only keeps scenarios that wire together two or more production objects: verification.pipeline (PTP stub → verification machine), ipc.shm_roundtrip (publisher/receiver over shared memory), and daemon.lifecycle (full SvtHandler init/shutdown). Single-class behaviour stays with gtest under src/. Added a short README documenting the boundary.

The pytest + test_scenarios stack is the same SCORE-standard framework used for the upcoming module-integration and system-test layers (multi-binary, QEMU), so keeping it here gives one runner surface scaling up to those levels. Let me know if that still feels off.

psutil
pytest-metadata
pytest-env
testing-utils @ git+https://github.com/eclipse-score/testing_tools.git@a2f9cded3deb636f5dc800bf7a47131487119721

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add version tag as comment

Suggested change
testing-utils @ git+https://github.com/eclipse-score/testing_tools.git@a2f9cded3deb636f5dc800bf7a47131487119721
testing-utils @ git+https://github.com/eclipse-score/testing_tools.git@a2f9cded3deb636f5dc800bf7a47131487119721 # v0.3.0

# via testing-utils
# WARNING: pip install will require the following package to be hashed.
# Consider using a hashable URL like https://github.com/jazzband/pip-tools/archive/SOMECOMMIT.zip
testing-utils @ git+https://github.com/eclipse-score/testing_tools.git@v0.3.0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops - why is the hash here replaced by the version tag? Thought this is generated ...

Comment on lines +43 to +57
void run(const std::string& /*input*/) const final
{
// Phase 1: construct — calls all four subsystem factory functions
auto handler = score::td::CreateSvtTimebase();

// Phase 2: initialize — wires MessageBroker pub-sub topology
handler->Initialize();

// Phase 3: stop from kIdle — verifies graceful shutdown without starting async workers
handler->Stop();

TRACING_INFO(kTargetName,
std::pair{std::string{"lifecycle_initialize_ok"}, std::string{"true"}},
std::pair{std::string{"lifecycle_complete"}, std::string{"true"}});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already tested in score/time_daemon/src/application/svt/svt_handler_integration_test.cpp

Comment on lines +41 to +85
void run(const std::string& /*input*/) const final
{
auto publisher = score::td::CreateSvtPublisher("cit_ipc_pub");
auto receiver = score::td::CreateSvtReceiver();

if (!publisher->Init())
{
throw std::runtime_error{"SvtPublisher::Init() failed"};
}
if (!receiver->Init())
{
throw std::runtime_error{"SvtReceiver::Init() failed"};
}

score::td::PtpTimeInfo info{};
info.ptp_assumed_time = std::chrono::nanoseconds{1'000'000'000LL};
info.local_time = score::td::PtpTimeInfo::ReferenceClock::time_point{std::chrono::nanoseconds{500'000'000LL}};
info.rate_deviation = 0.0;
info.status = {true, false, false, false, true};
info.sync_fup_data.sequence_id = 42U;
info.sync_fup_data.precise_origin_timestamp = 1'000'000'000ULL;

publisher->OnMessage(info);

const auto result = receiver->Receive();
const bool read_ok = result.has_value();

bool ptp_time_ok = false;
bool status_ok = false;
bool seq_ok = false;

if (read_ok)
{
const auto& snap = result.value();
ptp_time_ok = snap.ptp_assumed_time == static_cast<uint64_t>(1'000'000'000LL);
status_ok = snap.status.is_synchronized && snap.status.is_correct;
seq_ok = snap.sync_fup_data.sequence_id == 42U;
}

TRACING_INFO(kTargetName,
std::pair{std::string{"read_succeeded"}, std::string{read_ok ? "true" : "false"}},
std::pair{std::string{"ptp_time_preserved"}, std::string{ptp_time_ok ? "true" : "false"}},
std::pair{std::string{"status_preserved"}, std::string{status_ok ? "true" : "false"}},
std::pair{std::string{"seq_id_preserved"}, std::string{seq_ok ? "true" : "false"}});
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is also already tested: score/time_daemon/src/ipc/svt/common_factory_test.cpp

Comment on lines +46 to +82
{
auto ptp_machine = score::td::CreateGPTPStubMachine("cit_verif_ptp");
auto verifier = score::td::CreateSvtVerificationMachine("cit_verif");

std::promise<score::td::PtpTimeInfo> verified_promise;
auto verified_future = verified_promise.get_future();

// Wire PTP machine output into the verification pipeline.
ptp_machine->SetPublishCallback([&verifier](const score::td::PtpTimeInfo& data) {
verifier->OnMessage(data);
});

// Capture the first data point that exits the verification pipeline.
verifier->SetPublishCallback([&verified_promise](const score::td::PtpTimeInfo& data) {
try
{
verified_promise.set_value(data);
}
catch (const std::future_error&)
{
// Capture only the first verified data point.
}
});

verifier->Init();

if (!ptp_machine->Init())
{
throw std::runtime_error{"GPTPStubMachine::Init() failed"};
}
ptp_machine->Start();

if (verified_future.wait_for(std::chrono::seconds(5)) != std::future_status::ready)
{
ptp_machine->Stop();
throw std::runtime_error{"Timed out waiting for verified PTP data through pipeline"};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This kind of test makes sense, because it really combines multiple sub-components. Following remarks:

  1. We should use plain gtest for doing this. Makes error checking much easier and expressive, e.g. instead of having:
if (!ptp_machine->Init()) {
    throw std::runtime_error{"GPTPStubMachine::Init() failed"};
}

we'd have:

ASSERT_TRUE(ptp_machine->Init());

and no additional checks on Python side.

  1. A similar test like this could be achieved by instantiating the application svt_handler. Valery already added test code there (score/time_daemon/src/application/svt/svt_handler_integration_test.cpp) but that is commented out at the moment.

I really would use Python/ITF based tests only for testing a single - in this case the TimeDaemon - or multiple binaries working together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

4 participants