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.
SetUp()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.
TearDown()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>
run(T && 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 {
fixture_dataphosphor::logging::test::TestLogManagerDbus::fixture_data54 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
~fixture_dataphosphor::logging::test::TestLogManagerDbus::fixture_data65 ~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
stopphosphor::logging::test::TestLogManagerDbus::fixture_data73 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";
last_journal_entry()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.
TEST_F(TestLogManagerDbus,GenerateSimpleEvent)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.
TEST_F(TestLogManagerDbus,CallCommitSync)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.
TEST_F(TestLogManagerDbus,CallCommitAsync)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