diff --git a/.github/workflows/docs.yml b/.github/workflows/test_and_docs.yml similarity index 70% rename from .github/workflows/docs.yml rename to .github/workflows/test_and_docs.yml index bcb3dc92..d413f8de 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/test_and_docs.yml @@ -31,7 +31,24 @@ on: types: [created] jobs: + docs-verify: + uses: eclipse-score/cicd-workflows/.github/workflows/docs-verify.yml@main + permissions: + pull-requests: write + contents: read + with: + bazel-docs-verify-target: "//:docs_check" + run-tests: + uses: eclipse-score/cicd-workflows/.github/workflows/tests.yml@main + permissions: + contents: read + pull-requests: read + with: + bazel-target: 'test //src/... //tests/... --config=x86_64-linux' + upload-name: 'bazel-testlogs' build-docs: + needs: run-tests + if: ${{ always() }} uses: eclipse-score/cicd-workflows/.github/workflows/docs.yml@main permissions: contents: write @@ -43,3 +60,4 @@ jobs: # the bazel-target depends on your repo specific docs_targets configuration (e.g. "suffix") bazel-target: "//:docs -- --github_user=${{ github.repository_owner }} --github_repo=${{ github.event.repository.name }}" retention-days: 3 + tests-report-artifact: bazel-testlogs diff --git a/MODULE.bazel b/MODULE.bazel index 6c09448e..a1f86bfc 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,15 +16,15 @@ module( ) # Bazel global rules -bazel_dep(name = "rules_python", version = "1.4.1") +bazel_dep(name = "rules_python", version = "1.8.3") bazel_dep(name = "rules_rust", version = "0.61.0") -bazel_dep(name = "rules_cc", version = "0.2.14") -bazel_dep(name = "aspect_rules_lint", version = "1.5.3") +bazel_dep(name = "rules_cc", version = "0.2.16") +bazel_dep(name = "aspect_rules_lint", version = "2.0.0") bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2") bazel_dep(name = "platforms", version = "1.0.0") bazel_dep(name = "flatbuffers", version = "25.9.23") -bazel_dep(name = "download_utils", version = "1.0.1") -bazel_dep(name = "googletest", version = "1.17.0.bcr.1") +bazel_dep(name = "download_utils", version = "1.2.2") +bazel_dep(name = "googletest", version = "1.17.0.bcr.2") # S-CORE process rules bazel_dep(name = "score_bazel_platforms", version = "0.0.4") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c5b8ed8f..c436bd60 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -186,7 +186,6 @@ "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", - "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.1/MODULE.bazel": "9f8e815fba6e81dee850a33068166989000eabcf7690d2127a975c2ebda6baae", "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "827f54f492a3ce549c940106d73de332c2b30cebd0c20c0bc5d786aba7f116cb", "https://bcr.bazel.build/modules/googletest/1.17.0.bcr.2/source.json": "3664514073a819992320ffbce5825e4238459df344d8b01748af2208f8d2e1eb", "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", @@ -590,7 +589,6 @@ "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.14.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.15.2/MODULE.bazel": "not found", - "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0.bcr.1/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0.bcr.2/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/googletest/1.17.0/MODULE.bazel": "not found", "https://raw.githubusercontent.com/eclipse-score/bazel_registry/main/modules/grpc-java/1.62.2/MODULE.bazel": "not found", diff --git a/docs/conf.py b/docs/conf.py index cf13475a..4bd7a047 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,6 +16,11 @@ # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html +from itertools import chain +from pathlib import Path + +from docutils import nodes +from docutils.parsers.rst import Directive # -- Project information ----------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information @@ -54,3 +59,43 @@ # Enable numref numfig = True + + +class DisplayTestLogs(Directive): + """Find and display the raw content of all test.log files.""" + + def run(self): + env = self.state.document.settings.env + ws_root = Path(env.app.srcdir).parent + + result_nodes = [] + for log_file in chain( + (ws_root / "bazel-testlogs").rglob("test.log"), + (ws_root / "tests-report").rglob("test.log"), + ): + rel_path = log_file.relative_to(ws_root) + + title = nodes.rubric(text=str(rel_path)) + result_nodes.append(title) + + try: + content = log_file.read_text(encoding="utf-8") + except Exception as e: + content = f"Error reading file: {e}" + + code = nodes.literal_block(content, content) + code["language"] = "text" + code["source"] = str(rel_path) + result_nodes.append(code) + + if not result_nodes: + para = nodes.paragraph( + text="No test.log files found in bazel-testlogs or tests-report." + ) + result_nodes.append(para) + + return result_nodes + + +def setup(app): + app.add_directive("display-test-logs", DisplayTestLogs) diff --git a/docs/index.rst b/docs/index.rst index 6f70b540..3dfd4d99 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,6 +21,7 @@ Lifecycle :titlesonly: module/*/index + statistics.rst Overview -------- diff --git a/docs/statistics.rst b/docs/statistics.rst new file mode 100644 index 00000000..e02c0358 --- /dev/null +++ b/docs/statistics.rst @@ -0,0 +1,122 @@ +.. _statistics: + +Component Requirements Statistics +================================= + +Overview +-------- + +.. needpie:: Requirements Status + :labels: not valid, valid but not tested, valid and tested + :colors: red, yellow, green + + type == 'comp_req' and status == 'invalid' + type == 'comp_req' and testlink == '' and (status == 'valid' or status == 'invalid') + type == 'comp_req' and testlink != '' and (status == 'valid' or status == 'invalid') + +In Detail +--------- + +.. grid:: 2 + :class-container: score-grid + + .. grid-item-card:: + + .. needpie:: Requirements marked as Valid + :labels: not valid, valid + :colors: red, orange, green + + type == 'comp_req' and status == 'invalid' + type == 'comp_req' and status == 'valid' + + .. grid-item-card:: + + .. needpie:: Requirements with Codelinks + :labels: no codelink, with codelink + :colors: red, green + + type == 'comp_req' and source_code_link == '' + type == 'comp_req' and source_code_link != '' + + .. grid-item-card:: + + .. needpie:: Test Results + :labels: passed, failed, skipped + :colors: green, red, orange + + type == 'testcase' and result == 'passed' + type == 'testcase' and result == 'failed' + type == 'testcase' and result == 'skipped' + +.. grid:: 2 + + .. grid-item-card:: + + Failed Tests + + *Hint: This table should be empty. Before a PR can be merged all tests have to be successful.* + + .. needtable:: FAILED TESTS + :filter: result == "failed" + :tags: TEST + :columns: name as "testcase";result;fully_verifies;partially_verifies;test_type;derivation_technique;id as "link" + + .. grid-item-card:: + + Skipped / Disabled Tests + + .. needtable:: SKIPPED/DISABLED TESTS + :filter: result != "failed" and result != "passed" + :tags: TEST + :columns: name as "testcase";result;fully_verifies;partially_verifies;test_type;derivation_technique;id as "link" + + + + +All passed Tests +----------------- + +.. needtable:: SUCCESSFUL TESTS + :filter: result == "passed" + :tags: TEST + :columns: name as "testcase";result;fully_verifies;partially_verifies;test_type;derivation_technique;id as "link" + + +Details About Testcases +------------------------ + +.. needpie:: Test Types Used In Testcases + :labels: static-code-analysis, structural-statement-coverage, structural-branch-coverage, walkthrough, inspection, interface-test, requirements-based, resource-usage, control-flow-analysis, data-flow-analysis, fault-injection, struct-func-cov, struct-call-cov + :legend: + + type == 'testcase' and test_type == 'static-code-analysis' + type == 'testcase' and test_type == 'structural-statement-coverage' + type == 'testcase' and test_type == 'structural-branch-coverage' + type == 'testcase' and test_type == 'walkthrough' + type == 'testcase' and test_type == 'inspection' + type == 'testcase' and test_type == 'interface-test' + type == 'testcase' and test_type == 'requirements-based' + type == 'testcase' and test_type == 'resource-usage' + type == 'testcase' and test_type == 'control-flow-analysis' + type == 'testcase' and test_type == 'data-flow-analysis' + type == 'testcase' and test_type == 'fault-injection' + type == 'testcase' and test_type == 'struct-func-cov' + type == 'testcase' and test_type == 'struct-call-cov' + + +.. needpie:: Derivation Techniques Used In Testcases + :labels: requirements-analysis, boundary-values, equivalence-classes, fuzz-testing, error-guessing, explorative-testing + :legend: + + type == 'testcase' and derivation_technique == 'requirements-analysis' + type == 'testcase' and derivation_technique == 'boundary-values' + type == 'testcase' and derivation_technique == 'equivalence-classes' + type == 'testcase' and derivation_technique == 'fuzz-testing' + type == 'testcase' and derivation_technique == 'error-guessing' + type == 'testcase' and derivation_technique == 'explorative-testing' + + +Test Log Files +-------------- + +.. display-test-logs:: diff --git a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp index 32d76085..ff78e436 100644 --- a/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp +++ b/src/health_monitoring_lib/cpp/tests/health_monitor_test.cpp @@ -21,11 +21,21 @@ using ::testing::_; class HealthMonitorTest : public ::testing::Test { + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing "); + } }; // For first review round, only single test case to show up API TEST_F(HealthMonitorTest, TestName) { + RecordProperty( + "Description", + "This test demonstrates the usage of HealthMonitor and DeadlineMonitor APIs. It creates a HealthMonitor with a " + "DeadlineMonitor, retrieves the DeadlineMonitor, and tests starting a deadline."); auto builder_mon = deadline::DeadlineMonitorBuilder() .add_deadline(IdentTag("deadline_1"), TimeRange(std::chrono::milliseconds(100), std::chrono::milliseconds(200))) diff --git a/src/launch_manager_daemon/process_state_client_lib/src/processstateclient_UT.cpp b/src/launch_manager_daemon/process_state_client_lib/src/processstateclient_UT.cpp index 83eba4b1..1bd8e532 100644 --- a/src/launch_manager_daemon/process_state_client_lib/src/processstateclient_UT.cpp +++ b/src/launch_manager_daemon/process_state_client_lib/src/processstateclient_UT.cpp @@ -10,39 +10,51 @@ * * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -#include -#include -#include #include +#include +#include +#include using namespace testing; using namespace score::lcm; -using score::lcm::internal::ProcessStateNotifier; using score::lcm::ProcessStateReceiver; +using score::lcm::internal::ProcessStateNotifier; -class ProcessStateClient_UT : public ::testing::Test { - protected: - void SetUp() override { +class ProcessStateClient_UT : public ::testing::Test +{ + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing "); notifier_ = std::make_unique(); receiver_ = notifier_->constructReceiver(); } - void TearDown() override { + void TearDown() override + { receiver_.reset(); notifier_.reset(); } std::unique_ptr notifier_; std::unique_ptr receiver_; - }; -TEST_F(ProcessStateClient_UT, ProcessStateClient_ConstructReceiver_Succeeds) { +TEST_F(ProcessStateClient_UT, ProcessStateClient_ConstructReceiver_Succeeds) +{ + RecordProperty( + "Description", + "This test verifies that the ProcessStateNotifier can successfully construct a ProcessStateReceiver instance."); ASSERT_NE(notifier_, nullptr); ASSERT_NE(receiver_, nullptr); } -TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueOneProcess_Succeeds) { - PosixProcess process1{ +TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueOneProcess_Succeeds) +{ + RecordProperty("Description", + "This test verifies that a single PosixProcess can be successfully queued using the " + "ProcessStateNotifier and retrieved using the ProcessStateReceiver."); + PosixProcess process1{ .id = score::lcm::IdentifierHash("Process1"), .processStateId = score::lcm::ProcessState::kRunning, .processGroupStateId = score::lcm::IdentifierHash("PGState1"), @@ -54,22 +66,28 @@ TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueOneProcess_Succeeds) { // Retrieve the queued process via the receiver auto result = receiver_->getNextChangedPosixProcess(); - ASSERT_TRUE(result.has_value()); // Result contains Optional value - ASSERT_TRUE(result->has_value()); // Optional contains PosixProcess + ASSERT_TRUE(result.has_value()); // Result contains Optional value + ASSERT_TRUE(result->has_value()); // Optional contains PosixProcess EXPECT_EQ(result->value().id, process1.id); EXPECT_EQ(result->value().processStateId, process1.processStateId); EXPECT_EQ(result->value().processGroupStateId, process1.processGroupStateId); - + // Ensure no more processes are queued auto no_more = receiver_->getNextChangedPosixProcess(); - ASSERT_TRUE(no_more.has_value()); // Result contains Optional value - ASSERT_FALSE(no_more->has_value()); // Optional is empty + ASSERT_TRUE(no_more.has_value()); // Result contains Optional value + ASSERT_FALSE(no_more->has_value()); // Optional is empty } -TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueMaxNumberOfProcesses_Succeeds) { +TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueMaxNumberOfProcesses_Succeeds) +{ + RecordProperty( + "Description", + "This test verifies that the ProcessStateNotifier can successfully queue the maximum number of PosixProcess " + "instances defined by the buffer size, and that they can be retrieved using the ProcessStateReceiver."); // Queue maximum number of processes - for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) { - PosixProcess process{ + for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) + { + PosixProcess process{ .id = score::lcm::IdentifierHash("Process" + std::to_string(i)), .processStateId = score::lcm::ProcessState::kRunning, .processGroupStateId = score::lcm::IdentifierHash("PGState" + std::to_string(i)), @@ -79,7 +97,8 @@ TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueMaxNumberOfProcesses_Succe } // Retrieve and verify all queued processes - for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) { + for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) + { auto result = receiver_->getNextChangedPosixProcess(); ASSERT_TRUE(result.has_value()); ASSERT_TRUE(result->has_value()); @@ -92,16 +111,22 @@ TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueMaxNumberOfProcesses_Succe ASSERT_FALSE(no_more->has_value()); } -TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueOneProcessTooMany_Fails) { - PosixProcess process1{ +TEST_F(ProcessStateClient_UT, ProcessStateClient_QueueOneProcessTooMany_Fails) +{ + RecordProperty( + "Description", + "This test verifies that attempting to queue a PosixProcess when the buffer is already at maximum capacity " + "results in a failure, and that no additional processes can be retrieved from the receiver."); + PosixProcess process1{ .id = score::lcm::IdentifierHash("Process1"), .processStateId = score::lcm::ProcessState::kRunning, .processGroupStateId = score::lcm::IdentifierHash("PGState1"), }; // Fill the buffer to capacity - for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) { - PosixProcess proc{ + for (size_t i = 0; i < static_cast(BufferConstants::BUFFER_QUEUE_SIZE); ++i) + { + PosixProcess proc{ .id = score::lcm::IdentifierHash("Process" + std::to_string(i)), .processStateId = score::lcm::ProcessState::kRunning, .processGroupStateId = score::lcm::IdentifierHash("PGState" + std::to_string(i)), diff --git a/src/lifecycle_client_lib/src/lifecyclemanager.cpp b/src/lifecycle_client_lib/src/lifecyclemanager.cpp index eb66e682..94b7a3ee 100644 --- a/src/lifecycle_client_lib/src/lifecyclemanager.cpp +++ b/src/lifecycle_client_lib/src/lifecyclemanager.cpp @@ -108,21 +108,24 @@ bool score::mw::lifecycle::LifeCycleManager::initialize_internal() const auto empty_set_status = signal_->SigEmptySet(m_signal_set); if (empty_set_status.has_value() == false) { - mw::log::LogError() << kFailedCreateSig << empty_set_status.error().ToString(); + mw::log::LogError() << score::mw::log::LogString{kFailedCreateSig, sizeof(kFailedCreateSig)} + << empty_set_status.error().ToString(); return false; } /* KW_SUPPRESS_END:UNREACH.GEN */ const auto add_termination_status = signal_->AddTerminationSignal(m_signal_set); if (add_termination_status.has_value() == false) { - mw::log::LogError() << kFailedAddSigterm << add_termination_status.error().ToString(); + mw::log::LogError() << score::mw::log::LogString{kFailedAddSigterm, sizeof(kFailedAddSigterm)} + << add_termination_status.error().ToString(); return false; } const auto pthread_sig_mask_status = signal_->PthreadSigMask(m_signal_set); /* NOLINT(score-banned-function) using PthreadSigMask by desing */ if (pthread_sig_mask_status.has_value() == false) { - mw::log::LogError() << kFailedBlockSig << pthread_sig_mask_status.error().ToString(); + mw::log::LogError() << score::mw::log::LogString{kFailedBlockSig, sizeof(kFailedBlockSig)} + << pthread_sig_mask_status.error().ToString(); return false; } // only start thread if everything was ok @@ -156,7 +159,7 @@ void score::mw::lifecycle::LifeCycleManager::handle_signal() mw::log::LogError() << "Application will exit with status EXIT_FAILURE!"; /* quick_exit() is used instead of exit() to avoid undefined behavior when trying to finish execution even if it is still possible that initialization is ongoing and using global resources i.e. logging. - KW_SUPPRESS_START:MISRA.STDLIB.ABORT,MISRA.USE.EXPANSION: + KW_SUPPRESS_START:MISRA.STDLIB.ABORT,MISRA.USE.EXPANSION: (1) Exit call tolerated as we need to return an exit code. (2) Macro tolerated as failure value is implementation defined. */ score::os::Stdlib::instance().quick_exit(EXIT_FAILURE); diff --git a/tests/ut/identifier_hash_UT/identifier_hash_UT.cpp b/tests/ut/identifier_hash_UT/identifier_hash_UT.cpp index c3dbb658..cefad29f 100644 --- a/tests/ut/identifier_hash_UT/identifier_hash_UT.cpp +++ b/tests/ut/identifier_hash_UT/identifier_hash_UT.cpp @@ -20,8 +20,21 @@ using std::stringstream; using score::lcm::IdentifierHash; -TEST(IdentifierHashTest, IdentifierHash_with_string_view_created) +class IdentifierHashTest : public ::testing::Test { + protected: + void SetUp() override + { + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "explorative-testing "); + } +}; + +TEST_F(IdentifierHashTest, IdentifierHash_with_string_view_created) +{ + RecordProperty("Description", + "This test verifies that an IdentifierHash can be successfully created using a std::string_view, " + "and that its string representation can be retrieved correctly."); std::string_view idStrView = "ProcessGroup1/Startup"; IdentifierHash identifierHash(idStrView); stringstream strStream; @@ -29,8 +42,11 @@ TEST(IdentifierHashTest, IdentifierHash_with_string_view_created) ASSERT_EQ(strStream.str(), idStrView); } -TEST(IdentifierHashTest, IdentifierHash_with_string_created) +TEST_F(IdentifierHashTest, IdentifierHash_with_string_created) { + RecordProperty("Description", + "This test verifies that an IdentifierHash can be successfully created using a std::string, and " + "that its string representation can be retrieved correctly."); std::string idStr = "ProcessGroup1/Startup"; IdentifierHash identifierHash(idStr); stringstream strStream; @@ -38,16 +54,22 @@ TEST(IdentifierHashTest, IdentifierHash_with_string_created) ASSERT_EQ(strStream.str(), idStr); } -TEST(IdentifierHashTest, IdentifierHash_default_created) +TEST_F(IdentifierHashTest, IdentifierHash_default_created) { + RecordProperty("Description", + "This test verifies that a default-constructed IdentifierHash can be created, and that its string " + "representation is empty."); IdentifierHash identifierHash; stringstream strStream; strStream << identifierHash; ASSERT_EQ(strStream.str(), ""); } -TEST(IdentifierHashTest, IdentifierHash_invalid_hash_no_string_representation) +TEST_F(IdentifierHashTest, IdentifierHash_invalid_hash_no_string_representation) { + RecordProperty("Description", + "This test verifies that if an IdentifierHash is created with a string that is not registered in " + "the registry, its string representation indicates that it is unknown and includes the hash value."); std::string idStr = "MainFG"; IdentifierHash identifierHash(idStr); @@ -60,8 +82,12 @@ TEST(IdentifierHashTest, IdentifierHash_invalid_hash_no_string_representation) ASSERT_TRUE(strStream.str().find(std::to_string(identifierHash.data())) != std::string::npos); } -TEST(IdentifierHashTest, IdentifierHash_no_dangling_pointer_after_source_string_dies) +TEST_F(IdentifierHashTest, IdentifierHash_no_dangling_pointer_after_source_string_dies) { + RecordProperty("Description", + "This test verifies that an IdentifierHash created from a std::string does not have a dangling " + "pointer to the original string after it goes out of scope, and that its string representation can " + "still be retrieved correctly."); std::unique_ptr hash_ptr; std::string_view idStrView = "this string will be destroyed";