Files
logos-liblogos/tests/test_app_lifecycle.cpp
T
Dario LipicarandClaude Opus 4.8 b8de686bce fix(tests): avoid core boot during gtest discovery timeout (#160)
* fix(tests): avoid core boot during gtest discovery timeout

gtest_discover_tests defaulted to POST_BUILD discovery mode, which runs
the freshly-built logos_core_tests binary with --gtest_list_tests at build
time. test_app_lifecycle.cpp's main() called logos_core_init() before
gtest parsed its flags, so even a bare test-listing booted the full Qt
core (QCoreApplication + subprocess/socket/IOKit) and loaded the entire
dylib closure. In the Nix sandbox this blew past the 5s
TEST_DISCOVERY_TIMEOUT, killing the binary with empty output and failing
the whole derivation (cascading up to run-logos-standalone-ui).

Fix, two parts:
- CMakeLists.txt: use DISCOVERY_MODE PRE_TEST so enumeration is deferred
  to ctest time; the binary never runs during the build. This alone
  deterministically fixes the failure.
- test_app_lifecycle.cpp: run InitGoogleTest first and return early when
  only listing tests, before logos_core_init(). Safety net so listing
  never boots the core regardless of discovery mode.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(tests): use GTEST_FLAG_GET for list-tests guard

Read the list_tests flag via GTEST_FLAG_GET(list_tests) instead of
::testing::GTEST_FLAG(list_tests). GTEST_FLAG(name) is only a plain bool
in the non-Abseil gtest build; with the Abseil flags backend it is an
absl::Flag object, so the raw macro is not portable in a boolean context.
GTEST_FLAG_GET dispatches correctly for both backends.

The macro already qualifies with ::testing:: internally, so it must be
used unqualified — prefixing it (::testing::GTEST_FLAG_GET) expands to a
doubled ::testing::::testing:: token and fails to compile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 16:03:38 -03:00

183 lines
6.3 KiB
C++

#include <gtest/gtest.h>
#include "logos_core.h"
#include "qt_test_adapter.h"
#include <cstring>
#include <string>
static void clearModuleState() {
logos_core_terminate_all();
logos_core_clear();
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
// When only listing tests (e.g. POST_BUILD test discovery), don't boot the
// full core — just enumerate. Booting Qt/subprocess/IOKit here can blow past
// the test-discovery timeout in sandboxed builds.
// GTEST_FLAG_GET already qualifies with ::testing:: internally and works
// with both the Abseil and non-Abseil gtest flag backends — do not prefix it.
if (GTEST_FLAG_GET(list_tests)) {
return RUN_ALL_TESTS();
}
logos_core_init(argc, argv);
int result = RUN_ALL_TESTS();
logos_core_cleanup();
return result;
}
class AppLifecycleTest : public ::testing::Test {
protected:
void SetUp() override {
clearModuleState();
}
void TearDown() override {
clearModuleState();
}
};
// =============================================================================
// Initialization Tests
// =============================================================================
TEST_F(AppLifecycleTest, Init_LibraryIsUsableAfterInit) {
EXPECT_EQ(logos_core_get_modules_dirs_count(), 0);
}
// =============================================================================
// Module Directory Tests
// =============================================================================
TEST_F(AppLifecycleTest, AddModulesDir_SetsDirectory) {
const char* testDir = "/test/modules";
logos_core_add_modules_dir(testDir);
ASSERT_EQ(logos_core_get_modules_dirs_count(), 1);
char* dir = logos_core_get_modules_dir_at(0);
ASSERT_NE(dir, nullptr);
EXPECT_EQ(std::string(dir), std::string(testDir));
delete[] dir;
}
TEST_F(AppLifecycleTest, AddModulesDir_AppendsDirectory) {
logos_core_add_modules_dir("/dir1");
logos_core_add_modules_dir("/dir2");
ASSERT_EQ(logos_core_get_modules_dirs_count(), 2);
char* d0 = logos_core_get_modules_dir_at(0);
char* d1 = logos_core_get_modules_dir_at(1);
ASSERT_NE(d0, nullptr);
ASSERT_NE(d1, nullptr);
EXPECT_EQ(std::string(d0), "/dir1");
EXPECT_EQ(std::string(d1), "/dir2");
delete[] d0;
delete[] d1;
}
TEST_F(AppLifecycleTest, AddModulesDir_NoDuplicates) {
logos_core_add_modules_dir("/test");
logos_core_add_modules_dir("/test");
EXPECT_EQ(logos_core_get_modules_dirs_count(), 1);
}
// =============================================================================
// Cleanup Tests
// =============================================================================
TEST_F(AppLifecycleTest, Cleanup_ClearsGlobals) {
logos_core_register_module("test", "/path/to/test");
logos_core_add_modules_dir("/test");
clearModuleState();
char** loaded = logos_core_get_loaded_modules();
ASSERT_NE(loaded, nullptr);
EXPECT_EQ(loaded[0], nullptr);
delete[] loaded;
char** known = logos_core_get_known_modules();
ASSERT_NE(known, nullptr);
EXPECT_EQ(known[0], nullptr);
delete[] known;
}
TEST_F(AppLifecycleTest, Cleanup_ClearsState) {
EXPECT_TRUE(true) << "Cleanup behavior is tested indirectly through integration tests";
}
// =============================================================================
// Start Function Tests
// =============================================================================
TEST_F(AppLifecycleTest, Start_UsesCustomModulesDirs) {
logos_core_add_modules_dir("/custom/modules");
logos_core_start();
ASSERT_EQ(logos_core_get_modules_dirs_count(), 1);
char* dir = logos_core_get_modules_dir_at(0);
ASSERT_NE(dir, nullptr);
EXPECT_EQ(std::string(dir), "/custom/modules");
delete[] dir;
}
// =============================================================================
// Access Policy Tests
// =============================================================================
//
// logos_core_set_access_policy stores the policy; core parses it and
// registers the concrete per-target restrictions with capability_module
// once that module loads (inside logos_core_start). The parser is unit-
// tested in test_access_policy.cpp, and the enforcement (deny token
// issuance for a disallowed caller) is tested in capability_module's own
// suite. Here we only pin the C-API setter contract that holds without a
// running capability_module: the call accepts a well-formed policy, an
// empty string, and NULL (the documented "clear" signal) without crashing
// or aborting, and without disturbing unrelated core state.
TEST_F(AppLifecycleTest, SetAccessPolicy_AcceptsValidPolicyWithoutCrashing) {
const char* policy =
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{"
"\"package_manager\":{\"allowedCallers\":[\"package_manager_ui\"]},"
"\"package_downloader\":{\"allowedCallers\":[\"package_manager_ui\"]}}}";
// No-op today: the only contract is "doesn't crash, doesn't throw".
EXPECT_NO_THROW(logos_core_set_access_policy(policy));
}
TEST_F(AppLifecycleTest, SetAccessPolicy_AcceptsEmptyStringAsClear) {
EXPECT_NO_THROW(logos_core_set_access_policy(""));
}
TEST_F(AppLifecycleTest, SetAccessPolicy_AcceptsNullAsClear) {
// Unlike the module-name setters, this one must NOT abort on NULL —
// NULL is the documented "clear the policy" signal.
EXPECT_NO_THROW(logos_core_set_access_policy(nullptr));
}
TEST_F(AppLifecycleTest, SetAccessPolicy_IsIdempotentAcrossRepeatedCalls) {
// Setting then clearing then re-setting must be safe in any order.
EXPECT_NO_THROW(logos_core_set_access_policy("{\"version\":1}"));
EXPECT_NO_THROW(logos_core_set_access_policy(nullptr));
EXPECT_NO_THROW(logos_core_set_access_policy(""));
EXPECT_NO_THROW(logos_core_set_access_policy("{\"version\":1}"));
}
TEST_F(AppLifecycleTest, SetAccessPolicy_DoesNotDisturbModulesDirs) {
logos_core_add_modules_dir("/custom/modules");
ASSERT_EQ(logos_core_get_modules_dirs_count(), 1);
logos_core_set_access_policy(
"{\"version\":1,\"mode\":\"enforce\",\"restrictions\":{}}");
// The policy setter touches nothing else in core state.
EXPECT_EQ(logos_core_get_modules_dirs_count(), 1);
char* dir = logos_core_get_modules_dir_at(0);
ASSERT_NE(dir, nullptr);
EXPECT_EQ(std::string(dir), "/custom/modules");
delete[] dir;
}