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(); 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(); 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(); 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( 864 Return("/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("/inv/system/chassis/motherboard/bmc")); 1011 EXPECT_CALL(dataIface, getHWCalloutFields( 1012 "/inv/system/chassis/motherboard/bmc", _, _, _)) 1013 .Times(1) 1014 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 1015 SetArgReferee<3>("123456789ABC"))); 1016 1017 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 1018 "system/entry"}; 1019 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 1020 .WillOnce(Return(std::vector<bool>{false, false, false})); 1021 1022 message::Entry regEntry; 1023 regEntry.name = "test"; 1024 regEntry.subsystem = 5; 1025 regEntry.actionFlags = 0xC000; 1026 regEntry.src.type = 0xBD; 1027 regEntry.src.reasonCode = 0x1234; 1028 1029 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1030 ad, ffdc, dataIface, journal}; 1031 1032 ASSERT_TRUE(pel.valid()); 1033 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1034 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1035 ASSERT_EQ(callouts.size(), 2); 1036 ASSERT_TRUE(pel.isHwCalloutPresent()); 1037 1038 { 1039 EXPECT_EQ(callouts[0]->priority(), 'H'); 1040 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1041 1042 auto& fru = callouts[0]->fruIdentity(); 1043 EXPECT_EQ(fru->getPN().value(), "1234567"); 1044 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1045 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1046 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1047 } 1048 { 1049 EXPECT_EQ(callouts[1]->priority(), 'M'); 1050 EXPECT_EQ(callouts[1]->locationCode(), ""); 1051 1052 auto& fru = callouts[1]->fruIdentity(); 1053 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1054 EXPECT_EQ(fru->failingComponentType(), 1055 src::FRUIdentity::maintenanceProc); 1056 } 1057 fs::remove_all(dir); 1058 } 1059 1060 // Test PELs with symblic FRU callout. 1061 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest) 1062 { 1063 PelFFDCfile ffdcFile; 1064 ffdcFile.format = UserDataFormat::json; 1065 ffdcFile.subType = 0xCA; // Callout JSON 1066 ffdcFile.version = 1; 1067 1068 // Write these callouts to a JSON file and pass it into 1069 // the PEL as an FFDC file. 1070 auto inputJSON = R"([ 1071 { 1072 "Priority": "M", 1073 "Procedure": "SVCDOCS" 1074 } 1075 ])"_json; 1076 1077 auto s = inputJSON.dump(); 1078 std::vector<uint8_t> data{s.begin(), s.end()}; 1079 auto dir = makeTempDir(); 1080 ffdcFile.fd = writeFileAndGetFD(dir, data); 1081 1082 PelFFDC ffdc; 1083 ffdc.push_back(std::move(ffdcFile)); 1084 1085 AdditionalData ad; 1086 NiceMock<MockDataInterface> dataIface; 1087 NiceMock<MockJournal> journal; 1088 1089 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 1090 "system/entry"}; 1091 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 1092 .WillRepeatedly(Return(std::vector<bool>{false, false, false})); 1093 1094 message::Entry regEntry; 1095 regEntry.name = "test"; 1096 regEntry.subsystem = 5; 1097 regEntry.actionFlags = 0xC000; 1098 regEntry.src.type = 0xBD; 1099 regEntry.src.reasonCode = 0x1234; 1100 1101 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1102 ad, ffdc, dataIface, journal}; 1103 1104 ASSERT_TRUE(pel.valid()); 1105 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1106 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1107 ASSERT_EQ(callouts.size(), 1); 1108 ASSERT_FALSE(pel.isHwCalloutPresent()); 1109 1110 { 1111 EXPECT_EQ(callouts[0]->priority(), 'M'); 1112 EXPECT_EQ(callouts[0]->locationCode(), ""); 1113 1114 auto& fru = callouts[0]->fruIdentity(); 1115 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS"); 1116 } 1117 fs::remove_all(dir); 1118 } 1119 1120 TEST_F(PELTest, FlattenLinesTest) 1121 { 1122 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"}; 1123 1124 auto buffer = util::flattenLines(msgs); 1125 1126 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"}; 1127 std::vector<uint8_t> expected(string.begin(), string.end()); 1128 1129 EXPECT_EQ(buffer, expected); 1130 } 1131 1132 void checkJournalSection(const std::unique_ptr<Section>& section, 1133 const std::string& expected) 1134 { 1135 ASSERT_EQ(SectionID::userData, 1136 static_cast<SectionID>(section->header().id)); 1137 ASSERT_EQ(UserDataFormat::text, 1138 static_cast<UserDataFormat>(section->header().subType)); 1139 ASSERT_EQ(section->header().version, 1140 static_cast<uint8_t>(UserDataFormatVersion::text)); 1141 1142 auto ud = static_cast<UserData*>(section.get()); 1143 1144 std::vector<uint8_t> expectedData(expected.begin(), expected.end()); 1145 1146 // PEL sections are 4B aligned so add padding before the compare 1147 while (expectedData.size() % 4 != 0) 1148 { 1149 expectedData.push_back('\0'); 1150 } 1151 1152 EXPECT_EQ(ud->data(), expectedData); 1153 } 1154 1155 TEST_F(PELTest, CaptureJournalTest) 1156 { 1157 message::Entry regEntry; 1158 uint64_t timestamp = 5; 1159 1160 regEntry.name = "test"; 1161 regEntry.subsystem = 5; 1162 regEntry.actionFlags = 0xC000; 1163 regEntry.src.type = 0xBD; 1164 regEntry.src.reasonCode = 0x1234; 1165 1166 std::vector<std::string> data; 1167 AdditionalData ad{data}; 1168 NiceMock<MockDataInterface> dataIface; 1169 NiceMock<MockJournal> journal; 1170 PelFFDC ffdc; 1171 1172 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 1173 "system/entry"}; 1174 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 1175 .WillRepeatedly(Return(std::vector<bool>{false, false, false})); 1176 1177 size_t pelSectsWithOneUD{0}; 1178 1179 { 1180 // Capture 5 lines from the journal into a single UD section 1181 message::JournalCapture jc = size_t{5}; 1182 regEntry.journalCapture = jc; 1183 1184 std::vector<std::string> msgs{"test1 test2", "test3 test4", 1185 "test5 test6", "4", "5"}; 1186 1187 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs)); 1188 1189 PEL pel{ 1190 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1191 ad, ffdc, dataIface, journal}; 1192 1193 // Check the generated UserData section 1194 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"}; 1195 1196 checkJournalSection(pel.optionalSections().back(), expected); 1197 1198 // Save for upcoming testcases 1199 pelSectsWithOneUD = pel.privateHeader().sectionCount(); 1200 } 1201 1202 { 1203 // Attempt to capture too many journal entries so the 1204 // section gets dropped. 1205 message::JournalCapture jc = size_t{1}; 1206 regEntry.journalCapture = jc; 1207 1208 EXPECT_CALL(journal, sync()).Times(1); 1209 1210 // A 20000 byte line won't fit in a PEL 1211 EXPECT_CALL(journal, getMessages("", 1)) 1212 .WillOnce( 1213 Return(std::vector<std::string>{std::string(20000, 'x')})); 1214 1215 PEL pel{ 1216 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1217 ad, ffdc, dataIface, journal}; 1218 1219 // Check for 1 fewer sections than in the previous PEL 1220 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1); 1221 } 1222 1223 // Capture 3 different journal sections 1224 { 1225 message::AppCaptureList captureList{ 1226 message::AppCapture{"app1", 3}, 1227 message::AppCapture{"app2", 4}, 1228 message::AppCapture{"app3", 1}, 1229 }; 1230 message::JournalCapture jc = captureList; 1231 regEntry.journalCapture = jc; 1232 1233 std::vector<std::string> app1{"A B", "C D", "E F"}; 1234 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"}; 1235 std::vector<std::string> app3{"a b c"}; 1236 1237 std::string expected1{"A B\nC D\nE F\n"}; 1238 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"}; 1239 std::string expected3{"a b c\n"}; 1240 1241 EXPECT_CALL(journal, sync()).Times(1); 1242 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1)); 1243 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2)); 1244 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3)); 1245 1246 PEL pel{ 1247 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1248 ad, ffdc, dataIface, journal}; 1249 1250 // Two more sections than the 1 extra UD section in the first testcase 1251 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2); 1252 1253 const auto& optionalSections = pel.optionalSections(); 1254 auto numOptSections = optionalSections.size(); 1255 1256 checkJournalSection(optionalSections[numOptSections - 3], expected1); 1257 checkJournalSection(optionalSections[numOptSections - 2], expected2); 1258 checkJournalSection(optionalSections[numOptSections - 1], expected3); 1259 } 1260 1261 { 1262 // One section gets saved, and one is too big and gets dropped 1263 message::AppCaptureList captureList{ 1264 message::AppCapture{"app4", 2}, 1265 message::AppCapture{"app5", 1}, 1266 }; 1267 message::JournalCapture jc = captureList; 1268 regEntry.journalCapture = jc; 1269 1270 std::vector<std::string> app4{"w x", "y z"}; 1271 std::string expected4{"w x\ny z\n"}; 1272 1273 EXPECT_CALL(journal, sync()).Times(1); 1274 1275 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4)); 1276 1277 // A 20000 byte line won't fit in a PEL 1278 EXPECT_CALL(journal, getMessages("app5", 1)) 1279 .WillOnce( 1280 Return(std::vector<std::string>{std::string(20000, 'x')})); 1281 1282 PEL pel{ 1283 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 1284 ad, ffdc, dataIface, journal}; 1285 1286 // The last section should have been dropped, so same as first TC 1287 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD); 1288 1289 checkJournalSection(pel.optionalSections().back(), expected4); 1290 } 1291 } 1292