feat: add TimeDaemon CIT with pip hub infrastructure - #122
Conversation
License Check Results🚀 The license check job ran with the Bazel command: bazel run //:license-checkStatus: Click to expand output |
|
The created documentation from the pull request is available at: docu-html |
| psutil | ||
| pytest-metadata | ||
| pytest-env | ||
| testing-utils @ git+https://github.com/eclipse-score/testing_tools.git@a2f9cded3deb636f5dc800bf7a47131487119721 |
There was a problem hiding this comment.
Please add version tag as comment
| 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 |
There was a problem hiding this comment.
Done — added # v0.3.0 at end of line.
| # 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 |
There was a problem hiding this comment.
Oops - why is the hash here replaced by the version tag? Thought this is generated ...
There was a problem hiding this comment.
You're right, the lockfile had been manually edited and the requirements.update target was also broken (score_tooling//python_basics:requirements.txt was renamed to requirements.in after an upgrade). I fixed the BUILD reference and re-generated the lockfile with bazel run :requirements.update — it now keeps the commit hash from requirements.txt as expected.
| 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"}}); | ||
| } |
There was a problem hiding this comment.
This is already tested in score/time_daemon/src/application/svt/svt_handler_integration_test.cpp
There was a problem hiding this comment.
Good point, SvtHandlerTest.CreateAndDestroy already covers construct→Initialize→Stop. Removed the lifecycle CIT scenario.
| 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"}}); | ||
| } |
There was a problem hiding this comment.
This is also already tested: score/time_daemon/src/ipc/svt/common_factory_test.cpp
There was a problem hiding this comment.
Acknowledged — FactoryImplTest.TestReadAndWrite already exercises the full publish→receive path with EXPECT_EQ full-struct comparison, which is stricter than the CIT version. Removed.
| { | ||
| 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"}; | ||
| } |
There was a problem hiding this comment.
This kind of test makes sense, because it really combines multiple sub-components. Following remarks:
- 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.
- 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.
There was a problem hiding this comment.
Addressed. I agree that in-process multi-object wiring belongs in gtest:
Ported the pipeline scenario to svt_verification_machine_pipeline_integration_test.cpp under src/verification_machine/svt/, using ASSERT_/EXPECT_ assertions instead of TRACING_INFO + Python-side checks.
Removed the CIT test_scenarios/cpp/ binary and the corresponding pytest targets. The CIT scaffolding (conftest.py, cit_scenario.py, requirements) is kept for future process-level tests (launching the real TimeDaemon binary), with a note in the README clarifying that CIT is reserved for single-/multi-binary scenarios.
Re (2): I didn't wire this through svt_handler because the existing svt_handler_integration_test is currently commented out in the BUILD and relies on a mocked libqgptp; the new gtest uses the real GPTPStubMachine + SvtVerificationMachine directly, which is the specific stub→verification path I wanted to cover. Happy to consolidate into svt_handler_integration_test once that is re-enabled — let me know if you'd prefer that.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
b0aa357 to
b6236d6
Compare
b6236d6 to
0b23511
Compare
This PR adds TimeDaemon component integration tests with pip hub infrastructure (part 1 of 3):
Part of #56
test QNX