1 /**
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  */
16 #include "elog_entry.hpp"
17 #include "extensions/openpower-pels/generic.hpp"
18 #include "extensions/openpower-pels/pel.hpp"
19 #include "mocks.hpp"
20 #include "pel_utils.hpp"
21 
22 #include <filesystem>
23 #include <fstream>
24 
25 #include <gtest/gtest.h>
26 
27 namespace fs = std::filesystem;
28 using namespace openpower::pels;
29 using ::testing::_;
30 using ::testing::DoAll;
31 using ::testing::NiceMock;
32 using ::testing::Return;
33 using ::testing::SetArgReferee;
34 
35 class PELTest : public CleanLogID
36 {};
37 
38 fs::path makeTempDir()
39 {
40     char path[] = "/tmp/tempdirXXXXXX";
41     std::filesystem::path dir = mkdtemp(path);
42     return dir;
43 }
44 
45 int 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 
59 TEST_F(PELTest, FlattenTest)
60 {
61     auto data = pelDataFactory(TestPELType::pelSimple);
62     auto pel = std::make_unique<PEL>(data);
63 
64     // Check a few fields
65     EXPECT_TRUE(pel->valid());
66     EXPECT_EQ(pel->id(), 0x80818283);
67     EXPECT_EQ(pel->plid(), 0x50515253);
68     EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
69     EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
70 
71     // Test that data in == data out
72     auto flattenedData = pel->data();
73     EXPECT_EQ(data, flattenedData);
74     EXPECT_EQ(flattenedData.size(), pel->size());
75 }
76 
77 TEST_F(PELTest, CommitTimeTest)
78 {
79     auto data = pelDataFactory(TestPELType::pelSimple);
80     auto pel = std::make_unique<PEL>(data);
81 
82     auto origTime = pel->commitTime();
83     pel->setCommitTime();
84     auto newTime = pel->commitTime();
85 
86     EXPECT_NE(origTime, newTime);
87 
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);
91     EXPECT_EQ(newTime, newPel->commitTime());
92 }
93 
94 TEST_F(PELTest, AssignIDTest)
95 {
96     auto data = pelDataFactory(TestPELType::pelSimple);
97     auto pel = std::make_unique<PEL>(data);
98 
99     auto origID = pel->id();
100     pel->assignID();
101     auto newID = pel->id();
102 
103     EXPECT_NE(origID, newID);
104 
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);
108     EXPECT_EQ(newID, newPel->id());
109 }
110 
111 TEST_F(PELTest, WithLogIDTest)
112 {
113     auto data = pelDataFactory(TestPELType::pelSimple);
114     auto pel = std::make_unique<PEL>(data, 0x42);
115 
116     EXPECT_TRUE(pel->valid());
117     EXPECT_EQ(pel->obmcLogID(), 0x42);
118 }
119 
120 TEST_F(PELTest, InvalidPELTest)
121 {
122     auto data = pelDataFactory(TestPELType::pelSimple);
123 
124     // Too small
125     data.resize(PrivateHeader::flattenedSize());
126 
127     auto pel = std::make_unique<PEL>(data);
128 
129     EXPECT_TRUE(pel->privateHeader().valid());
130     EXPECT_FALSE(pel->userHeader().valid());
131     EXPECT_FALSE(pel->valid());
132 
133     // Now corrupt the private header
134     data = pelDataFactory(TestPELType::pelSimple);
135     data.at(0) = 0;
136     pel = std::make_unique<PEL>(data);
137 
138     EXPECT_FALSE(pel->privateHeader().valid());
139     EXPECT_TRUE(pel->userHeader().valid());
140     EXPECT_FALSE(pel->valid());
141 }
142 
143 TEST_F(PELTest, EmptyDataTest)
144 {
145     std::vector<uint8_t> data;
146     auto pel = std::make_unique<PEL>(data);
147 
148     EXPECT_FALSE(pel->privateHeader().valid());
149     EXPECT_FALSE(pel->userHeader().valid());
150     EXPECT_FALSE(pel->valid());
151 }
152 
153 TEST_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;
161     regEntry.src.type = 0xBD;
162     regEntry.src.reasonCode = 0x1234;
163 
164     std::vector<std::string> data{"KEY1=VALUE1"};
165     AdditionalData ad{data};
166     NiceMock<MockDataInterface> dataIface;
167     NiceMock<MockJournal> journal;
168     PelFFDC ffdc;
169 
170     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
171                                       "system/entry"};
172     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
173         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
174 
175     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
176             ad,       ffdc, dataIface, journal};
177 
178     EXPECT_TRUE(pel.valid());
179     EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
180     EXPECT_EQ(pel.userHeader().severity(), 0x40);
181 
182     EXPECT_EQ(pel.primarySRC().value()->asciiString(),
183               "BD051234                        ");
184 
185     // Check that certain optional sections have been created
186     size_t mtmsCount = 0;
187     size_t euhCount = 0;
188     size_t udCount = 0;
189 
190     for (const auto& section : pel.optionalSections())
191     {
192         if (section->header().id ==
193             static_cast<uint16_t>(SectionID::failingMTMS))
194         {
195             mtmsCount++;
196         }
197         else if (section->header().id ==
198                  static_cast<uint16_t>(SectionID::extendedUserHeader))
199         {
200             euhCount++;
201         }
202         else if (section->header().id ==
203                  static_cast<uint16_t>(SectionID::userData))
204         {
205             udCount++;
206         }
207     }
208 
209     EXPECT_EQ(mtmsCount, 1);
210     EXPECT_EQ(euhCount, 1);
211     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
212     ASSERT_FALSE(pel.isHwCalloutPresent());
213 
214     {
215         // The same thing, but without the action flags specified
216         // in the registry, so the constructor should set them.
217         regEntry.actionFlags = std::nullopt;
218 
219         PEL pel2{
220             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
221             ad,       ffdc, dataIface, journal};
222 
223         EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
224     }
225 }
226 
227 // Test that when the AdditionalData size is over 16KB that
228 // the PEL that's created is exactly 16KB since the UserData
229 // section that contains all that data was pruned.
230 TEST_F(PELTest, CreateTooBigADTest)
231 {
232     message::Entry regEntry;
233     uint64_t timestamp = 5;
234 
235     regEntry.name = "test";
236     regEntry.subsystem = 5;
237     regEntry.actionFlags = 0xC000;
238     regEntry.src.type = 0xBD;
239     regEntry.src.reasonCode = 0x1234;
240     PelFFDC ffdc;
241 
242     // Over the 16KB max PEL size
243     std::string bigAD{"KEY1="};
244     bigAD += std::string(17000, 'G');
245 
246     std::vector<std::string> data{bigAD};
247     AdditionalData ad{data};
248     NiceMock<MockDataInterface> dataIface;
249     NiceMock<MockJournal> journal;
250 
251     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
252                                       "system/entry"};
253     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
254         .WillOnce(Return(std::vector<bool>{false, false, false}));
255 
256     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
257             ad,       ffdc, dataIface, journal};
258 
259     EXPECT_TRUE(pel.valid());
260     EXPECT_EQ(pel.size(), 16384);
261 
262     // Make sure that there are still 2 UD sections.
263     const auto& optSections = pel.optionalSections();
264     auto udCount = std::count_if(
265         optSections.begin(), optSections.end(), [](const auto& section) {
266             return section->header().id ==
267                    static_cast<uint16_t>(SectionID::userData);
268         });
269 
270     EXPECT_EQ(udCount, 2); // AD section and sysInfo section
271 }
272 
273 // Test that we'll create Generic optional sections for sections that
274 // there aren't explicit classes for.
275 TEST_F(PELTest, GenericSectionTest)
276 {
277     auto data = pelDataFactory(TestPELType::pelSimple);
278 
279     std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX'
280                                   0x00, 0x18, // Size
281                                   0x01, 0x02, // version, subtype
282                                   0x03, 0x04, // comp ID
283 
284                                   // some data
285                                   0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
286                                   0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
287                                   0x00};
288 
289     std::vector<uint8_t> section2{
290         0x59, 0x59, // ID 'YY'
291         0x00, 0x20, // Size
292         0x01, 0x02, // version, subtype
293         0x03, 0x04, // comp ID
294 
295         // some data
296         0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
297         0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
298 
299     // Add the new sections at the end
300     data.insert(data.end(), section1.begin(), section1.end());
301     data.insert(data.end(), section2.begin(), section2.end());
302 
303     // Increment the section count
304     data.at(27) += 2;
305     auto origData = data;
306 
307     PEL pel{data};
308 
309     const auto& sections = pel.optionalSections();
310 
311     bool foundXX = false;
312     bool foundYY = false;
313 
314     // Check that we can find these 2 Generic sections
315     for (const auto& section : sections)
316     {
317         if (section->header().id == 0x5858)
318         {
319             foundXX = true;
320             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
321         }
322         else if (section->header().id == 0x5959)
323         {
324             foundYY = true;
325             EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
326         }
327     }
328 
329     EXPECT_TRUE(foundXX);
330     EXPECT_TRUE(foundYY);
331 
332     // Now flatten and check
333     auto newData = pel.data();
334 
335     EXPECT_EQ(origData, newData);
336 }
337 
338 // Test that an invalid section will still get a Generic object
339 TEST_F(PELTest, InvalidGenericTest)
340 {
341     auto data = pelDataFactory(TestPELType::pelSimple);
342 
343     // Not a valid section
344     std::vector<uint8_t> section1{0x01, 0x02, 0x03};
345 
346     data.insert(data.end(), section1.begin(), section1.end());
347 
348     // Increment the section count
349     data.at(27) += 1;
350 
351     PEL pel{data};
352     EXPECT_FALSE(pel.valid());
353 
354     const auto& sections = pel.optionalSections();
355 
356     bool foundGeneric = false;
357     for (const auto& section : sections)
358     {
359         if (dynamic_cast<Generic*>(section.get()) != nullptr)
360         {
361             foundGeneric = true;
362             EXPECT_EQ(section->valid(), false);
363             break;
364         }
365     }
366 
367     EXPECT_TRUE(foundGeneric);
368 }
369 
370 // Create a UserData section out of AdditionalData
371 TEST_F(PELTest, MakeUDSectionTest)
372 {
373     std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
374                                 "ESEL=TEST"};
375     AdditionalData additionalData{ad};
376 
377     auto ud = util::makeADUserDataSection(additionalData);
378 
379     EXPECT_TRUE(ud->valid());
380     EXPECT_EQ(ud->header().id, 0x5544);
381     EXPECT_EQ(ud->header().version, 0x01);
382     EXPECT_EQ(ud->header().subType, 0x01);
383     EXPECT_EQ(ud->header().componentID, 0x2000);
384 
385     const auto& d = ud->data();
386 
387     std::string jsonString{d.begin(), d.end()};
388 
389     std::string expectedJSON =
390         R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
391 
392     // The actual data is null padded to a 4B boundary.
393     std::vector<uint8_t> expectedData;
394     expectedData.resize(52, '\0');
395     memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
396 
397     EXPECT_EQ(d, expectedData);
398 
399     // Ensure we can read this as JSON
400     auto newJSON = nlohmann::json::parse(jsonString);
401     EXPECT_EQ(newJSON["KEY1"], "VALUE1");
402     EXPECT_EQ(newJSON["KEY2"], "VALUE2");
403     EXPECT_EQ(newJSON["KEY3"], "VALUE3");
404 }
405 
406 // Create the UserData section that contains system info
407 TEST_F(PELTest, SysInfoSectionTest)
408 {
409     MockDataInterface dataIface;
410 
411     EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
412     EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
413     EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
414     EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
415     EXPECT_CALL(dataIface, getBootState())
416         .WillOnce(Return("State.SystemInitComplete"));
417     EXPECT_CALL(dataIface, getSystemIMKeyword())
418         .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
419 
420     std::string pid = "_PID=" + std::to_string(getpid());
421     std::vector<std::string> ad{pid};
422     AdditionalData additionalData{ad};
423 
424     auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
425 
426     EXPECT_TRUE(ud->valid());
427     EXPECT_EQ(ud->header().id, 0x5544);
428     EXPECT_EQ(ud->header().version, 0x01);
429     EXPECT_EQ(ud->header().subType, 0x01);
430     EXPECT_EQ(ud->header().componentID, 0x2000);
431 
432     // Pull out the JSON data and check it.
433     const auto& d = ud->data();
434     std::string jsonString{d.begin(), d.end()};
435     auto json = nlohmann::json::parse(jsonString);
436 
437     // Ensure the 'Process Name' entry contains the name of this test
438     // executable.
439     auto name = json["Process Name"].get<std::string>();
440     auto found = (name.find("pel_test") != std::string::npos) ||
441                  (name.find("test-openpower-pels-pel") != std::string::npos);
442     EXPECT_TRUE(found);
443     // @TODO(stwcx): remove 'pel_test' when removing autotools.
444 
445     auto version = json["FW Version ID"].get<std::string>();
446     EXPECT_EQ(version, "ABCD1234");
447 
448     auto state = json["BMCState"].get<std::string>();
449     EXPECT_EQ(state, "Ready");
450 
451     state = json["ChassisState"].get<std::string>();
452     EXPECT_EQ(state, "On");
453 
454     state = json["HostState"].get<std::string>();
455     EXPECT_EQ(state, "Off");
456 
457     state = json["BootState"].get<std::string>();
458     EXPECT_EQ(state, "SystemInitComplete");
459 
460     auto keyword = json["System IM"].get<std::string>();
461     EXPECT_EQ(keyword, "000155AA");
462 }
463 
464 // Test that the sections that override
465 //     virtual std::optional<std::string> Section::getJSON() const
466 // return valid JSON.
467 TEST_F(PELTest, SectionJSONTest)
468 {
469     auto data = pelDataFactory(TestPELType::pelSimple);
470     PEL pel{data};
471 
472     // Check that all JSON returned from the sections is
473     // parseable by nlohmann::json, which will throw an
474     // exception and fail the test if there is a problem.
475 
476     // The getJSON() response needs to be wrapped in a { } to make
477     // actual valid JSON (PEL::toJSON() usually handles that).
478 
479     auto jsonString = pel.privateHeader().getJSON('O');
480 
481     // PrivateHeader always prints JSON
482     ASSERT_TRUE(jsonString);
483     *jsonString = '{' + *jsonString + '}';
484     auto json = nlohmann::json::parse(*jsonString);
485 
486     jsonString = pel.userHeader().getJSON('O');
487 
488     // UserHeader always prints JSON
489     ASSERT_TRUE(jsonString);
490     *jsonString = '{' + *jsonString + '}';
491     json = nlohmann::json::parse(*jsonString);
492 
493     for (const auto& section : pel.optionalSections())
494     {
495         // The optional sections may or may not have implemented getJSON().
496         jsonString = section->getJSON('O');
497         if (jsonString)
498         {
499             *jsonString = '{' + *jsonString + '}';
500             auto json = nlohmann::json::parse(*jsonString);
501         }
502     }
503 }
504 
505 PelFFDCfile getJSONFFDC(const fs::path& dir)
506 {
507     PelFFDCfile ffdc;
508     ffdc.format = UserDataFormat::json;
509     ffdc.subType = 5;
510     ffdc.version = 42;
511 
512     auto inputJSON = R"({
513         "key1": "value1",
514         "key2": 42,
515         "key3" : [1, 2, 3, 4, 5],
516         "key4": {"key5": "value5"}
517     })"_json;
518 
519     // Write the JSON to a file and get its descriptor.
520     auto s = inputJSON.dump();
521     std::vector<uint8_t> data{s.begin(), s.end()};
522     ffdc.fd = writeFileAndGetFD(dir, data);
523 
524     return ffdc;
525 }
526 
527 TEST_F(PELTest, MakeJSONFileUDSectionTest)
528 {
529     auto dir = makeTempDir();
530 
531     {
532         auto ffdc = getJSONFFDC(dir);
533 
534         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
535         close(ffdc.fd);
536         ASSERT_TRUE(ud);
537         ASSERT_TRUE(ud->valid());
538         EXPECT_EQ(ud->header().id, 0x5544);
539 
540         EXPECT_EQ(ud->header().version,
541                   static_cast<uint8_t>(UserDataFormatVersion::json));
542         EXPECT_EQ(ud->header().subType,
543                   static_cast<uint8_t>(UserDataFormat::json));
544         EXPECT_EQ(ud->header().componentID,
545                   static_cast<uint16_t>(ComponentID::phosphorLogging));
546 
547         // Pull the JSON back out of the the UserData section
548         const auto& d = ud->data();
549         std::string js{d.begin(), d.end()};
550         auto json = nlohmann::json::parse(js);
551 
552         EXPECT_EQ("value1", json["key1"].get<std::string>());
553         EXPECT_EQ(42, json["key2"].get<int>());
554 
555         std::vector<int> key3Values{1, 2, 3, 4, 5};
556         EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
557 
558         std::map<std::string, std::string> key4Values{{"key5", "value5"}};
559         auto actual = json["key4"].get<std::map<std::string, std::string>>();
560         EXPECT_EQ(key4Values, actual);
561     }
562 
563     {
564         // A bad FD
565         PelFFDCfile ffdc;
566         ffdc.format = UserDataFormat::json;
567         ffdc.subType = 5;
568         ffdc.version = 42;
569         ffdc.fd = 10000;
570 
571         // The section shouldn't get made
572         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
573         ASSERT_FALSE(ud);
574     }
575 
576     fs::remove_all(dir);
577 }
578 
579 PelFFDCfile getCBORFFDC(const fs::path& dir)
580 {
581     PelFFDCfile ffdc;
582     ffdc.format = UserDataFormat::cbor;
583     ffdc.subType = 5;
584     ffdc.version = 42;
585 
586     auto inputJSON = R"({
587         "key1": "value1",
588         "key2": 42,
589         "key3" : [1, 2, 3, 4, 5],
590         "key4": {"key5": "value5"}
591     })"_json;
592 
593     // Convert the JSON to CBOR and write it to a file
594     auto data = nlohmann::json::to_cbor(inputJSON);
595     ffdc.fd = writeFileAndGetFD(dir, data);
596 
597     return ffdc;
598 }
599 
600 TEST_F(PELTest, MakeCBORFileUDSectionTest)
601 {
602     auto dir = makeTempDir();
603 
604     auto ffdc = getCBORFFDC(dir);
605     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
606     close(ffdc.fd);
607     ASSERT_TRUE(ud);
608     ASSERT_TRUE(ud->valid());
609     EXPECT_EQ(ud->header().id, 0x5544);
610 
611     EXPECT_EQ(ud->header().version,
612               static_cast<uint8_t>(UserDataFormatVersion::cbor));
613     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
614     EXPECT_EQ(ud->header().componentID,
615               static_cast<uint16_t>(ComponentID::phosphorLogging));
616 
617     // Pull the CBOR back out of the PEL section
618     // The number of pad bytes to make the section be 4B aligned
619     // was added at the end, read it and then remove it and the
620     // padding before parsing it.
621     auto data = ud->data();
622     Stream stream{data};
623     stream.offset(data.size() - 4);
624     uint32_t pad;
625     stream >> pad;
626 
627     data.resize(data.size() - 4 - pad);
628 
629     auto json = nlohmann::json::from_cbor(data);
630 
631     EXPECT_EQ("value1", json["key1"].get<std::string>());
632     EXPECT_EQ(42, json["key2"].get<int>());
633 
634     std::vector<int> key3Values{1, 2, 3, 4, 5};
635     EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
636 
637     std::map<std::string, std::string> key4Values{{"key5", "value5"}};
638     auto actual = json["key4"].get<std::map<std::string, std::string>>();
639     EXPECT_EQ(key4Values, actual);
640 
641     fs::remove_all(dir);
642 }
643 
644 PelFFDCfile getTextFFDC(const fs::path& dir)
645 {
646     PelFFDCfile ffdc;
647     ffdc.format = UserDataFormat::text;
648     ffdc.subType = 5;
649     ffdc.version = 42;
650 
651     std::string text{"this is some text that will be used for FFDC"};
652     std::vector<uint8_t> data{text.begin(), text.end()};
653 
654     ffdc.fd = writeFileAndGetFD(dir, data);
655 
656     return ffdc;
657 }
658 
659 TEST_F(PELTest, MakeTextFileUDSectionTest)
660 {
661     auto dir = makeTempDir();
662 
663     auto ffdc = getTextFFDC(dir);
664     auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
665     close(ffdc.fd);
666     ASSERT_TRUE(ud);
667     ASSERT_TRUE(ud->valid());
668     EXPECT_EQ(ud->header().id, 0x5544);
669 
670     EXPECT_EQ(ud->header().version,
671               static_cast<uint8_t>(UserDataFormatVersion::text));
672     EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
673     EXPECT_EQ(ud->header().componentID,
674               static_cast<uint16_t>(ComponentID::phosphorLogging));
675 
676     // Get the text back out
677     std::string text{ud->data().begin(), ud->data().end()};
678     EXPECT_EQ(text, "this is some text that will be used for FFDC");
679 
680     fs::remove_all(dir);
681 }
682 
683 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
684 {
685     PelFFDCfile ffdc;
686     ffdc.format = UserDataFormat::custom;
687     ffdc.subType = 5;
688     ffdc.version = 42;
689 
690     ffdc.fd = writeFileAndGetFD(dir, data);
691 
692     return ffdc;
693 }
694 
695 TEST_F(PELTest, MakeCustomFileUDSectionTest)
696 {
697     auto dir = makeTempDir();
698 
699     {
700         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
701 
702         auto ffdc = getCustomFFDC(dir, data);
703         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
704         close(ffdc.fd);
705         ASSERT_TRUE(ud);
706         ASSERT_TRUE(ud->valid());
707         EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
708         EXPECT_EQ(ud->header().id, 0x5544);
709 
710         EXPECT_EQ(ud->header().version, 42);
711         EXPECT_EQ(ud->header().subType, 5);
712         EXPECT_EQ(ud->header().componentID, 0x2002);
713 
714         // Get the data back out
715         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
716         EXPECT_EQ(data, newData);
717     }
718 
719     // Do the same thing again, but make it be non 4B aligned
720     // so the data gets padded.
721     {
722         std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
723 
724         auto ffdc = getCustomFFDC(dir, data);
725         auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
726         close(ffdc.fd);
727         ASSERT_TRUE(ud);
728         ASSERT_TRUE(ud->valid());
729         EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
730         EXPECT_EQ(ud->header().id, 0x5544);
731 
732         EXPECT_EQ(ud->header().version, 42);
733         EXPECT_EQ(ud->header().subType, 5);
734         EXPECT_EQ(ud->header().componentID, 0x2002);
735 
736         // Get the data back out
737         std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
738 
739         // pad the original to 12B so we can compare
740         data.push_back(0);
741         data.push_back(0);
742         data.push_back(0);
743 
744         EXPECT_EQ(data, newData);
745     }
746 
747     fs::remove_all(dir);
748 }
749 
750 // Test Adding FFDC from files to a PEL
751 TEST_F(PELTest, CreateWithFFDCTest)
752 {
753     auto dir = makeTempDir();
754     message::Entry regEntry;
755     uint64_t timestamp = 5;
756 
757     regEntry.name = "test";
758     regEntry.subsystem = 5;
759     regEntry.actionFlags = 0xC000;
760     regEntry.src.type = 0xBD;
761     regEntry.src.reasonCode = 0x1234;
762 
763     std::vector<std::string> additionalData{"KEY1=VALUE1"};
764     AdditionalData ad{additionalData};
765     NiceMock<MockDataInterface> dataIface;
766     NiceMock<MockJournal> journal;
767     PelFFDC ffdc;
768 
769     std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
770 
771     // This will be trimmed when added
772     std::vector<uint8_t> hugeCustomData(17000, 0x42);
773 
774     ffdc.emplace_back(std::move(getJSONFFDC(dir)));
775     ffdc.emplace_back(std::move(getCBORFFDC(dir)));
776     ffdc.emplace_back(std::move(getTextFFDC(dir)));
777     ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
778     ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
779 
780     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
781                                       "system/entry"};
782     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
783         .WillOnce(Return(std::vector<bool>{false, false, false}));
784 
785     PEL pel{regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
786             ad,       ffdc, dataIface, journal};
787 
788     EXPECT_TRUE(pel.valid());
789 
790     // Clipped to the max
791     EXPECT_EQ(pel.size(), 16384);
792 
793     // Check for the FFDC sections
794     size_t udCount = 0;
795     Section* ud = nullptr;
796 
797     for (const auto& section : pel.optionalSections())
798     {
799         if (section->header().id == static_cast<uint16_t>(SectionID::userData))
800         {
801             udCount++;
802             ud = section.get();
803         }
804     }
805 
806     EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
807 
808     // Check the last section was trimmed to
809     // something a bit less that 17000.
810     EXPECT_GT(ud->header().size, 14000);
811     EXPECT_LT(ud->header().size, 16000);
812 
813     fs::remove_all(dir);
814 }
815 
816 // Create a PEL with device callouts
817 TEST_F(PELTest, CreateWithDevCalloutsTest)
818 {
819     message::Entry regEntry;
820     uint64_t timestamp = 5;
821 
822     regEntry.name = "test";
823     regEntry.subsystem = 5;
824     regEntry.actionFlags = 0xC000;
825     regEntry.src.type = 0xBD;
826     regEntry.src.reasonCode = 0x1234;
827 
828     NiceMock<MockDataInterface> dataIface;
829     NiceMock<MockJournal> journal;
830     PelFFDC ffdc;
831 
832     const auto calloutJSON = R"(
833     {
834         "I2C":
835         {
836             "14":
837             {
838                 "114":
839                 {
840                     "Callouts":[
841                         {
842                         "Name": "/chassis/motherboard/cpu0",
843                         "LocationCode": "P1",
844                         "Priority": "H"
845                         }
846                     ],
847                     "Dest": "proc 0 target"
848                 }
849             }
850         }
851     })";
852 
853     std::vector<std::string> names{"systemA"};
854     EXPECT_CALL(dataIface, getSystemNames)
855         .Times(2)
856         .WillRepeatedly(Return(names));
857 
858     EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
859         .Times(1)
860         .WillOnce(Return("UXXX-P1"));
861 
862     EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
863         .WillOnce(Return(std::vector<std::string>{
864             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
865 
866     EXPECT_CALL(
867         dataIface,
868         getHWCalloutFields(
869             "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
870         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
871                         SetArgReferee<3>("123456789ABC")));
872 
873     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
874                                       "system/entry"};
875     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
876         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
877 
878     auto dataPath = getPELReadOnlyDataPath();
879     std::ofstream file{dataPath / "systemA_dev_callouts.json"};
880     file << calloutJSON;
881     file.close();
882 
883     {
884         std::vector<std::string> data{
885             "CALLOUT_ERRNO=5",
886             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
887             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
888 
889         AdditionalData ad{data};
890 
891         PEL pel{
892             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
893             ad,       ffdc, dataIface, journal};
894 
895         ASSERT_TRUE(pel.primarySRC().value()->callouts());
896         auto& callouts = pel.primarySRC().value()->callouts()->callouts();
897         ASSERT_EQ(callouts.size(), 1);
898         ASSERT_TRUE(pel.isHwCalloutPresent());
899 
900         EXPECT_EQ(callouts[0]->priority(), 'H');
901         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
902 
903         auto& fru = callouts[0]->fruIdentity();
904         EXPECT_EQ(fru->getPN().value(), "1234567");
905         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
906         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
907 
908         const auto& section = pel.optionalSections().back();
909 
910         ASSERT_EQ(section->header().id, 0x5544); // UD
911         auto ud = static_cast<UserData*>(section.get());
912 
913         // Check that there was a UserData section added that
914         // contains debug details about the device.
915         const auto& d = ud->data();
916         std::string jsonString{d.begin(), d.end()};
917         auto actualJSON = nlohmann::json::parse(jsonString);
918 
919         auto expectedJSON = R"(
920             {
921                 "PEL Internal Debug Data": {
922                     "SRC": [
923                       "I2C: bus: 14 address: 114 dest: proc 0 target"
924                     ]
925                 }
926             }
927         )"_json;
928 
929         EXPECT_EQ(actualJSON, expectedJSON);
930     }
931 
932     {
933         // Device path not found (wrong i2c addr), so no callouts
934         std::vector<std::string> data{
935             "CALLOUT_ERRNO=5",
936             "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
937             "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
938 
939         AdditionalData ad{data};
940 
941         PEL pel{
942             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
943             ad,       ffdc, dataIface, journal};
944 
945         // no callouts
946         EXPECT_FALSE(pel.primarySRC().value()->callouts());
947 
948         // Now check that there was a UserData section
949         // that contains the lookup error.
950         const auto& section = pel.optionalSections().back();
951 
952         ASSERT_EQ(section->header().id, 0x5544); // UD
953         auto ud = static_cast<UserData*>(section.get());
954 
955         const auto& d = ud->data();
956 
957         std::string jsonString{d.begin(), d.end()};
958 
959         auto actualJSON = nlohmann::json::parse(jsonString);
960 
961         auto expectedJSON =
962             "{\"PEL Internal Debug Data\":{\"SRC\":"
963             "[\"Problem looking up I2C callouts on 14 153: "
964             "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
965 
966         EXPECT_EQ(actualJSON, expectedJSON);
967     }
968 
969     fs::remove_all(dataPath);
970 }
971 
972 // Test PELs when the callouts are passed in using a JSON file.
973 TEST_F(PELTest, CreateWithJSONCalloutsTest)
974 {
975     PelFFDCfile ffdcFile;
976     ffdcFile.format = UserDataFormat::json;
977     ffdcFile.subType = 0xCA; // Callout JSON
978     ffdcFile.version = 1;
979 
980     // Write these callouts to a JSON file and pass it into
981     // the PEL as an FFDC file.
982     auto inputJSON = R"([
983         {
984             "Priority": "H",
985             "LocationCode": "P0-C1"
986         },
987         {
988             "Priority": "M",
989             "Procedure": "PROCEDURE"
990         }
991     ])"_json;
992 
993     auto s = inputJSON.dump();
994     std::vector<uint8_t> data{s.begin(), s.end()};
995     auto dir = makeTempDir();
996     ffdcFile.fd = writeFileAndGetFD(dir, data);
997 
998     PelFFDC ffdc;
999     ffdc.push_back(std::move(ffdcFile));
1000 
1001     AdditionalData ad;
1002     NiceMock<MockDataInterface> dataIface;
1003     NiceMock<MockJournal> journal;
1004 
1005     EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1006         .Times(1)
1007         .WillOnce(Return("UXXX-P0-C1"));
1008     EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1009         .Times(1)
1010         .WillOnce(Return(
1011             std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
1012     EXPECT_CALL(dataIface, getHWCalloutFields(
1013                                "/inv/system/chassis/motherboard/bmc", _, _, _))
1014         .Times(1)
1015         .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1016                         SetArgReferee<3>("123456789ABC")));
1017 
1018     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1019                                       "system/entry"};
1020     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1021         .WillOnce(Return(std::vector<bool>{false, false, false}));
1022 
1023     message::Entry regEntry;
1024     regEntry.name = "test";
1025     regEntry.subsystem = 5;
1026     regEntry.actionFlags = 0xC000;
1027     regEntry.src.type = 0xBD;
1028     regEntry.src.reasonCode = 0x1234;
1029 
1030     PEL pel{regEntry, 42,   5,         phosphor::logging::Entry::Level::Error,
1031             ad,       ffdc, dataIface, journal};
1032 
1033     ASSERT_TRUE(pel.valid());
1034     ASSERT_TRUE(pel.primarySRC().value()->callouts());
1035     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1036     ASSERT_EQ(callouts.size(), 2);
1037     ASSERT_TRUE(pel.isHwCalloutPresent());
1038 
1039     {
1040         EXPECT_EQ(callouts[0]->priority(), 'H');
1041         EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1042 
1043         auto& fru = callouts[0]->fruIdentity();
1044         EXPECT_EQ(fru->getPN().value(), "1234567");
1045         EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1046         EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1047         EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1048     }
1049     {
1050         EXPECT_EQ(callouts[1]->priority(), 'M');
1051         EXPECT_EQ(callouts[1]->locationCode(), "");
1052 
1053         auto& fru = callouts[1]->fruIdentity();
1054         EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1055         EXPECT_EQ(fru->failingComponentType(),
1056                   src::FRUIdentity::maintenanceProc);
1057     }
1058     fs::remove_all(dir);
1059 }
1060 
1061 // Test PELs with symblic FRU callout.
1062 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1063 {
1064     PelFFDCfile ffdcFile;
1065     ffdcFile.format = UserDataFormat::json;
1066     ffdcFile.subType = 0xCA; // Callout JSON
1067     ffdcFile.version = 1;
1068 
1069     // Write these callouts to a JSON file and pass it into
1070     // the PEL as an FFDC file.
1071     auto inputJSON = R"([
1072         {
1073             "Priority": "M",
1074             "Procedure": "SVCDOCS"
1075         }
1076     ])"_json;
1077 
1078     auto s = inputJSON.dump();
1079     std::vector<uint8_t> data{s.begin(), s.end()};
1080     auto dir = makeTempDir();
1081     ffdcFile.fd = writeFileAndGetFD(dir, data);
1082 
1083     PelFFDC ffdc;
1084     ffdc.push_back(std::move(ffdcFile));
1085 
1086     AdditionalData ad;
1087     NiceMock<MockDataInterface> dataIface;
1088     NiceMock<MockJournal> journal;
1089 
1090     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1091                                       "system/entry"};
1092     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1093         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1094 
1095     message::Entry regEntry;
1096     regEntry.name = "test";
1097     regEntry.subsystem = 5;
1098     regEntry.actionFlags = 0xC000;
1099     regEntry.src.type = 0xBD;
1100     regEntry.src.reasonCode = 0x1234;
1101 
1102     PEL pel{regEntry, 42,   5,         phosphor::logging::Entry::Level::Error,
1103             ad,       ffdc, dataIface, journal};
1104 
1105     ASSERT_TRUE(pel.valid());
1106     ASSERT_TRUE(pel.primarySRC().value()->callouts());
1107     const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1108     ASSERT_EQ(callouts.size(), 1);
1109     ASSERT_FALSE(pel.isHwCalloutPresent());
1110 
1111     {
1112         EXPECT_EQ(callouts[0]->priority(), 'M');
1113         EXPECT_EQ(callouts[0]->locationCode(), "");
1114 
1115         auto& fru = callouts[0]->fruIdentity();
1116         EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1117     }
1118     fs::remove_all(dir);
1119 }
1120 
1121 TEST_F(PELTest, FlattenLinesTest)
1122 {
1123     std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1124 
1125     auto buffer = util::flattenLines(msgs);
1126 
1127     std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1128     std::vector<uint8_t> expected(string.begin(), string.end());
1129 
1130     EXPECT_EQ(buffer, expected);
1131 }
1132 
1133 void checkJournalSection(const std::unique_ptr<Section>& section,
1134                          const std::string& expected)
1135 {
1136     ASSERT_EQ(SectionID::userData,
1137               static_cast<SectionID>(section->header().id));
1138     ASSERT_EQ(UserDataFormat::text,
1139               static_cast<UserDataFormat>(section->header().subType));
1140     ASSERT_EQ(section->header().version,
1141               static_cast<uint8_t>(UserDataFormatVersion::text));
1142 
1143     auto ud = static_cast<UserData*>(section.get());
1144 
1145     std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1146 
1147     // PEL sections are 4B aligned so add padding before the compare
1148     while (expectedData.size() % 4 != 0)
1149     {
1150         expectedData.push_back('\0');
1151     }
1152 
1153     EXPECT_EQ(ud->data(), expectedData);
1154 }
1155 
1156 TEST_F(PELTest, CaptureJournalTest)
1157 {
1158     message::Entry regEntry;
1159     uint64_t timestamp = 5;
1160 
1161     regEntry.name = "test";
1162     regEntry.subsystem = 5;
1163     regEntry.actionFlags = 0xC000;
1164     regEntry.src.type = 0xBD;
1165     regEntry.src.reasonCode = 0x1234;
1166 
1167     std::vector<std::string> data;
1168     AdditionalData ad{data};
1169     NiceMock<MockDataInterface> dataIface;
1170     NiceMock<MockJournal> journal;
1171     PelFFDC ffdc;
1172 
1173     std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1174                                       "system/entry"};
1175     EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1176         .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1177 
1178     size_t pelSectsWithOneUD{0};
1179 
1180     {
1181         // Capture 5 lines from the journal into a single UD section
1182         message::JournalCapture jc = size_t{5};
1183         regEntry.journalCapture = jc;
1184 
1185         std::vector<std::string> msgs{"test1 test2", "test3 test4",
1186                                       "test5 test6", "4", "5"};
1187 
1188         EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1189 
1190         PEL pel{
1191             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1192             ad,       ffdc, dataIface, journal};
1193 
1194         // Check the generated UserData section
1195         std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1196 
1197         checkJournalSection(pel.optionalSections().back(), expected);
1198 
1199         // Save for upcoming testcases
1200         pelSectsWithOneUD = pel.privateHeader().sectionCount();
1201     }
1202 
1203     {
1204         // Attempt to capture too many journal entries so the
1205         // section gets dropped.
1206         message::JournalCapture jc = size_t{1};
1207         regEntry.journalCapture = jc;
1208 
1209         EXPECT_CALL(journal, sync()).Times(1);
1210 
1211         // A 20000 byte line won't fit in a PEL
1212         EXPECT_CALL(journal, getMessages("", 1))
1213             .WillOnce(
1214                 Return(std::vector<std::string>{std::string(20000, 'x')}));
1215 
1216         PEL pel{
1217             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1218             ad,       ffdc, dataIface, journal};
1219 
1220         // Check for 1 fewer sections than in the previous PEL
1221         EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1222     }
1223 
1224     // Capture 3 different journal sections
1225     {
1226         message::AppCaptureList captureList{
1227             message::AppCapture{"app1", 3},
1228             message::AppCapture{"app2", 4},
1229             message::AppCapture{"app3", 1},
1230         };
1231         message::JournalCapture jc = captureList;
1232         regEntry.journalCapture = jc;
1233 
1234         std::vector<std::string> app1{"A B", "C D", "E F"};
1235         std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1236         std::vector<std::string> app3{"a b c"};
1237 
1238         std::string expected1{"A B\nC D\nE F\n"};
1239         std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1240         std::string expected3{"a b c\n"};
1241 
1242         EXPECT_CALL(journal, sync()).Times(1);
1243         EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1244         EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1245         EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1246 
1247         PEL pel{
1248             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1249             ad,       ffdc, dataIface, journal};
1250 
1251         // Two more sections than the 1 extra UD section in the first testcase
1252         ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1253 
1254         const auto& optionalSections = pel.optionalSections();
1255         auto numOptSections = optionalSections.size();
1256 
1257         checkJournalSection(optionalSections[numOptSections - 3], expected1);
1258         checkJournalSection(optionalSections[numOptSections - 2], expected2);
1259         checkJournalSection(optionalSections[numOptSections - 1], expected3);
1260     }
1261 
1262     {
1263         // One section gets saved, and one is too big and gets dropped
1264         message::AppCaptureList captureList{
1265             message::AppCapture{"app4", 2},
1266             message::AppCapture{"app5", 1},
1267         };
1268         message::JournalCapture jc = captureList;
1269         regEntry.journalCapture = jc;
1270 
1271         std::vector<std::string> app4{"w x", "y z"};
1272         std::string expected4{"w x\ny z\n"};
1273 
1274         EXPECT_CALL(journal, sync()).Times(1);
1275 
1276         EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1277 
1278         // A 20000 byte line won't fit in a PEL
1279         EXPECT_CALL(journal, getMessages("app5", 1))
1280             .WillOnce(
1281                 Return(std::vector<std::string>{std::string(20000, 'x')}));
1282 
1283         PEL pel{
1284             regEntry, 42,   timestamp, phosphor::logging::Entry::Level::Error,
1285             ad,       ffdc, dataIface, journal};
1286 
1287         // The last section should have been dropped, so same as first TC
1288         ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1289 
1290         checkJournalSection(pel.optionalSections().back(), expected4);
1291     }
1292 }
1293