1 #include "config.h" 2 3 #include "log_manager.hpp" 4 #include "paths.hpp" 5 6 #include <phosphor-logging/commit.hpp> 7 #include <sdbusplus/async.hpp> 8 #include <sdbusplus/server/manager.hpp> 9 #include <xyz/openbmc_project/Logging/Entry/client.hpp> 10 #include <xyz/openbmc_project/Logging/event.hpp> 11 12 #include <thread> 13 14 #include <gmock/gmock.h> 15 #include <gtest/gtest.h> 16 17 namespace phosphor::logging::test 18 { 19 using LoggingCleared = sdbusplus::event::xyz::openbmc_project::Logging::Cleared; 20 using LoggingEntry = sdbusplus::client::xyz::openbmc_project::logging::Entry<>; 21 22 // Fixture to spawn the log-manager for dbus-based testing. 23 class TestLogManagerDbus : public ::testing::Test 24 { 25 protected: 26 // Create the daemon and sdbusplus::async::contexts. 27 void SetUp() override 28 { 29 // The daemon requires directories to be created first. 30 std::filesystem::create_directories(phosphor::logging::paths::error()); 31 32 data = std::make_unique<fixture_data>(); 33 } 34 35 // Stop the daemon, etc. 36 void TearDown() override 37 { 38 data.reset(); 39 } 40 41 /** Run a client task, wait for it to complete, and stop daemon. */ 42 template <typename T> 43 void run(T&& t) 44 { 45 data->client_ctx.spawn(std::move(t) | stdexec::then([this]() { 46 data->stop(data->client_ctx); 47 })); 48 data->client_ctx.run(); 49 } 50 51 // Data for the fixture. 52 struct fixture_data 53 { 54 fixture_data() : 55 client_ctx(), server_ctx(), objManager(server_ctx, OBJ_LOGGING), 56 iMgr(server_ctx, OBJ_INTERNAL), mgr(server_ctx, OBJ_LOGGING, iMgr) 57 { 58 // Create a thread for the daemon. 59 task = std::thread([this]() { 60 server_ctx.request_name(BUSNAME_LOGGING); 61 server_ctx.run(); 62 }); 63 } 64 65 ~fixture_data() 66 { 67 // Stop the server and wait for the thread to exit. 68 stop(server_ctx); 69 task.join(); 70 } 71 72 // Spawn a task to gracefully shutdown an sdbusplus::async::context 73 static void stop(sdbusplus::async::context& ctx) 74 { 75 ctx.spawn(stdexec::just() | 76 stdexec::then([&ctx]() { ctx.request_stop(); })); 77 } 78 79 sdbusplus::async::context client_ctx; 80 sdbusplus::async::context server_ctx; 81 sdbusplus::server::manager_t objManager; 82 internal::Manager iMgr; 83 Manager mgr; 84 std::thread task; 85 }; 86 87 std::unique_ptr<fixture_data> data; 88 89 static constexpr auto journal_unavailable = "UNAVAILABLE"; 90 std::string last_journal_entry() 91 { 92 if constexpr (LG2_COMMIT_JOURNAL) 93 { 94 // When running under Docker, the journal is not available and 95 // sd-journal calls just silently pass. Return a string to make 96 // it obvious. 97 if (!std::filesystem::exists("/run/systemd/journal/socket")) 98 { 99 return journal_unavailable; 100 } 101 102 sd_journal* j = nullptr; 103 104 sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY); 105 sd_journal_add_match(j, "SYSLOG_IDENTIFIER=test_manager_dbus_tests", 106 SIZE_MAX); 107 108 SD_JOURNAL_FOREACH_BACKWARDS(j) 109 { 110 const char* data = nullptr; 111 size_t length = 0; 112 113 sd_journal_get_data(j, "MESSAGE", (const void**)&data, &length); 114 115 std::string entry(data, length); 116 if (entry.contains("OPENBMC_MESSAGE_ID=")) 117 { 118 return entry; 119 } 120 } 121 } 122 123 return ""; 124 } 125 }; 126 127 // Ensure we can successfully create and throw an sdbusplus event. 128 TEST_F(TestLogManagerDbus, GenerateSimpleEvent) 129 { 130 EXPECT_THROW( 131 { throw LoggingCleared("NUMBER_OF_LOGS", 1); }, LoggingCleared); 132 return; 133 } 134 135 // Call the synchronous version of the commit function and verify that the 136 // daemon gives us a path. 137 TEST_F(TestLogManagerDbus, CallCommitSync) 138 { 139 auto path = lg2::commit(LoggingCleared("NUMBER_OF_LOGS", 3)); 140 141 if constexpr (LG2_COMMIT_DBUS) 142 { 143 ASSERT_FALSE(path.str.empty()); 144 EXPECT_THAT( 145 path.str, 146 ::testing::StartsWith( 147 std::filesystem::path(LoggingEntry::namespace_path::value) / 148 LoggingEntry::namespace_path::entry)); 149 } 150 151 if constexpr (LG2_COMMIT_JOURNAL) 152 { 153 auto entry = last_journal_entry(); 154 if (entry != journal_unavailable) 155 { 156 EXPECT_THAT(entry, ::testing::HasSubstr( 157 "\"xyz.openbmc_project.Logging.Cleared\":")); 158 EXPECT_THAT(entry, ::testing::HasSubstr("\"NUMBER_OF_LOGS\":3")); 159 } 160 } 161 } 162 163 // Call the asynchronous version of the commit function and verify that the 164 // metadata is saved correctly. 165 TEST_F(TestLogManagerDbus, CallCommitAsync) 166 { 167 sdbusplus::message::object_path path{}; 168 std::string log_count{}; 169 pid_t pid = 0; 170 std::string source_file{}; 171 172 auto create_log = [&, this]() -> sdbusplus::async::task<> { 173 // Log an event. 174 path = co_await lg2::commit(data->client_ctx, 175 LoggingCleared("NUMBER_OF_LOGS", 6)); 176 177 if constexpr (LG2_COMMIT_DBUS) 178 { 179 // Grab the additional data. 180 auto additionalData = co_await LoggingEntry(data->client_ctx) 181 .service(Entry::default_service) 182 .path(path.str) 183 .additional_data(); 184 185 // Extract the NUMBER_OF_LOGS, PID, and CODE_FILE. 186 for (const auto& value : additionalData) 187 { 188 auto getValue = [&value]() { 189 return value.substr(value.find_first_of('=') + 1); 190 }; 191 192 if (value.starts_with("NUMBER_OF_LOGS=")) 193 { 194 log_count = getValue(); 195 } 196 if (value.starts_with("_PID=")) 197 { 198 pid = std::stoull(getValue()); 199 } 200 if (value.starts_with("_CODE_FILE=")) 201 { 202 source_file = getValue(); 203 } 204 } 205 } 206 207 co_return; 208 }; 209 210 run(create_log()); 211 212 if constexpr (LG2_COMMIT_DBUS) 213 { 214 ASSERT_FALSE(path.str.empty()); 215 ASSERT_FALSE(log_count.empty()); 216 217 EXPECT_THAT( 218 path.str, 219 ::testing::StartsWith( 220 std::filesystem::path(LoggingEntry::namespace_path::value) / 221 LoggingEntry::namespace_path::entry)); 222 223 EXPECT_EQ(log_count, "6"); 224 EXPECT_EQ(pid, getpid()); 225 EXPECT_EQ(source_file, std::source_location::current().file_name()); 226 } 227 228 if constexpr (LG2_COMMIT_JOURNAL) 229 { 230 auto entry = last_journal_entry(); 231 if (entry != journal_unavailable) 232 { 233 EXPECT_THAT(entry, ::testing::HasSubstr( 234 "\"xyz.openbmc_project.Logging.Cleared\":")); 235 EXPECT_THAT(entry, ::testing::HasSubstr("\"NUMBER_OF_LOGS\":6")); 236 } 237 } 238 } 239 240 } // namespace phosphor::logging::test 241