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 
90 // Ensure we can successfully create and throw an sdbusplus event.
91 TEST_F(TestLogManagerDbus, GenerateSimpleEvent)
92 {
93     EXPECT_THROW(
94         { throw LoggingCleared("NUMBER_OF_LOGS", 1); }, LoggingCleared);
95     return;
96 }
97 
98 // Call the synchronous version of the commit function and verify that the
99 // daemon gives us a path.
100 TEST_F(TestLogManagerDbus, CallCommitSync)
101 {
102     auto path = lg2::commit(LoggingCleared("NUMBER_OF_LOGS", 3));
103     ASSERT_FALSE(path.str.empty());
104     EXPECT_THAT(path.str,
105                 ::testing::StartsWith(
106                     std::filesystem::path(LoggingEntry::namespace_path::value) /
107                     LoggingEntry::namespace_path::entry));
108 }
109 
110 // Call the asynchronous version of the commit function and verify that the
111 // metadata is saved correctly.
112 TEST_F(TestLogManagerDbus, CallCommitAsync)
113 {
114     sdbusplus::message::object_path path{};
115     std::string log_count{};
116     pid_t pid = 0;
117     std::string source_file{};
118 
119     auto create_log = [&, this]() -> sdbusplus::async::task<> {
120         // Log an event.
121         path = co_await lg2::commit(data->client_ctx,
122                                     LoggingCleared("NUMBER_OF_LOGS", 6));
123 
124         // Grab the additional data.
125         auto additionalData = co_await LoggingEntry(data->client_ctx)
126                                   .service(Entry::default_service)
127                                   .path(path.str)
128                                   .additional_data();
129 
130         // Extract the NUMBER_OF_LOGS, PID, and CODE_FILE.
131         for (const auto& value : additionalData)
132         {
133             auto getValue = [&value]() {
134                 return value.substr(value.find_first_of('=') + 1);
135             };
136 
137             if (value.starts_with("NUMBER_OF_LOGS="))
138             {
139                 log_count = getValue();
140             }
141             if (value.starts_with("_PID="))
142             {
143                 pid = std::stoull(getValue());
144             }
145             if (value.starts_with("_CODE_FILE="))
146             {
147                 source_file = getValue();
148             }
149         }
150 
151         co_return;
152     };
153 
154     run(create_log());
155 
156     ASSERT_FALSE(path.str.empty());
157     ASSERT_FALSE(log_count.empty());
158 
159     EXPECT_THAT(path.str,
160                 ::testing::StartsWith(
161                     std::filesystem::path(LoggingEntry::namespace_path::value) /
162                     LoggingEntry::namespace_path::entry));
163 
164     EXPECT_EQ(log_count, "6");
165     EXPECT_EQ(pid, getpid());
166     EXPECT_EQ(source_file, std::source_location::current().file_name());
167 }
168 
169 } // namespace phosphor::logging::test
170