Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ use_repo(
# LLVM toolchain (used for clang-format and clang-tidy)
bazel_dep(name = "toolchains_llvm", version = "1.4.0", dev_dependency = True)

llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm")
llvm = use_extension("@toolchains_llvm//toolchain/extensions:llvm.bzl", "llvm", dev_dependency = True)
llvm.toolchain(
llvm_version = "15.0.2",
stdlib = {"linux-x86_64": "stdc++"},
Expand Down
2 changes: 1 addition & 1 deletion MODULE.bazel.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions score/launch_manager/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,13 @@ alias(
actual = "//score/launch_manager/src/lifecycle_client:applicationcontext_mock",
)

alias(
cc_library(
name = "lifecycle_mock_cc",
actual = "//score/launch_manager/src/lifecycle_client:lifecycle_mock",
testonly = True,
deps = [
"//score/launch_manager/src/lifecycle_client:lifecycle_mock",
"//score/launch_manager/src/lifecycle_client:report_running_mock",
],
)

alias(
Expand Down
11 changes: 11 additions & 0 deletions score/launch_manager/src/lifecycle_client/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ cc_library(
visibility = ["//score/launch_manager:__subpackages__"],
deps = [
":applicationcontext_mock",
":lifecyclemanager",
Comment thread
NicolasFussberger marked this conversation as resolved.
"@googletest//:gtest",
"@score_baselibs//score/os/utils:signal",
"@score_baselibs//score/os/utils/mocklib:signal_mock",
Expand All @@ -209,6 +210,16 @@ cc_library(
],
)

cc_test(
name = "lifecycle_mocks_UT",
srcs = ["src/lifecycle_mocks_UT.cpp"],
deps = [
":runapplication",
"//score/launch_manager:lifecycle_mock_cc",
"@googletest//:gtest_main",
],
)

cc_test(
name = "runapplication_UT",
srcs = ["src/runapplication_UT.cpp"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,19 +60,38 @@ score::mw::lifecycle::ApplicationContextMock::~ApplicationContextMock()
}

score::mw::lifecycle::ApplicationContext::ApplicationContext(const std::int32_t argc, const char* const argv[])
: m_args(argv, argv + argc), m_app_path(argc > 0 ? std::string{argv[0]} : std::string{})
{
auto& constructor_callback = GetConstructorCallback();
constructor_callback(argc, argv);
if (constructor_callback)
{
constructor_callback(argc, argv);
}
}

const std::vector<std::string>& score::mw::lifecycle::ApplicationContext::get_arguments() const noexcept
{
auto& get_arguments_callback = GetGetArgumentsCallback();
return get_arguments_callback();
if (get_arguments_callback)
{
return get_arguments_callback();
}
return m_args;
}

std::string score::mw::lifecycle::ApplicationContext::get_argument(const std::string_view flag) const noexcept
{
auto& get_argument_callback = GetGetArgumentCallback();
return get_argument_callback(flag);
if (get_argument_callback)
{
return get_argument_callback(flag);
}
for (auto it = m_args.cbegin(); it != m_args.cend(); ++it)
{
if (it->rfind(flag, 0) == 0)
{
return *it;
}
}
return {};
}
182 changes: 182 additions & 0 deletions score/launch_manager/src/lifecycle_client/src/lifecycle_mocks_UT.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/********************************************************************************
* 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
********************************************************************************/

/// @file cpp_lifecycle_app_UT.cpp
/// @brief Unit tests for cpp_lifecycle_app that demonstrate how to use
/// LifeCycleManagerMock and ApplicationContextMock from external packages.
///
/// These tests guard the mock API against accidental regressions and serve as
/// a reference for other components (e.g. config_management) that depend on the
/// lifecycle mocks in their own test suites.

#include "score/mw/lifecycle/applicationcontextmock.h"
#include "score/mw/lifecycle/lifecyclemanagermock.h"
#include "score/mw/lifecycle/runapplication.h"

#include <gmock/gmock.h>
#include <gtest/gtest.h>

#include <string>
#include <vector>

using ::testing::_;
using ::testing::AnyNumber;
using ::testing::Eq;
using ::testing::Return;
using ::testing::ReturnRef;

namespace
{

/// Minimal Application that records which argument was retrieved during Initialize().
class ArgCapturingApp final : public score::mw::lifecycle::Application
{
public:
std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext& ctx) override
{
captured_arg_ = ctx.get_argument("--config");
return captured_arg_.empty() ? EXIT_FAILURE : EXIT_SUCCESS;
}

std::int32_t Run(const score::cpp::stop_token&) override
{
return EXIT_SUCCESS;
}

std::string captured_arg_;
};

/// Minimal Application that always initialises and runs successfully.
class AlwaysOkApp final : public score::mw::lifecycle::Application
{
public:
std::int32_t Initialize(const score::mw::lifecycle::ApplicationContext&) override
{
return EXIT_SUCCESS;
}

std::int32_t Run(const score::cpp::stop_token&) override
{
return EXIT_SUCCESS;
}
};

// ---------------------------------------------------------------------------
// Test fixture
// ---------------------------------------------------------------------------

/// @brief Tests for lifecycle mock usage from an external-package perspective.
///
/// The mocks must be constructed *before* any score::mw::lifecycle::Run<> object
/// so that the static link-time substitution callbacks are installed in time.
class LifecycleMockUsageTest : public ::testing::Test
{
protected:
score::mw::lifecycle::ApplicationContextMock context_mock_;
score::mw::lifecycle::LifeCycleManagerMock lifecycle_mock_;

static constexpr int kArgc = 1;
const char* kArgv[1] = {"test_app"};

void SetUp() override
{
EXPECT_CALL(context_mock_, ctor(_, _)).Times(AnyNumber());
EXPECT_CALL(lifecycle_mock_, ctor()).Times(AnyNumber());
EXPECT_CALL(lifecycle_mock_, dtor()).Times(AnyNumber());
}
};

} // namespace

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

/// Verify that LifeCycleManagerMock::run() is called and its return value
/// is propagated through AsPosixProcess() — basic mock wiring smoke test.
TEST_F(LifecycleMockUsageTest, MockRunReturnValueIsForwardedToCaller)
{
RecordProperty("Description",
"LifeCycleManagerMock::run() return value reaches AsPosixProcess().");

EXPECT_CALL(lifecycle_mock_, run(_, _)).WillOnce(Return(EXIT_SUCCESS));

score::mw::lifecycle::Run<AlwaysOkApp> runner(kArgc, kArgv);
EXPECT_EQ(runner.AsPosixProcess(), EXIT_SUCCESS);
}

/// Verify that a non-zero exit code coming from the mock is forwarded correctly,
/// matching the behaviour that external callers (e.g. config_management) rely on.
TEST_F(LifecycleMockUsageTest, MockRunNonZeroReturnValueIsForwarded)
{
RecordProperty("Description",
"A non-zero return from LifeCycleManagerMock::run() is forwarded unchanged.");

EXPECT_CALL(lifecycle_mock_, run(_, _)).WillOnce(Return(EXIT_FAILURE));

score::mw::lifecycle::Run<AlwaysOkApp> runner(kArgc, kArgv);
EXPECT_EQ(runner.AsPosixProcess(), EXIT_FAILURE);
}

/// Verify that ApplicationContextMock::get_argument() is called during Initialize()
/// and that the returned value is visible to the application logic.
/// This is the pattern used by config_management's unit tests to simulate
/// command-line arguments without spawning a real process.
TEST_F(LifecycleMockUsageTest, ApplicationReceivesArgumentFromContextMock)
{
RecordProperty("Description",
"ApplicationContextMock::get_argument() result is visible inside Initialize().");

const std::string expected_config{"--config=/etc/app.cfg"};

EXPECT_CALL(lifecycle_mock_, run(_, _))
.WillOnce([&](score::mw::lifecycle::Application& app,
const score::mw::lifecycle::ApplicationContext& ctx) {
EXPECT_CALL(context_mock_, get_argument(std::string_view{"--config"}))
.WillOnce(Return(expected_config));
return app.Initialize(ctx);
});

score::mw::lifecycle::Run<ArgCapturingApp> runner(kArgc, kArgv);
EXPECT_EQ(runner.AsPosixProcess(), EXIT_SUCCESS);
}

/// Verify that ApplicationContextMock::get_arguments() can provide a full
/// argument list — mirroring how config_management passes a simulated argv.
TEST_F(LifecycleMockUsageTest, ContextMockProvidesArgumentList)
{
RecordProperty("Description",
"ApplicationContextMock::get_arguments() returns the configured argument list.");

const std::vector<std::string> expected_args{"my_app", "--verbose", "--config=/tmp/cfg"};

EXPECT_CALL(context_mock_, get_arguments()).WillRepeatedly(ReturnRef(expected_args));

const auto& args = context_mock_.get_arguments();
EXPECT_EQ(args, expected_args);
}

/// Verify that exactly one LifeCycleManager is constructed and destroyed per
/// AsPosixProcess() call — important for callers that own RAII resources.
TEST_F(LifecycleMockUsageTest, ExactlyOneLifecycleManagerLifetimePerRun)
{
RecordProperty("Description",
"Exactly one ctor/dtor pair of LifeCycleManager occurs per AsPosixProcess().");

EXPECT_CALL(lifecycle_mock_, ctor()).Times(1);
EXPECT_CALL(lifecycle_mock_, dtor()).Times(1);
EXPECT_CALL(lifecycle_mock_, run(_, _)).WillOnce(Return(EXIT_SUCCESS));

score::mw::lifecycle::Run<AlwaysOkApp> runner(kArgc, kArgv);
runner.AsPosixProcess();
}
Loading