blob: 8369b051a2e950b3fab3a9947618b5accd2e95cd [file] [log] [blame]
Matt Spinler97f7abc2019-11-06 09:40:23 -06001/**
2 * Copyright © 2019 IBM Corporation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
Matt Spinlerb8323632019-09-20 15:11:04 -050016#include "elog_entry.hpp"
Matt Spinler131870c2019-09-25 13:29:04 -050017#include "extensions/openpower-pels/generic.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050018#include "extensions/openpower-pels/pel.hpp"
Matt Spinleraa659472019-10-23 09:26:48 -050019#include "mocks.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050020#include "pel_utils.hpp"
21
22#include <filesystem>
23#include <fstream>
24
25#include <gtest/gtest.h>
26
27namespace fs = std::filesystem;
28using namespace openpower::pels;
Matt Spinler0a90a852020-06-04 13:18:27 -050029using ::testing::_;
William A. Kennington IIIb41fa542021-05-29 14:45:16 -070030using ::testing::DoAll;
Matt Spinler56ad2a02020-03-26 14:00:52 -050031using ::testing::NiceMock;
Matt Spinler677381b2020-01-23 10:04:29 -060032using ::testing::Return;
Matt Spinler0a90a852020-06-04 13:18:27 -050033using ::testing::SetArgReferee;
Matt Spinlercb6b0592019-07-16 15:58:51 -050034
35class PELTest : public CleanLogID
Patrick Williams2544b412022-10-04 08:41:06 -050036{};
Matt Spinlercb6b0592019-07-16 15:58:51 -050037
Matt Spinler5b289b22020-03-26 14:27:19 -050038fs::path makeTempDir()
39{
40 char path[] = "/tmp/tempdirXXXXXX";
41 std::filesystem::path dir = mkdtemp(path);
42 return dir;
43}
44
45int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
46{
47 static size_t count = 0;
48 fs::path path = dir / (std::string{"file"} + std::to_string(count));
49 std::ofstream stream{path};
50 count++;
51
52 stream.write(reinterpret_cast<const char*>(data.data()), data.size());
53 stream.close();
54
55 FILE* fp = fopen(path.c_str(), "r");
56 return fileno(fp);
57}
58
Matt Spinlercb6b0592019-07-16 15:58:51 -050059TEST_F(PELTest, FlattenTest)
60{
Matt Spinler42828bd2019-10-11 10:39:30 -050061 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler42828bd2019-10-11 10:39:30 -050062 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050063
64 // Check a few fields
65 EXPECT_TRUE(pel->valid());
66 EXPECT_EQ(pel->id(), 0x80818283);
67 EXPECT_EQ(pel->plid(), 0x50515253);
Matt Spinler97d19b42019-10-29 11:34:03 -050068 EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
69 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
Matt Spinlercb6b0592019-07-16 15:58:51 -050070
71 // Test that data in == data out
72 auto flattenedData = pel->data();
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060073 EXPECT_EQ(data, flattenedData);
74 EXPECT_EQ(flattenedData.size(), pel->size());
Matt Spinlercb6b0592019-07-16 15:58:51 -050075}
76
77TEST_F(PELTest, CommitTimeTest)
78{
Matt Spinler42828bd2019-10-11 10:39:30 -050079 auto data = pelDataFactory(TestPELType::pelSimple);
80 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050081
82 auto origTime = pel->commitTime();
83 pel->setCommitTime();
84 auto newTime = pel->commitTime();
85
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060086 EXPECT_NE(origTime, newTime);
Matt Spinlercb6b0592019-07-16 15:58:51 -050087
88 // Make a new PEL and check new value is still there
89 auto newData = pel->data();
90 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060091 EXPECT_EQ(newTime, newPel->commitTime());
Matt Spinlercb6b0592019-07-16 15:58:51 -050092}
93
94TEST_F(PELTest, AssignIDTest)
95{
Matt Spinler42828bd2019-10-11 10:39:30 -050096 auto data = pelDataFactory(TestPELType::pelSimple);
97 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050098
99 auto origID = pel->id();
100 pel->assignID();
101 auto newID = pel->id();
102
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600103 EXPECT_NE(origID, newID);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500104
105 // Make a new PEL and check new value is still there
106 auto newData = pel->data();
107 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600108 EXPECT_EQ(newID, newPel->id());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500109}
110
111TEST_F(PELTest, WithLogIDTest)
112{
Matt Spinler42828bd2019-10-11 10:39:30 -0500113 auto data = pelDataFactory(TestPELType::pelSimple);
114 auto pel = std::make_unique<PEL>(data, 0x42);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500115
116 EXPECT_TRUE(pel->valid());
117 EXPECT_EQ(pel->obmcLogID(), 0x42);
118}
119
120TEST_F(PELTest, InvalidPELTest)
121{
Matt Spinler42828bd2019-10-11 10:39:30 -0500122 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500123
124 // Too small
Matt Spinler42828bd2019-10-11 10:39:30 -0500125 data.resize(PrivateHeader::flattenedSize());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500126
Matt Spinler42828bd2019-10-11 10:39:30 -0500127 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500128
Matt Spinler97d19b42019-10-29 11:34:03 -0500129 EXPECT_TRUE(pel->privateHeader().valid());
130 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500131 EXPECT_FALSE(pel->valid());
132
Matt Spinlercb6b0592019-07-16 15:58:51 -0500133 // Now corrupt the private header
Matt Spinler42828bd2019-10-11 10:39:30 -0500134 data = pelDataFactory(TestPELType::pelSimple);
135 data.at(0) = 0;
136 pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500137
Matt Spinler97d19b42019-10-29 11:34:03 -0500138 EXPECT_FALSE(pel->privateHeader().valid());
139 EXPECT_TRUE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500140 EXPECT_FALSE(pel->valid());
141}
142
143TEST_F(PELTest, EmptyDataTest)
144{
145 std::vector<uint8_t> data;
146 auto pel = std::make_unique<PEL>(data);
147
Matt Spinler97d19b42019-10-29 11:34:03 -0500148 EXPECT_FALSE(pel->privateHeader().valid());
149 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500150 EXPECT_FALSE(pel->valid());
151}
Matt Spinlerb8323632019-09-20 15:11:04 -0500152
153TEST_F(PELTest, CreateFromRegistryTest)
154{
155 message::Entry regEntry;
156 uint64_t timestamp = 5;
157
158 regEntry.name = "test";
159 regEntry.subsystem = 5;
160 regEntry.actionFlags = 0xC000;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500161 regEntry.src.type = 0xBD;
162 regEntry.src.reasonCode = 0x1234;
Matt Spinlerb8323632019-09-20 15:11:04 -0500163
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600164 std::vector<std::string> data{"KEY1=VALUE1"};
165 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500166 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600167 NiceMock<MockJournal> journal;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500168 PelFFDC ffdc;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500169
Matt Spinler56ad2a02020-03-26 14:00:52 -0500170 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600171 ad, ffdc, dataIface, journal};
Matt Spinlerb8323632019-09-20 15:11:04 -0500172
173 EXPECT_TRUE(pel.valid());
Matt Spinler97d19b42019-10-29 11:34:03 -0500174 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
175 EXPECT_EQ(pel.userHeader().severity(), 0x40);
Matt Spinlerb8323632019-09-20 15:11:04 -0500176
Matt Spinlerbd716f02019-10-15 10:54:11 -0500177 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
178 "BD051234 ");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600179
180 // Check that certain optional sections have been created
181 size_t mtmsCount = 0;
182 size_t euhCount = 0;
183 size_t udCount = 0;
184
185 for (const auto& section : pel.optionalSections())
186 {
187 if (section->header().id ==
188 static_cast<uint16_t>(SectionID::failingMTMS))
189 {
190 mtmsCount++;
191 }
192 else if (section->header().id ==
193 static_cast<uint16_t>(SectionID::extendedUserHeader))
194 {
195 euhCount++;
196 }
197 else if (section->header().id ==
198 static_cast<uint16_t>(SectionID::userData))
199 {
200 udCount++;
201 }
202 }
203
204 EXPECT_EQ(mtmsCount, 1);
205 EXPECT_EQ(euhCount, 1);
206 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600207 ASSERT_FALSE(pel.isHwCalloutPresent());
Matt Spinler1f93c592020-09-10 10:43:08 -0500208
209 {
210 // The same thing, but without the action flags specified
211 // in the registry, so the constructor should set them.
212 regEntry.actionFlags = std::nullopt;
213
Patrick Williams075c7922024-08-16 15:19:49 -0400214 PEL pel2{regEntry, 42,
215 timestamp, phosphor::logging::Entry::Level::Error,
216 ad, ffdc,
217 dataIface, journal};
Matt Spinler1f93c592020-09-10 10:43:08 -0500218
219 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
220 }
Matt Spinlerb8323632019-09-20 15:11:04 -0500221}
Matt Spinler131870c2019-09-25 13:29:04 -0500222
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500223// Test that when the AdditionalData size is over 16KB that
224// the PEL that's created is exactly 16KB since the UserData
225// section that contains all that data was pruned.
226TEST_F(PELTest, CreateTooBigADTest)
227{
228 message::Entry regEntry;
229 uint64_t timestamp = 5;
230
231 regEntry.name = "test";
232 regEntry.subsystem = 5;
233 regEntry.actionFlags = 0xC000;
234 regEntry.src.type = 0xBD;
235 regEntry.src.reasonCode = 0x1234;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500236 PelFFDC ffdc;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500237
238 // Over the 16KB max PEL size
239 std::string bigAD{"KEY1="};
240 bigAD += std::string(17000, 'G');
241
242 std::vector<std::string> data{bigAD};
243 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500244 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600245 NiceMock<MockJournal> journal;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500246
Matt Spinler56ad2a02020-03-26 14:00:52 -0500247 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600248 ad, ffdc, dataIface, journal};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500249
250 EXPECT_TRUE(pel.valid());
251 EXPECT_EQ(pel.size(), 16384);
252
253 // Make sure that there are still 2 UD sections.
Matt Spinlerbe952d22022-07-01 11:30:11 -0500254 const auto& optSections = pel.optionalSections();
Patrick Williams075c7922024-08-16 15:19:49 -0400255 auto udCount = std::count_if(
256 optSections.begin(), optSections.end(), [](const auto& section) {
257 return section->header().id ==
258 static_cast<uint16_t>(SectionID::userData);
259 });
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500260
261 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
262}
263
Matt Spinler131870c2019-09-25 13:29:04 -0500264// Test that we'll create Generic optional sections for sections that
265// there aren't explicit classes for.
266TEST_F(PELTest, GenericSectionTest)
267{
Matt Spinler42828bd2019-10-11 10:39:30 -0500268 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500269
Patrick Williams075c7922024-08-16 15:19:49 -0400270 std::vector<uint8_t> section1{
271 0x58, 0x58, // ID 'XX'
272 0x00, 0x18, // Size
273 0x01, 0x02, // version, subtype
274 0x03, 0x04, // comp ID
Matt Spinler131870c2019-09-25 13:29:04 -0500275
Patrick Williams075c7922024-08-16 15:19:49 -0400276 // some data
277 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
278 0x09, 0x22, 0x3A, 0x00};
Matt Spinler131870c2019-09-25 13:29:04 -0500279
280 std::vector<uint8_t> section2{
281 0x59, 0x59, // ID 'YY'
282 0x00, 0x20, // Size
283 0x01, 0x02, // version, subtype
284 0x03, 0x04, // comp ID
285
286 // some data
287 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
288 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
289
290 // Add the new sections at the end
Matt Spinler42828bd2019-10-11 10:39:30 -0500291 data.insert(data.end(), section1.begin(), section1.end());
292 data.insert(data.end(), section2.begin(), section2.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500293
294 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500295 data.at(27) += 2;
296 auto origData = data;
Matt Spinler131870c2019-09-25 13:29:04 -0500297
Matt Spinler42828bd2019-10-11 10:39:30 -0500298 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500299
300 const auto& sections = pel.optionalSections();
301
302 bool foundXX = false;
303 bool foundYY = false;
304
305 // Check that we can find these 2 Generic sections
306 for (const auto& section : sections)
307 {
308 if (section->header().id == 0x5858)
309 {
310 foundXX = true;
311 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
312 }
313 else if (section->header().id == 0x5959)
314 {
315 foundYY = true;
316 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
317 }
318 }
319
320 EXPECT_TRUE(foundXX);
321 EXPECT_TRUE(foundYY);
Matt Spinler07eefc52019-09-26 11:18:26 -0500322
323 // Now flatten and check
324 auto newData = pel.data();
325
326 EXPECT_EQ(origData, newData);
Matt Spinler131870c2019-09-25 13:29:04 -0500327}
328
329// Test that an invalid section will still get a Generic object
330TEST_F(PELTest, InvalidGenericTest)
331{
Matt Spinler42828bd2019-10-11 10:39:30 -0500332 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500333
334 // Not a valid section
335 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
336
Matt Spinler42828bd2019-10-11 10:39:30 -0500337 data.insert(data.end(), section1.begin(), section1.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500338
339 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500340 data.at(27) += 1;
Matt Spinler131870c2019-09-25 13:29:04 -0500341
Matt Spinler42828bd2019-10-11 10:39:30 -0500342 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500343 EXPECT_FALSE(pel.valid());
344
345 const auto& sections = pel.optionalSections();
346
347 bool foundGeneric = false;
348 for (const auto& section : sections)
349 {
350 if (dynamic_cast<Generic*>(section.get()) != nullptr)
351 {
352 foundGeneric = true;
353 EXPECT_EQ(section->valid(), false);
354 break;
355 }
356 }
357
358 EXPECT_TRUE(foundGeneric);
359}
Matt Spinlerafa857c2019-10-24 13:03:46 -0500360
361// Create a UserData section out of AdditionalData
362TEST_F(PELTest, MakeUDSectionTest)
363{
364 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
365 "ESEL=TEST"};
366 AdditionalData additionalData{ad};
367
368 auto ud = util::makeADUserDataSection(additionalData);
369
370 EXPECT_TRUE(ud->valid());
371 EXPECT_EQ(ud->header().id, 0x5544);
372 EXPECT_EQ(ud->header().version, 0x01);
373 EXPECT_EQ(ud->header().subType, 0x01);
374 EXPECT_EQ(ud->header().componentID, 0x2000);
375
376 const auto& d = ud->data();
377
378 std::string jsonString{d.begin(), d.end()};
Matt Spinler53407be2019-11-18 09:16:31 -0600379
380 std::string expectedJSON =
Matt Spinlerafa857c2019-10-24 13:03:46 -0500381 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
Matt Spinler53407be2019-11-18 09:16:31 -0600382
383 // The actual data is null padded to a 4B boundary.
384 std::vector<uint8_t> expectedData;
385 expectedData.resize(52, '\0');
386 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
387
388 EXPECT_EQ(d, expectedData);
Matt Spinlerafa857c2019-10-24 13:03:46 -0500389
390 // Ensure we can read this as JSON
391 auto newJSON = nlohmann::json::parse(jsonString);
392 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
393 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
394 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
Matt Spinler97d19b42019-10-29 11:34:03 -0500395}
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600396
397// Create the UserData section that contains system info
Matt Spinler677381b2020-01-23 10:04:29 -0600398TEST_F(PELTest, SysInfoSectionTest)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600399{
400 MockDataInterface dataIface;
401
Matt Spinler677381b2020-01-23 10:04:29 -0600402 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
Matt Spinler4aa23a12020-02-03 15:05:09 -0600403 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
404 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
405 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500406 EXPECT_CALL(dataIface, getBootState())
407 .WillOnce(Return("State.SystemInitComplete"));
Ben Tynere32b7e72021-05-18 12:38:40 -0500408 EXPECT_CALL(dataIface, getSystemIMKeyword())
409 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
Matt Spinler677381b2020-01-23 10:04:29 -0600410
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600411 std::string pid = "_PID=" + std::to_string(getpid());
412 std::vector<std::string> ad{pid};
413 AdditionalData additionalData{ad};
414
415 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
416
417 EXPECT_TRUE(ud->valid());
418 EXPECT_EQ(ud->header().id, 0x5544);
419 EXPECT_EQ(ud->header().version, 0x01);
420 EXPECT_EQ(ud->header().subType, 0x01);
421 EXPECT_EQ(ud->header().componentID, 0x2000);
422
423 // Pull out the JSON data and check it.
424 const auto& d = ud->data();
425 std::string jsonString{d.begin(), d.end()};
426 auto json = nlohmann::json::parse(jsonString);
427
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500428 // Ensure the 'Process Name' entry contains the name of this test
429 // executable.
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600430 auto name = json["Process Name"].get<std::string>();
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500431 auto found = (name.find("pel_test") != std::string::npos) ||
432 (name.find("test-openpower-pels-pel") != std::string::npos);
433 EXPECT_TRUE(found);
434 // @TODO(stwcx): remove 'pel_test' when removing autotools.
Matt Spinler677381b2020-01-23 10:04:29 -0600435
Matt Spinlerc2b8a512021-05-21 12:44:42 -0600436 auto version = json["FW Version ID"].get<std::string>();
Matt Spinler677381b2020-01-23 10:04:29 -0600437 EXPECT_EQ(version, "ABCD1234");
Matt Spinler4aa23a12020-02-03 15:05:09 -0600438
439 auto state = json["BMCState"].get<std::string>();
440 EXPECT_EQ(state, "Ready");
441
442 state = json["ChassisState"].get<std::string>();
443 EXPECT_EQ(state, "On");
444
445 state = json["HostState"].get<std::string>();
446 EXPECT_EQ(state, "Off");
Ben Tynere32b7e72021-05-18 12:38:40 -0500447
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500448 state = json["BootState"].get<std::string>();
449 EXPECT_EQ(state, "SystemInitComplete");
450
Ben Tynere32b7e72021-05-18 12:38:40 -0500451 auto keyword = json["System IM"].get<std::string>();
452 EXPECT_EQ(keyword, "000155AA");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600453}
Matt Spinlerce3f4502020-01-22 15:44:35 -0600454
455// Test that the sections that override
456// virtual std::optional<std::string> Section::getJSON() const
457// return valid JSON.
458TEST_F(PELTest, SectionJSONTest)
459{
460 auto data = pelDataFactory(TestPELType::pelSimple);
461 PEL pel{data};
462
463 // Check that all JSON returned from the sections is
464 // parseable by nlohmann::json, which will throw an
465 // exception and fail the test if there is a problem.
466
467 // The getJSON() response needs to be wrapped in a { } to make
468 // actual valid JSON (PEL::toJSON() usually handles that).
469
Matt Spinlerb832aa52023-03-21 15:32:34 -0500470 auto jsonString = pel.privateHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600471
472 // PrivateHeader always prints JSON
473 ASSERT_TRUE(jsonString);
474 *jsonString = '{' + *jsonString + '}';
475 auto json = nlohmann::json::parse(*jsonString);
476
Matt Spinlerb832aa52023-03-21 15:32:34 -0500477 jsonString = pel.userHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600478
479 // UserHeader always prints JSON
480 ASSERT_TRUE(jsonString);
481 *jsonString = '{' + *jsonString + '}';
482 json = nlohmann::json::parse(*jsonString);
483
484 for (const auto& section : pel.optionalSections())
485 {
486 // The optional sections may or may not have implemented getJSON().
Matt Spinlerb832aa52023-03-21 15:32:34 -0500487 jsonString = section->getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600488 if (jsonString)
489 {
490 *jsonString = '{' + *jsonString + '}';
491 auto json = nlohmann::json::parse(*jsonString);
492 }
493 }
494}
Matt Spinler5b289b22020-03-26 14:27:19 -0500495
496PelFFDCfile getJSONFFDC(const fs::path& dir)
497{
498 PelFFDCfile ffdc;
499 ffdc.format = UserDataFormat::json;
500 ffdc.subType = 5;
501 ffdc.version = 42;
502
503 auto inputJSON = R"({
504 "key1": "value1",
505 "key2": 42,
506 "key3" : [1, 2, 3, 4, 5],
507 "key4": {"key5": "value5"}
508 })"_json;
509
510 // Write the JSON to a file and get its descriptor.
511 auto s = inputJSON.dump();
512 std::vector<uint8_t> data{s.begin(), s.end()};
513 ffdc.fd = writeFileAndGetFD(dir, data);
514
515 return ffdc;
516}
517
518TEST_F(PELTest, MakeJSONFileUDSectionTest)
519{
520 auto dir = makeTempDir();
521
522 {
523 auto ffdc = getJSONFFDC(dir);
524
525 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
526 close(ffdc.fd);
527 ASSERT_TRUE(ud);
528 ASSERT_TRUE(ud->valid());
529 EXPECT_EQ(ud->header().id, 0x5544);
530
531 EXPECT_EQ(ud->header().version,
532 static_cast<uint8_t>(UserDataFormatVersion::json));
533 EXPECT_EQ(ud->header().subType,
534 static_cast<uint8_t>(UserDataFormat::json));
535 EXPECT_EQ(ud->header().componentID,
536 static_cast<uint16_t>(ComponentID::phosphorLogging));
537
538 // Pull the JSON back out of the the UserData section
539 const auto& d = ud->data();
540 std::string js{d.begin(), d.end()};
541 auto json = nlohmann::json::parse(js);
542
543 EXPECT_EQ("value1", json["key1"].get<std::string>());
544 EXPECT_EQ(42, json["key2"].get<int>());
545
546 std::vector<int> key3Values{1, 2, 3, 4, 5};
547 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
548
549 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
550 auto actual = json["key4"].get<std::map<std::string, std::string>>();
551 EXPECT_EQ(key4Values, actual);
552 }
553
554 {
555 // A bad FD
556 PelFFDCfile ffdc;
557 ffdc.format = UserDataFormat::json;
558 ffdc.subType = 5;
559 ffdc.version = 42;
560 ffdc.fd = 10000;
561
562 // The section shouldn't get made
563 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
564 ASSERT_FALSE(ud);
565 }
566
567 fs::remove_all(dir);
568}
569
570PelFFDCfile getCBORFFDC(const fs::path& dir)
571{
572 PelFFDCfile ffdc;
573 ffdc.format = UserDataFormat::cbor;
574 ffdc.subType = 5;
575 ffdc.version = 42;
576
577 auto inputJSON = R"({
578 "key1": "value1",
579 "key2": 42,
580 "key3" : [1, 2, 3, 4, 5],
581 "key4": {"key5": "value5"}
582 })"_json;
583
584 // Convert the JSON to CBOR and write it to a file
585 auto data = nlohmann::json::to_cbor(inputJSON);
586 ffdc.fd = writeFileAndGetFD(dir, data);
587
588 return ffdc;
589}
590
591TEST_F(PELTest, MakeCBORFileUDSectionTest)
592{
593 auto dir = makeTempDir();
594
595 auto ffdc = getCBORFFDC(dir);
596 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
597 close(ffdc.fd);
598 ASSERT_TRUE(ud);
599 ASSERT_TRUE(ud->valid());
600 EXPECT_EQ(ud->header().id, 0x5544);
601
602 EXPECT_EQ(ud->header().version,
603 static_cast<uint8_t>(UserDataFormatVersion::cbor));
604 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
605 EXPECT_EQ(ud->header().componentID,
606 static_cast<uint16_t>(ComponentID::phosphorLogging));
607
608 // Pull the CBOR back out of the PEL section
609 // The number of pad bytes to make the section be 4B aligned
610 // was added at the end, read it and then remove it and the
611 // padding before parsing it.
612 auto data = ud->data();
613 Stream stream{data};
614 stream.offset(data.size() - 4);
615 uint32_t pad;
616 stream >> pad;
617
618 data.resize(data.size() - 4 - pad);
619
620 auto json = nlohmann::json::from_cbor(data);
621
622 EXPECT_EQ("value1", json["key1"].get<std::string>());
623 EXPECT_EQ(42, json["key2"].get<int>());
624
625 std::vector<int> key3Values{1, 2, 3, 4, 5};
626 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
627
628 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
629 auto actual = json["key4"].get<std::map<std::string, std::string>>();
630 EXPECT_EQ(key4Values, actual);
631
632 fs::remove_all(dir);
633}
634
635PelFFDCfile getTextFFDC(const fs::path& dir)
636{
637 PelFFDCfile ffdc;
638 ffdc.format = UserDataFormat::text;
639 ffdc.subType = 5;
640 ffdc.version = 42;
641
642 std::string text{"this is some text that will be used for FFDC"};
643 std::vector<uint8_t> data{text.begin(), text.end()};
644
645 ffdc.fd = writeFileAndGetFD(dir, data);
646
647 return ffdc;
648}
649
650TEST_F(PELTest, MakeTextFileUDSectionTest)
651{
652 auto dir = makeTempDir();
653
654 auto ffdc = getTextFFDC(dir);
655 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
656 close(ffdc.fd);
657 ASSERT_TRUE(ud);
658 ASSERT_TRUE(ud->valid());
659 EXPECT_EQ(ud->header().id, 0x5544);
660
661 EXPECT_EQ(ud->header().version,
662 static_cast<uint8_t>(UserDataFormatVersion::text));
663 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
664 EXPECT_EQ(ud->header().componentID,
665 static_cast<uint16_t>(ComponentID::phosphorLogging));
666
667 // Get the text back out
668 std::string text{ud->data().begin(), ud->data().end()};
669 EXPECT_EQ(text, "this is some text that will be used for FFDC");
670
671 fs::remove_all(dir);
672}
673
674PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
675{
676 PelFFDCfile ffdc;
677 ffdc.format = UserDataFormat::custom;
678 ffdc.subType = 5;
679 ffdc.version = 42;
680
681 ffdc.fd = writeFileAndGetFD(dir, data);
682
683 return ffdc;
684}
685
686TEST_F(PELTest, MakeCustomFileUDSectionTest)
687{
688 auto dir = makeTempDir();
689
690 {
691 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
692
693 auto ffdc = getCustomFFDC(dir, data);
694 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
695 close(ffdc.fd);
696 ASSERT_TRUE(ud);
697 ASSERT_TRUE(ud->valid());
698 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
699 EXPECT_EQ(ud->header().id, 0x5544);
700
701 EXPECT_EQ(ud->header().version, 42);
702 EXPECT_EQ(ud->header().subType, 5);
703 EXPECT_EQ(ud->header().componentID, 0x2002);
704
705 // Get the data back out
706 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
707 EXPECT_EQ(data, newData);
708 }
709
710 // Do the same thing again, but make it be non 4B aligned
711 // so the data gets padded.
712 {
713 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
714
715 auto ffdc = getCustomFFDC(dir, data);
716 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
717 close(ffdc.fd);
718 ASSERT_TRUE(ud);
719 ASSERT_TRUE(ud->valid());
720 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
721 EXPECT_EQ(ud->header().id, 0x5544);
722
723 EXPECT_EQ(ud->header().version, 42);
724 EXPECT_EQ(ud->header().subType, 5);
725 EXPECT_EQ(ud->header().componentID, 0x2002);
726
727 // Get the data back out
728 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
729
730 // pad the original to 12B so we can compare
731 data.push_back(0);
732 data.push_back(0);
733 data.push_back(0);
734
735 EXPECT_EQ(data, newData);
736 }
737
738 fs::remove_all(dir);
739}
740
741// Test Adding FFDC from files to a PEL
742TEST_F(PELTest, CreateWithFFDCTest)
743{
744 auto dir = makeTempDir();
745 message::Entry regEntry;
746 uint64_t timestamp = 5;
747
748 regEntry.name = "test";
749 regEntry.subsystem = 5;
750 regEntry.actionFlags = 0xC000;
751 regEntry.src.type = 0xBD;
752 regEntry.src.reasonCode = 0x1234;
753
754 std::vector<std::string> additionalData{"KEY1=VALUE1"};
755 AdditionalData ad{additionalData};
756 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600757 NiceMock<MockJournal> journal;
Matt Spinler5b289b22020-03-26 14:27:19 -0500758 PelFFDC ffdc;
759
760 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
761
762 // This will be trimmed when added
763 std::vector<uint8_t> hugeCustomData(17000, 0x42);
764
765 ffdc.emplace_back(std::move(getJSONFFDC(dir)));
766 ffdc.emplace_back(std::move(getCBORFFDC(dir)));
767 ffdc.emplace_back(std::move(getTextFFDC(dir)));
768 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
769 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
770
771 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600772 ad, ffdc, dataIface, journal};
Matt Spinler5b289b22020-03-26 14:27:19 -0500773
774 EXPECT_TRUE(pel.valid());
775
776 // Clipped to the max
777 EXPECT_EQ(pel.size(), 16384);
778
779 // Check for the FFDC sections
780 size_t udCount = 0;
781 Section* ud = nullptr;
782
783 for (const auto& section : pel.optionalSections())
784 {
785 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
786 {
787 udCount++;
788 ud = section.get();
789 }
790 }
791
792 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
793
794 // Check the last section was trimmed to
795 // something a bit less that 17000.
796 EXPECT_GT(ud->header().size, 14000);
797 EXPECT_LT(ud->header().size, 16000);
798
799 fs::remove_all(dir);
800}
Matt Spinler0a90a852020-06-04 13:18:27 -0500801
802// Create a PEL with device callouts
803TEST_F(PELTest, CreateWithDevCalloutsTest)
804{
805 message::Entry regEntry;
806 uint64_t timestamp = 5;
807
808 regEntry.name = "test";
809 regEntry.subsystem = 5;
810 regEntry.actionFlags = 0xC000;
811 regEntry.src.type = 0xBD;
812 regEntry.src.reasonCode = 0x1234;
813
814 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600815 NiceMock<MockJournal> journal;
Matt Spinler0a90a852020-06-04 13:18:27 -0500816 PelFFDC ffdc;
817
818 const auto calloutJSON = R"(
819 {
820 "I2C":
821 {
822 "14":
823 {
824 "114":
825 {
826 "Callouts":[
827 {
828 "Name": "/chassis/motherboard/cpu0",
829 "LocationCode": "P1",
830 "Priority": "H"
831 }
832 ],
833 "Dest": "proc 0 target"
834 }
835 }
836 }
837 })";
838
839 std::vector<std::string> names{"systemA"};
840 EXPECT_CALL(dataIface, getSystemNames)
841 .Times(2)
Matt Spinler1ab66962020-10-29 13:21:44 -0500842 .WillRepeatedly(Return(names));
Matt Spinler0a90a852020-06-04 13:18:27 -0500843
Matt Spinler0d92b522021-06-16 13:28:17 -0600844 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
845 .Times(1)
Matt Spinler0a90a852020-06-04 13:18:27 -0500846 .WillOnce(Return("UXXX-P1"));
847
Matt Spinler2f9225a2020-08-05 12:58:49 -0500848 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
Matt Spinlerbad056b2023-01-25 14:16:57 -0600849 .WillOnce(Return(std::vector<std::string>{
850 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
Matt Spinler0a90a852020-06-04 13:18:27 -0500851
Patrick Williams075c7922024-08-16 15:19:49 -0400852 EXPECT_CALL(dataIface,
853 getHWCalloutFields(
854 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
855 _, _, _))
Matt Spinler0a90a852020-06-04 13:18:27 -0500856 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
857 SetArgReferee<3>("123456789ABC")));
858
859 auto dataPath = getPELReadOnlyDataPath();
860 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
861 file << calloutJSON;
862 file.close();
863
864 {
865 std::vector<std::string> data{
866 "CALLOUT_ERRNO=5",
867 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
868 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
869
870 AdditionalData ad{data};
871
Patrick Williams075c7922024-08-16 15:19:49 -0400872 PEL pel{regEntry, 42,
873 timestamp, phosphor::logging::Entry::Level::Error,
874 ad, ffdc,
875 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500876
877 ASSERT_TRUE(pel.primarySRC().value()->callouts());
878 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
879 ASSERT_EQ(callouts.size(), 1);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600880 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinler0a90a852020-06-04 13:18:27 -0500881
882 EXPECT_EQ(callouts[0]->priority(), 'H');
883 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
884
885 auto& fru = callouts[0]->fruIdentity();
886 EXPECT_EQ(fru->getPN().value(), "1234567");
887 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
888 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
889
890 const auto& section = pel.optionalSections().back();
891
892 ASSERT_EQ(section->header().id, 0x5544); // UD
893 auto ud = static_cast<UserData*>(section.get());
894
895 // Check that there was a UserData section added that
896 // contains debug details about the device.
897 const auto& d = ud->data();
898 std::string jsonString{d.begin(), d.end()};
899 auto actualJSON = nlohmann::json::parse(jsonString);
900
901 auto expectedJSON = R"(
902 {
903 "PEL Internal Debug Data": {
904 "SRC": [
905 "I2C: bus: 14 address: 114 dest: proc 0 target"
906 ]
907 }
908 }
909 )"_json;
910
911 EXPECT_EQ(actualJSON, expectedJSON);
912 }
913
914 {
915 // Device path not found (wrong i2c addr), so no callouts
916 std::vector<std::string> data{
917 "CALLOUT_ERRNO=5",
918 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
919 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
920
921 AdditionalData ad{data};
922
Patrick Williams075c7922024-08-16 15:19:49 -0400923 PEL pel{regEntry, 42,
924 timestamp, phosphor::logging::Entry::Level::Error,
925 ad, ffdc,
926 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500927
928 // no callouts
929 EXPECT_FALSE(pel.primarySRC().value()->callouts());
930
931 // Now check that there was a UserData section
932 // that contains the lookup error.
933 const auto& section = pel.optionalSections().back();
934
935 ASSERT_EQ(section->header().id, 0x5544); // UD
936 auto ud = static_cast<UserData*>(section.get());
937
938 const auto& d = ud->data();
939
940 std::string jsonString{d.begin(), d.end()};
941
942 auto actualJSON = nlohmann::json::parse(jsonString);
943
944 auto expectedJSON =
945 "{\"PEL Internal Debug Data\":{\"SRC\":"
946 "[\"Problem looking up I2C callouts on 14 153: "
947 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
948
949 EXPECT_EQ(actualJSON, expectedJSON);
950 }
951
952 fs::remove_all(dataPath);
953}
Matt Spinlere513dbc2020-08-27 11:14:17 -0500954
955// Test PELs when the callouts are passed in using a JSON file.
956TEST_F(PELTest, CreateWithJSONCalloutsTest)
957{
958 PelFFDCfile ffdcFile;
959 ffdcFile.format = UserDataFormat::json;
960 ffdcFile.subType = 0xCA; // Callout JSON
961 ffdcFile.version = 1;
962
963 // Write these callouts to a JSON file and pass it into
Matt Spinler4efed0e2024-02-26 11:16:07 -0600964 // the PEL as an FFDC file. Also has a duplicate that
965 // will be removed.
Matt Spinlere513dbc2020-08-27 11:14:17 -0500966 auto inputJSON = R"([
967 {
968 "Priority": "H",
969 "LocationCode": "P0-C1"
970 },
971 {
972 "Priority": "M",
973 "Procedure": "PROCEDURE"
Matt Spinler4efed0e2024-02-26 11:16:07 -0600974 },
975 {
976 "Priority": "L",
977 "Procedure": "PROCEDURE"
Matt Spinlere513dbc2020-08-27 11:14:17 -0500978 }
979 ])"_json;
980
981 auto s = inputJSON.dump();
982 std::vector<uint8_t> data{s.begin(), s.end()};
983 auto dir = makeTempDir();
984 ffdcFile.fd = writeFileAndGetFD(dir, data);
985
986 PelFFDC ffdc;
987 ffdc.push_back(std::move(ffdcFile));
988
989 AdditionalData ad;
990 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600991 NiceMock<MockJournal> journal;
Matt Spinlere513dbc2020-08-27 11:14:17 -0500992
993 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
994 .Times(1)
995 .WillOnce(Return("UXXX-P0-C1"));
996 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
997 .Times(1)
Matt Spinlerbad056b2023-01-25 14:16:57 -0600998 .WillOnce(Return(
999 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
Matt Spinlere513dbc2020-08-27 11:14:17 -05001000 EXPECT_CALL(dataIface, getHWCalloutFields(
1001 "/inv/system/chassis/motherboard/bmc", _, _, _))
1002 .Times(1)
1003 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1004 SetArgReferee<3>("123456789ABC")));
1005
1006 message::Entry regEntry;
1007 regEntry.name = "test";
1008 regEntry.subsystem = 5;
1009 regEntry.actionFlags = 0xC000;
1010 regEntry.src.type = 0xBD;
1011 regEntry.src.reasonCode = 0x1234;
1012
Matt Spinler9d921092022-12-15 11:54:49 -06001013 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1014 ad, ffdc, dataIface, journal};
Matt Spinlere513dbc2020-08-27 11:14:17 -05001015
1016 ASSERT_TRUE(pel.valid());
1017 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1018 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1019 ASSERT_EQ(callouts.size(), 2);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001020 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinlere513dbc2020-08-27 11:14:17 -05001021
1022 {
1023 EXPECT_EQ(callouts[0]->priority(), 'H');
1024 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1025
1026 auto& fru = callouts[0]->fruIdentity();
1027 EXPECT_EQ(fru->getPN().value(), "1234567");
1028 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1029 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1030 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1031 }
1032 {
1033 EXPECT_EQ(callouts[1]->priority(), 'M');
1034 EXPECT_EQ(callouts[1]->locationCode(), "");
1035
1036 auto& fru = callouts[1]->fruIdentity();
1037 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1038 EXPECT_EQ(fru->failingComponentType(),
1039 src::FRUIdentity::maintenanceProc);
1040 }
1041 fs::remove_all(dir);
1042}
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001043
1044// Test PELs with symblic FRU callout.
1045TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1046{
1047 PelFFDCfile ffdcFile;
1048 ffdcFile.format = UserDataFormat::json;
1049 ffdcFile.subType = 0xCA; // Callout JSON
1050 ffdcFile.version = 1;
1051
1052 // Write these callouts to a JSON file and pass it into
1053 // the PEL as an FFDC file.
1054 auto inputJSON = R"([
1055 {
1056 "Priority": "M",
1057 "Procedure": "SVCDOCS"
1058 }
1059 ])"_json;
1060
1061 auto s = inputJSON.dump();
1062 std::vector<uint8_t> data{s.begin(), s.end()};
1063 auto dir = makeTempDir();
1064 ffdcFile.fd = writeFileAndGetFD(dir, data);
1065
1066 PelFFDC ffdc;
1067 ffdc.push_back(std::move(ffdcFile));
1068
1069 AdditionalData ad;
1070 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -06001071 NiceMock<MockJournal> journal;
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001072
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001073 message::Entry regEntry;
1074 regEntry.name = "test";
1075 regEntry.subsystem = 5;
1076 regEntry.actionFlags = 0xC000;
1077 regEntry.src.type = 0xBD;
1078 regEntry.src.reasonCode = 0x1234;
1079
Matt Spinler9d921092022-12-15 11:54:49 -06001080 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1081 ad, ffdc, dataIface, journal};
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001082
1083 ASSERT_TRUE(pel.valid());
1084 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1085 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1086 ASSERT_EQ(callouts.size(), 1);
1087 ASSERT_FALSE(pel.isHwCalloutPresent());
1088
1089 {
1090 EXPECT_EQ(callouts[0]->priority(), 'M');
1091 EXPECT_EQ(callouts[0]->locationCode(), "");
1092
1093 auto& fru = callouts[0]->fruIdentity();
1094 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1095 }
1096 fs::remove_all(dir);
1097}
Matt Spinler9d921092022-12-15 11:54:49 -06001098
1099TEST_F(PELTest, FlattenLinesTest)
1100{
1101 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1102
1103 auto buffer = util::flattenLines(msgs);
1104
1105 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1106 std::vector<uint8_t> expected(string.begin(), string.end());
1107
1108 EXPECT_EQ(buffer, expected);
1109}
1110
1111void checkJournalSection(const std::unique_ptr<Section>& section,
1112 const std::string& expected)
1113{
1114 ASSERT_EQ(SectionID::userData,
1115 static_cast<SectionID>(section->header().id));
1116 ASSERT_EQ(UserDataFormat::text,
1117 static_cast<UserDataFormat>(section->header().subType));
1118 ASSERT_EQ(section->header().version,
1119 static_cast<uint8_t>(UserDataFormatVersion::text));
1120
1121 auto ud = static_cast<UserData*>(section.get());
1122
1123 std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1124
1125 // PEL sections are 4B aligned so add padding before the compare
1126 while (expectedData.size() % 4 != 0)
1127 {
1128 expectedData.push_back('\0');
1129 }
1130
1131 EXPECT_EQ(ud->data(), expectedData);
1132}
1133
1134TEST_F(PELTest, CaptureJournalTest)
1135{
1136 message::Entry regEntry;
1137 uint64_t timestamp = 5;
1138
1139 regEntry.name = "test";
1140 regEntry.subsystem = 5;
1141 regEntry.actionFlags = 0xC000;
1142 regEntry.src.type = 0xBD;
1143 regEntry.src.reasonCode = 0x1234;
1144
1145 std::vector<std::string> data;
1146 AdditionalData ad{data};
1147 NiceMock<MockDataInterface> dataIface;
1148 NiceMock<MockJournal> journal;
1149 PelFFDC ffdc;
1150
Matt Spinler9d921092022-12-15 11:54:49 -06001151 size_t pelSectsWithOneUD{0};
1152
1153 {
1154 // Capture 5 lines from the journal into a single UD section
1155 message::JournalCapture jc = size_t{5};
1156 regEntry.journalCapture = jc;
1157
1158 std::vector<std::string> msgs{"test1 test2", "test3 test4",
1159 "test5 test6", "4", "5"};
1160
1161 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1162
Patrick Williams075c7922024-08-16 15:19:49 -04001163 PEL pel{regEntry, 42,
1164 timestamp, phosphor::logging::Entry::Level::Error,
1165 ad, ffdc,
1166 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001167
1168 // Check the generated UserData section
1169 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1170
1171 checkJournalSection(pel.optionalSections().back(), expected);
1172
1173 // Save for upcoming testcases
1174 pelSectsWithOneUD = pel.privateHeader().sectionCount();
1175 }
1176
1177 {
1178 // Attempt to capture too many journal entries so the
1179 // section gets dropped.
1180 message::JournalCapture jc = size_t{1};
1181 regEntry.journalCapture = jc;
1182
1183 EXPECT_CALL(journal, sync()).Times(1);
1184
1185 // A 20000 byte line won't fit in a PEL
1186 EXPECT_CALL(journal, getMessages("", 1))
1187 .WillOnce(
1188 Return(std::vector<std::string>{std::string(20000, 'x')}));
1189
Patrick Williams075c7922024-08-16 15:19:49 -04001190 PEL pel{regEntry, 42,
1191 timestamp, phosphor::logging::Entry::Level::Error,
1192 ad, ffdc,
1193 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001194
1195 // Check for 1 fewer sections than in the previous PEL
1196 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1197 }
1198
1199 // Capture 3 different journal sections
1200 {
1201 message::AppCaptureList captureList{
1202 message::AppCapture{"app1", 3},
1203 message::AppCapture{"app2", 4},
1204 message::AppCapture{"app3", 1},
1205 };
1206 message::JournalCapture jc = captureList;
1207 regEntry.journalCapture = jc;
1208
1209 std::vector<std::string> app1{"A B", "C D", "E F"};
1210 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1211 std::vector<std::string> app3{"a b c"};
1212
1213 std::string expected1{"A B\nC D\nE F\n"};
1214 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1215 std::string expected3{"a b c\n"};
1216
1217 EXPECT_CALL(journal, sync()).Times(1);
1218 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1219 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1220 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1221
Patrick Williams075c7922024-08-16 15:19:49 -04001222 PEL pel{regEntry, 42,
1223 timestamp, phosphor::logging::Entry::Level::Error,
1224 ad, ffdc,
1225 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001226
1227 // Two more sections than the 1 extra UD section in the first testcase
1228 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1229
1230 const auto& optionalSections = pel.optionalSections();
1231 auto numOptSections = optionalSections.size();
1232
1233 checkJournalSection(optionalSections[numOptSections - 3], expected1);
1234 checkJournalSection(optionalSections[numOptSections - 2], expected2);
1235 checkJournalSection(optionalSections[numOptSections - 1], expected3);
1236 }
1237
1238 {
1239 // One section gets saved, and one is too big and gets dropped
1240 message::AppCaptureList captureList{
1241 message::AppCapture{"app4", 2},
1242 message::AppCapture{"app5", 1},
1243 };
1244 message::JournalCapture jc = captureList;
1245 regEntry.journalCapture = jc;
1246
1247 std::vector<std::string> app4{"w x", "y z"};
1248 std::string expected4{"w x\ny z\n"};
1249
1250 EXPECT_CALL(journal, sync()).Times(1);
1251
1252 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1253
1254 // A 20000 byte line won't fit in a PEL
1255 EXPECT_CALL(journal, getMessages("app5", 1))
1256 .WillOnce(
1257 Return(std::vector<std::string>{std::string(20000, 'x')}));
1258
Patrick Williams075c7922024-08-16 15:19:49 -04001259 PEL pel{regEntry, 42,
1260 timestamp, phosphor::logging::Entry::Level::Error,
1261 ad, ffdc,
1262 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001263
1264 // The last section should have been dropped, so same as first TC
1265 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1266
1267 checkJournalSection(pel.optionalSections().back(), expected4);
1268 }
1269}