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