diff --git a/BUILD b/BUILD index ba3b540541..86a1d41812 100644 --- a/BUILD +++ b/BUILD @@ -35,6 +35,10 @@ docs( "bundle": "//score/concurrency:docs", "mount_at": "baselibs/components/concurrency", }, + { + "bundle": "//score/scope_exit:docs", + "mount_at": "baselibs/components/scope_exit", + }, { "bundle": "//score/containers:docs", "mount_at": "baselibs/components/containers", diff --git a/docs/baselibs/components/index.rst b/docs/baselibs/components/index.rst index 75aac0dcd8..8f9c718196 100644 --- a/docs/baselibs/components/index.rst +++ b/docs/baselibs/components/index.rst @@ -18,7 +18,7 @@ Components .. toctree:: :maxdepth: 1 - abi_compatible_data_types/docs/index + abi_compatible_data_types/docs/index Overview @@ -55,3 +55,4 @@ Overview - *mw::log*: Logging frontend. - :need:`doc__utils`: Provides a collection of small, reusable utilities that do not fit into the other base libraries. +- :need:`doc__scope_exit`: Provides move-only ownership utilities for scope-based cleanup and flag transfer. diff --git a/score/scope_exit/BUILD b/score/scope_exit/BUILD index fb8be4bbee..5bdb7890c2 100644 --- a/score/scope_exit/BUILD +++ b/score/scope_exit/BUILD @@ -15,6 +15,7 @@ load("@rules_cc//cc:defs.bzl", "cc_library") load("@score_baselibs//:bazel/unit_tests.bzl", "cc_gtest_unit_test") load("@score_baselibs//score/language/safecpp:toolchain_features.bzl", "COMPILER_WARNING_FEATURES") load("@score_baselibs//score/quality/clang_tidy:extra_checks.bzl", "clang_tidy_extra_checks") +load("@score_docs_as_code//:docs.bzl", "docs_bundle") cc_library( name = "flag_owner", @@ -64,3 +65,9 @@ clang_tidy_extra_checks( ], tidy_config_file = ".clang-tidy-extra", ) + +docs_bundle( + name = "docs", + source_dir = "docs", + visibility = ["//visibility:public"], +) diff --git a/score/scope_exit/docs/architecture/index.rst b/score/scope_exit/docs/architecture/index.rst new file mode 100644 index 0000000000..ef024633e6 --- /dev/null +++ b/score/scope_exit/docs/architecture/index.rst @@ -0,0 +1,34 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +Scope Exit Architecture +######################## + +.. document:: Scope Exit Architecture + :id: doc__scope_exit_architecture + :status: draft + :version: 1 + :safety: ASIL_B + :security: YES + :realizes: wp__component_arch[version==1] + +.. comp:: Scope Exit + :id: comp__baselibs_scope_exit + :security: YES + :safety: ASIL_B + :status: valid + :version: 1 + :belongs_to: feat__baselibs[version==1] + + The Scope Exit component contains the scope guard and flag ownership utilities. \ No newline at end of file diff --git a/score/scope_exit/docs/index.rst b/score/scope_exit/docs/index.rst new file mode 100644 index 0000000000..1c808f5470 --- /dev/null +++ b/score/scope_exit/docs/index.rst @@ -0,0 +1,33 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +Scope Exit +########## + +.. document:: Scope Exit + :id: doc__scope_exit + :status: draft + :version: 1 + :safety: ASIL_B + :security: YES + :realizes: wp__cmpt_request[version==1] + +.. toctree:: + :hidden: + + requirements/index.rst + architecture/index.rst + +The Scope Exit component provides move-only ownership utilities for reliable +scope-based cleanup and flag transfer. \ No newline at end of file diff --git a/score/scope_exit/docs/requirements/index.rst b/score/scope_exit/docs/requirements/index.rst new file mode 100644 index 0000000000..5007f0e661 --- /dev/null +++ b/score/scope_exit/docs/requirements/index.rst @@ -0,0 +1,54 @@ +.. + # ******************************************************************************* + # 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 + # ******************************************************************************* + +Requirements +############ + +.. document:: Scope Exit Requirements + :id: doc__scope_exit_requirements + :status: draft + :version: 1 + :safety: ASIL_B + :security: YES + :realizes: wp__requirements_comp[version==1] + :tags: requirements, scope_exit + +Functional Requirements +======================= + +.. comp_req:: Scope-Based Cleanup Ownership + :id: comp_req__scope_exit__cleanup_ownership + :reqtype: Functional + :security: YES + :safety: ASIL_B + :derived_from: feat_req__baselibs__utils_library[version==2] + :status: valid + :version: 1 + :tags: inspected + :satisfied_by: comp__baselibs_scope_exit[version==1] + + The Scope Exit component shall invoke an owned cleanup callback at scope exit at most once, and shall transfer or release callback ownership according to move construction, move assignment, and explicit release operations. + +.. comp_req:: Move-Only Flag Ownership + :id: comp_req__scope_exit__flag_ownership + :reqtype: Functional + :security: YES + :safety: ASIL_B + :derived_from: feat_req__baselibs__utils_library[version==2] + :status: valid + :version: 1 + :tags: inspected + :satisfied_by: comp__baselibs_scope_exit[version==1] + + The Scope Exit component shall provide a move-only flag owner that transfers its flag value to the destination and clears the source during move operations, while preserving the value during self-move assignment. \ No newline at end of file diff --git a/score/scope_exit/flag_owner_test.cpp b/score/scope_exit/flag_owner_test.cpp index 2d02f243cc..ce64fd9827 100644 --- a/score/scope_exit/flag_owner_test.cpp +++ b/score/scope_exit/flag_owner_test.cpp @@ -23,6 +23,10 @@ namespace TEST(FlagOwnerTest, CreatingFlagOwnerWithTrueInitialValueWillSetTheFlag) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__flag_ownership"); + RecordProperty("Description", "Check that a flag owner initialized true reports an engaged flag."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "boundary-values"); // When creating the FlagOwner with the initial value set to true FlagOwner flag_owner{true}; @@ -32,6 +36,10 @@ TEST(FlagOwnerTest, CreatingFlagOwnerWithTrueInitialValueWillSetTheFlag) TEST(FlagOwnerTest, CreatingFlagOwnerWithFalseInitialValueWillClearTheFlag) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__flag_ownership"); + RecordProperty("Description", "Check that a flag owner initialized false reports a cleared flag."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "boundary-values"); // When creating the FlagOwner with the initial value set to false FlagOwner flag_owner{false}; @@ -41,6 +49,10 @@ TEST(FlagOwnerTest, CreatingFlagOwnerWithFalseInitialValueWillClearTheFlag) TEST(FlagOwnerTest, MoveConstructingAFlagOwnerWillTransferTheFlagValue) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__flag_ownership"); + RecordProperty("Description", "Check that move construction transfers the flag and clears the source owner."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); // Given a FlagOwner with the flag set FlagOwner flag_owner_1{true}; @@ -54,6 +66,10 @@ TEST(FlagOwnerTest, MoveConstructingAFlagOwnerWillTransferTheFlagValue) TEST(FlagOwnerTest, MoveAssigningAFlagOwnerWillTransferTheFlagValue) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__flag_ownership"); + RecordProperty("Description", "Check that move assignment transfers the flag and clears the source owner."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); // Given a FlagOwner with the flag set and another the the flag cleraed FlagOwner flag_owner_1{true}; FlagOwner flag_owner_2{false}; @@ -68,6 +84,10 @@ TEST(FlagOwnerTest, MoveAssigningAFlagOwnerWillTransferTheFlagValue) TEST(FlagOwnerTest, SelfMoveAssigningAFlagOwnerDoesNotChangeFlagValue) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__flag_ownership"); + RecordProperty("Description", "Check that self-move assignment leaves the flag value unchanged."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "boundary-values"); // Given a FlagOwner with the flag set const bool initial_flag_value{true}; // Note. we use a std::optional to avoid a clang compiler warning -Wself-move diff --git a/score/scope_exit/scope_exit_test.cpp b/score/scope_exit/scope_exit_test.cpp index 85d772f20b..570a41779e 100644 --- a/score/scope_exit/scope_exit_test.cpp +++ b/score/scope_exit/scope_exit_test.cpp @@ -63,6 +63,11 @@ class ScopeExitFixture : public ::testing::Test // use GivenAScopeExit. TEST_F(ScopeExitFixture, CreatingDoesNotCallDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that constructing a scope guard does not invoke its cleanup callback prematurely."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "boundary-values"); // When creating a ScopeExit bool destruction_handler_called{false}; ScopeExit<> scope_exit{[&destruction_handler_called]() noexcept { @@ -75,6 +80,11 @@ TEST_F(ScopeExitFixture, CreatingDoesNotCallDestructionHandler) TEST_F(ScopeExitFixture, DestroyingCallsDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that destroying an active scope guard invokes its cleanup callback exactly once."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "requirements-analysis"); bool destruction_handler_called{false}; { // Given a ScopeExit @@ -91,6 +101,11 @@ TEST_F(ScopeExitFixture, DestroyingCallsDestructionHandler) TEST_F(ScopeExitFixture, DestroyingWithScopedFunctionCallsDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that a scope guard accepts a move-only scoped callback and invokes it at destruction."); + RecordProperty("TestType", "interface-test"); + RecordProperty("DerivationTechnique", "equivalence-classes"); safecpp::Scope<> scope{}; bool destruction_handler_called{false}; { @@ -108,6 +123,11 @@ TEST_F(ScopeExitFixture, DestroyingWithScopedFunctionCallsDestructionHandler) TEST_F(ScopeExitFixture, DestroyingAfterCallingReleaseDoesNotCallDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that releasing an active scope guard suppresses its cleanup callback at destruction."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "boundary-values"); GivenAScopeExit(); // and given that Release has been called on the ScopeExit @@ -122,6 +142,12 @@ TEST_F(ScopeExitFixture, DestroyingAfterCallingReleaseDoesNotCallDestructionHand TEST_F(ScopeExitFixture, MoveConstructingGuardDoesNotCallDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty( + "Description", + "Check that move construction transfers callback ownership without invoking the moved-from callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenAScopeExit(); // When move constructing a new ScopeExit @@ -133,6 +159,11 @@ TEST_F(ScopeExitFixture, MoveConstructingGuardDoesNotCallDestructionHandler) TEST_F(ScopeExitFixture, DestroyingMoveConstructedMovedFromGuardDoesNotCallDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that destroying a moved-from guard does not invoke the transferred cleanup callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenAScopeExit(); // and given a new ScopeExit move constructed from another @@ -147,6 +178,11 @@ TEST_F(ScopeExitFixture, DestroyingMoveConstructedMovedFromGuardDoesNotCallDestr TEST_F(ScopeExitFixture, DestroyingMoveConstructedMovedToGuardCallsDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that destroying the move destination invokes the transferred cleanup callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenAScopeExit(); // and given a new ScopeExit move constructed from another @@ -161,6 +197,12 @@ TEST_F(ScopeExitFixture, DestroyingMoveConstructedMovedToGuardCallsDestructionHa TEST_F(ScopeExitFixture, MoveAssigningGuardCallsDestructionHandlerOnMovedToGuard) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty( + "Description", + "Check that move assignment invokes the destination's former callback and transfers the source callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenTwoScopeExits(); // When move assigning one ScopeExit to another @@ -173,6 +215,11 @@ TEST_F(ScopeExitFixture, MoveAssigningGuardCallsDestructionHandlerOnMovedToGuard TEST_F(ScopeExitFixture, DestroyingMoveAssignedMovedFromGuardDoesNotCallDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that destroying a move-assigned source guard does not invoke its transferred callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenTwoScopeExits(); // and given that one ScopeExit was move assigned to another @@ -187,6 +234,11 @@ TEST_F(ScopeExitFixture, DestroyingMoveAssignedMovedFromGuardDoesNotCallDestruct TEST_F(ScopeExitFixture, DestroyingMoveAssignedMovedToGuardCallsDestructionHandler) { + RecordProperty("PartiallyVerifies", "comp_req__scope_exit__cleanup_ownership"); + RecordProperty("Description", + "Check that destroying a move-assigned destination invokes the transferred callback."); + RecordProperty("TestType", "requirements-based"); + RecordProperty("DerivationTechnique", "equivalence-classes"); GivenTwoScopeExits(); // and given that one ScopeExit was move assigned to another