diff options
Diffstat (limited to 'test')
38 files changed, 6828 insertions, 33 deletions
diff --git a/test/Makefile.am b/test/Makefile.am index 7fd38d1..64aa6d8 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -9,7 +9,8 @@ check_PROGRAMS = \ remote_logging_test_address \ remote_logging_test_port \ remote_logging_test_config \ - sdjournal_mock_test + sdjournal_mock_test \ + extensions_test test_cppflags = \ -Igtest \ @@ -23,7 +24,7 @@ test_cxxflags = \ test_ldflags = \ -lgtest_main -lgtest \ - -lgmock \ + -lgmock -lstdc++fs \ $(PTHREAD_LIBS) \ $(OESDK_TESTCASE_FLAGS) \ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ @@ -33,12 +34,12 @@ test_ldadd = \ $(top_builddir)/elog_serialize.o \ $(top_builddir)/elog_entry.o \ $(top_builddir)/log_manager.o \ - $(top_builddir)/org.openbmc.Associations.o \ $(top_builddir)/xyz/openbmc_project/Logging/Internal/Manager/server.o \ $(top_builddir)/elog_meta.o \ $(top_builddir)/elog-lookup.o \ $(top_builddir)/elog-process-metadata.o \ - $(top_builddir)/sdjournal.o + $(top_builddir)/sdjournal.o \ + $(top_builddir)/extensions.o remote_logging_test_ldadd = \ $(top_builddir)/phosphor-rsyslog-config/server-conf.o \ @@ -49,48 +50,42 @@ elog_errorwrap_test_CXXFLAGS = $(test_cxxflags) elog_errorwrap_test_SOURCES = elog_errorwrap_test.cpp elog_errorwrap_test_LDADD = $(test_ldadd) elog_errorwrap_test_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) serialization_test_path_CPPFLAGS = $(test_cppflags) serialization_test_path_CXXFLAGS = $(test_cxxflags) serialization_test_path_SOURCES = serialization_test_path.cpp serialization_test_path_LDADD = $(test_ldadd) serialization_test_path_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) serialization_test_properties_CPPFLAGS = $(test_cppflags) serialization_test_properties_CXXFLAGS = $(test_cxxflags) serialization_test_properties_SOURCES = serialization_test_properties.cpp serialization_test_properties_LDADD = $(test_ldadd) serialization_test_properties_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) remote_logging_test_address_CPPFLAGS = $(test_cppflags) remote_logging_test_address_CXXFLAGS = $(test_cxxflags) remote_logging_test_address_SOURCES = remote_logging_test_address.cpp remote_logging_test_address_LDADD = $(remote_logging_test_ldadd) remote_logging_test_address_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) remote_logging_test_port_CPPFLAGS = $(test_cppflags) remote_logging_test_port_CXXFLAGS = $(test_cxxflags) remote_logging_test_port_SOURCES = remote_logging_test_port.cpp remote_logging_test_port_LDADD = $(remote_logging_test_ldadd) remote_logging_test_port_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) remote_logging_test_config_CPPFLAGS = $(test_cppflags) remote_logging_test_config_CXXFLAGS = $(test_cxxflags) remote_logging_test_config_SOURCES = remote_logging_test_config.cpp remote_logging_test_config_LDADD = $(remote_logging_test_ldadd) remote_logging_test_config_LDFLAGS = \ - $(test_ldflags) \ - -lstdc++fs + $(test_ldflags) sdjournal_mock_test_CPPFLAGS = $(test_cppflags) sdjournal_mock_test_CXXFLAGS = $(test_cxxflags) @@ -98,5 +93,22 @@ sdjournal_mock_test_SOURCES = sdtest.cpp sdjournal_mock_test_LDADD = $(top_builddir)/sdjournal.o sdjournal_mock_test_LDFLAGS = $(test_ldflags) +extensions_test_CPPFLAGS = $(test_cppflags) +extensions_test_CXXFLAGS = $(test_cxxflags) +extensions_test_SOURCES = extensions_test.cpp +extensions_test_LDADD = \ + $(top_builddir)/elog_entry.o \ + $(top_builddir)/elog-lookup.o \ + $(top_builddir)/elog_meta.o \ + $(top_builddir)/elog-process-metadata.o \ + $(top_builddir)/elog_serialize.o \ + $(top_builddir)/log_manager.o \ + $(top_builddir)/xyz/openbmc_project/Logging/Internal/Manager/server.o +extensions_test_LDFLAGS = $(test_ldflags) + # TODO Remove once the test-case failure is resolved openbmc/phosphor-logging#11 XFAIL_TESTS = elog_errorwrap_test + +if ENABLE_PEL_EXTENSION +include openpower-pels/Makefile.include +endif diff --git a/test/extensions_test.cpp b/test/extensions_test.cpp new file mode 100644 index 0000000..17c3395 --- /dev/null +++ b/test/extensions_test.cpp @@ -0,0 +1,98 @@ +#include "elog_entry.hpp" +#include "extensions.hpp" + +#include <gtest/gtest.h> + +using namespace phosphor::logging; + +// gtest doesn't like this happening in another file, so do it here. +StartupFunctions Extensions::startupFunctions{}; +CreateFunctions Extensions::createFunctions{}; +DeleteFunctions Extensions::deleteFunctions{}; +DeleteProhibitedFunctions Extensions::deleteProhibitedFunctions{}; +Extensions::DefaultErrorCaps Extensions::defaultErrorCaps = + Extensions::DefaultErrorCaps::enable; + +void startup1(internal::Manager& manager) +{ +} + +void startup2(internal::Manager& manager) +{ +} + +void create1(const std::string& message, uint32_t id, uint64_t timestamp, + Entry::Level severity, const AdditionalDataArg& additionalData, + const AssociationEndpointsArg& assocs) +{ +} + +void create2(const std::string& message, uint32_t id, uint64_t timestamp, + Entry::Level severity, const AdditionalDataArg& additionalData, + const AssociationEndpointsArg& assocs) +{ +} + +void deleteLog1(uint32_t id) +{ +} + +void deleteLog2(uint32_t id) +{ +} + +void deleteProhibited1(uint32_t id, bool& prohibited) +{ + prohibited = true; +} + +void deleteProhibited2(uint32_t id, bool& prohibited) +{ + prohibited = true; +} + +DISABLE_LOG_ENTRY_CAPS(); +REGISTER_EXTENSION_FUNCTION(startup1); +REGISTER_EXTENSION_FUNCTION(startup2); +REGISTER_EXTENSION_FUNCTION(create1); +REGISTER_EXTENSION_FUNCTION(create2); +REGISTER_EXTENSION_FUNCTION(deleteProhibited1); +REGISTER_EXTENSION_FUNCTION(deleteProhibited2); +REGISTER_EXTENSION_FUNCTION(deleteLog1); +REGISTER_EXTENSION_FUNCTION(deleteLog2); + +TEST(ExtensionsTest, FunctionCallTest) +{ + auto bus = sdbusplus::bus::new_default(); + internal::Manager manager(bus, "testpath"); + + EXPECT_EQ(Extensions::getStartupFunctions().size(), 2); + for (auto& s : Extensions::getStartupFunctions()) + { + s(manager); + } + + AdditionalDataArg ad; + AssociationEndpointsArg assocs; + EXPECT_EQ(Extensions::getCreateFunctions().size(), 2); + for (auto& c : Extensions::getCreateFunctions()) + { + c("test", 5, 6, Entry::Level::Informational, ad, assocs); + } + + EXPECT_EQ(Extensions::getDeleteFunctions().size(), 2); + for (auto& d : Extensions::getDeleteFunctions()) + { + d(5); + } + + EXPECT_EQ(Extensions::getDeleteProhibitedFunctions().size(), 2); + for (auto& p : Extensions::getDeleteProhibitedFunctions()) + { + bool prohibited = false; + p(5, prohibited); + EXPECT_TRUE(prohibited); + } + + EXPECT_TRUE(Extensions::disableDefaultLogCaps()); +} diff --git a/test/openpower-pels/Makefile.include b/test/openpower-pels/Makefile.include new file mode 100644 index 0000000..09eb36b --- /dev/null +++ b/test/openpower-pels/Makefile.include @@ -0,0 +1,364 @@ +TESTS += $(check_PROGRAMS) + +check_PROGRAMS += \ + additional_data_test \ + ascii_string_test \ + bcd_time_test \ + event_logger_test \ + extended_user_header_test \ + failing_mtms_test \ + fru_identity_test \ + generic_section_test \ + host_notifier_test \ + json_utils_test \ + log_id_test \ + mru_test \ + mtms_test \ + pce_identity_test \ + pel_manager_test \ + pel_rules_test \ + pel_test \ + pel_values_test \ + private_header_test \ + real_pel_test \ + registry_test \ + repository_test \ + section_header_test \ + severity_test \ + src_test \ + src_callout_test \ + src_callouts_test \ + stream_test \ + user_data_test \ + user_header_test + +pel_objects = \ + $(top_builddir)/extensions/openpower-pels/ascii_string.o \ + $(top_builddir)/extensions/openpower-pels/bcd_time.o \ + $(top_builddir)/extensions/openpower-pels/callout.o \ + $(top_builddir)/extensions/openpower-pels/callouts.o \ + $(top_builddir)/extensions/openpower-pels/extended_user_header.o \ + $(top_builddir)/extensions/openpower-pels/failing_mtms.o \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o \ + $(top_builddir)/extensions/openpower-pels/generic.o \ + $(top_builddir)/extensions/openpower-pels/json_utils.o \ + $(top_builddir)/extensions/openpower-pels/log_id.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o \ + $(top_builddir)/extensions/openpower-pels/mru.o \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o \ + $(top_builddir)/extensions/openpower-pels/pel.o \ + $(top_builddir)/extensions/openpower-pels/pel_rules.o \ + $(top_builddir)/extensions/openpower-pels/pel_values.o \ + $(top_builddir)/extensions/openpower-pels/private_header.o \ + $(top_builddir)/extensions/openpower-pels/registry.o \ + $(top_builddir)/extensions/openpower-pels/section_factory.o \ + $(top_builddir)/extensions/openpower-pels/severity.o \ + $(top_builddir)/extensions/openpower-pels/src.o \ + $(top_builddir)/extensions/openpower-pels/user_data.o \ + $(top_builddir)/extensions/openpower-pels/user_header.o + +additional_data_test_SOURCES = %reldir%/additional_data_test.cpp +additional_data_test_CPPFLAGS = $(test_cppflags) +additional_data_test_CXXFLAGS = $(test_cxxflags) +additional_data_test_LDADD = $(test_ldadd) +additional_data_test_LDFLAGS = $(test_ldflags) + +stream_test_SOURCES = %reldir%/stream_test.cpp +stream_test_CPPFLAGS = $(test_cppflags) +stream_test_CXXFLAGS = $(test_cxxflags) +stream_test_LDADD = $(test_ldadd) +stream_test_LDFLAGS = $(test_ldflags) + +bcd_time_test_SOURCES = \ + %reldir%/bcd_time_test.cpp +bcd_time_test_CPPFLAGS = $(test_cppflags) +bcd_time_test_CXXFLAGS = $(test_cxxflags) +bcd_time_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/bcd_time.o +bcd_time_test_LDFLAGS = $(test_ldflags) + +section_header_test_SOURCES = \ + %reldir%/section_header_test.cpp +section_header_test_CPPFLAGS = $(test_cppflags) +section_header_test_CXXFLAGS = $(test_cxxflags) +section_header_test_LDADD = $(test_ldadd) +section_header_test_LDFLAGS = $(test_ldflags) + +private_header_test_SOURCES = \ + %reldir%/private_header_test.cpp %reldir%/pel_utils.cpp %reldir%/paths.cpp +private_header_test_CPPFLAGS = $(test_cppflags) +private_header_test_CXXFLAGS = $(test_cxxflags) +private_header_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) +private_header_test_LDFLAGS = $(test_ldflags) + +user_header_test_SOURCES = \ + %reldir%/user_header_test.cpp %reldir%/pel_utils.cpp %reldir%/paths.cpp +user_header_test_CPPFLAGS = $(test_cppflags) +user_header_test_CXXFLAGS = $(test_cxxflags) +user_header_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) +user_header_test_LDFLAGS = $(test_ldflags) + +log_id_test_SOURCES = \ + %reldir%/log_id_test.cpp %reldir%/paths.cpp +log_id_test_CPPFLAGS = $(test_cppflags) +log_id_test_CXXFLAGS = $(test_cxxflags) +log_id_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/log_id.o +log_id_test_LDFLAGS = $(test_ldflags) + +pel_test_SOURCES = \ + %reldir%/pel_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp +pel_test_CPPFLAGS = $(test_cppflags) +pel_test_CXXFLAGS = $(test_cxxflags) +pel_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) +pel_test_LDFLAGS = $(test_ldflags) + +real_pel_test_SOURCES = \ + %reldir%/real_pel_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp +real_pel_test_CPPFLAGS = $(test_cppflags) +real_pel_test_CXXFLAGS = $(test_cxxflags) +real_pel_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) +real_pel_test_LDFLAGS = $(test_ldflags) + +repository_test_SOURCES = \ + %reldir%/repository_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp +repository_test_CPPFLAGS = $(test_cppflags) +repository_test_CXXFLAGS = $(test_cxxflags) +repository_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) \ + $(top_builddir)/extensions/openpower-pels/repository.o +repository_test_LDFLAGS = $(test_ldflags) + +pel_manager_test_SOURCES = \ + %reldir%/pel_manager_test.cpp %reldir%/paths.cpp %reldir%/pel_utils.cpp +pel_manager_test_CPPFLAGS = $(test_cppflags) +pel_manager_test_CXXFLAGS = \ + $(test_cxxflags) \ + $(SDEVENTPLUS_CFLAGS) +pel_manager_test_LDADD = \ + $(test_ldadd) \ + $(pel_objects) \ + $(top_builddir)/extensions/openpower-pels/data_interface.o \ + $(top_builddir)/extensions/openpower-pels/host_notifier.o \ + $(top_builddir)/extensions/openpower-pels/manager.o \ + $(top_builddir)/extensions/openpower-pels/repository.o +pel_manager_test_LDFLAGS = \ + $(test_ldflags) \ + $(SDEVENTPLUS_LIBS) + +registry_test_SOURCES = \ + %reldir%/registry_test.cpp %reldir%/paths.cpp +registry_test_CPPFLAGS = $(test_cppflags) +registry_test_CXXFLAGS = $(test_cxxflags) +registry_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/registry.o \ + $(top_builddir)/extensions/openpower-pels/pel_values.o +registry_test_LDFLAGS = $(test_ldflags) + +severity_test_SOURCES = %reldir%/severity_test.cpp +severity_test_CPPFLAGS = $(test_cppflags) +severity_test_CXXFLAGS = $(test_cxxflags) +severity_test_LDADD = \ + $(test_ldflags) \ + $(top_builddir)/extensions/openpower-pels/severity.o +severity_test_LDFLAGS = $(test_ldflags) + +mtms_test_SOURCES = %reldir%/mtms_test.cpp +mtms_test_CPPFLAGS = $(test_cppflags) +mtms_test_CXXFLAGS = $(test_cxxflags) +mtms_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/mtms.o +mtms_test_LDFLAGS = $(test_ldflags) + +failing_mtms_test_SOURCES = %reldir%/failing_mtms_test.cpp +failing_mtms_test_CPPFLAGS = $(test_cppflags) +failing_mtms_test_CXXFLAGS = $(test_cxxflags) +failing_mtms_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/failing_mtms.o \ + $(top_builddir)/extensions/openpower-pels/json_utils.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o +failing_mtms_test_LDFLAGS = $(test_ldflags) + +pel_values_test_SOURCES = %reldir%/pel_values_test.cpp +pel_values_test_CPPFLAGS = $(test_cppflags) +pel_values_test_CXXFLAGS = $(test_cxxflags) +pel_values_test_LDADD = \ + $(test_ldflags) \ + $(top_builddir)/extensions/openpower-pels/pel_values.o +pel_values_test_LDFLAGS = $(test_ldflags) + +generic_section_test_SOURCES = \ + %reldir%/generic_section_test.cpp %reldir%/pel_utils.cpp +generic_section_test_CPPFLAGS = $(test_cppflags) +generic_section_test_CXXFLAGS = $(test_cxxflags) +generic_section_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/generic.o +generic_section_test_LDFLAGS = $(test_ldflags) + +user_data_test_SOURCES = \ + %reldir%/user_data_test.cpp %reldir%/pel_utils.cpp +user_data_test_CPPFLAGS = $(test_cppflags) +user_data_test_CXXFLAGS = $(test_cxxflags) +user_data_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/user_data.o +user_data_test_LDFLAGS = $(test_ldflags) + +ascii_string_test_SOURCES = %reldir%/ascii_string_test.cpp +ascii_string_test_CPPFLAGS = $(test_cppflags) +ascii_string_test_CXXFLAGS = $(test_cxxflags) +ascii_string_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/ascii_string.o +ascii_string_test_LDFLAGS = $(test_ldflags) + +fru_identity_test_SOURCES = %reldir%/fru_identity_test.cpp +fru_identity_test_CPPFLAGS = $(test_cppflags) +fru_identity_test_CXXFLAGS = $(test_cxxflags) +fru_identity_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o +fru_identity_test_LDFLAGS = $(test_ldflags) + +pce_identity_test_SOURCES = %reldir%/pce_identity_test.cpp +pce_identity_test_CPPFLAGS = $(test_cppflags) +pce_identity_test_CXXFLAGS = $(test_cxxflags) +pce_identity_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o +pce_identity_test_LDFLAGS = $(test_ldflags) + +mru_test_SOURCES = %reldir%/mru_test.cpp +mru_test_CPPFLAGS = $(test_cppflags) +mru_test_CXXFLAGS = $(test_cxxflags) +mru_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/mru.o +mru_test_LDFLAGS = $(test_ldflags) + +src_callout_test_SOURCES = \ + %reldir%/src_callout_test.cpp \ + %reldir%/pel_utils.cpp +src_callout_test_CPPFLAGS = $(test_cppflags) +src_callout_test_CXXFLAGS = $(test_cxxflags) +src_callout_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/callout.o \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o \ + $(top_builddir)/extensions/openpower-pels/mru.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o +src_callout_test_LDFLAGS = $(test_ldflags) + +src_callouts_test_SOURCES = \ + %reldir%/src_callouts_test.cpp \ + %reldir%/pel_utils.cpp +src_callouts_test_CPPFLAGS = $(test_cppflags) +src_callouts_test_CXXFLAGS = $(test_cxxflags) +src_callouts_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/callout.o \ + $(top_builddir)/extensions/openpower-pels/callouts.o \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o \ + $(top_builddir)/extensions/openpower-pels/mru.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o +src_callouts_test_LDFLAGS = $(test_ldflags) + +src_test_SOURCES = \ + %reldir%/src_test.cpp \ + %reldir%/pel_utils.cpp +src_test_CPPFLAGS = $(test_cppflags) +src_test_CXXFLAGS = $(test_cxxflags) +src_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/ascii_string.o \ + $(top_builddir)/extensions/openpower-pels/callout.o \ + $(top_builddir)/extensions/openpower-pels/callouts.o \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o \ + $(top_builddir)/extensions/openpower-pels/json_utils.o \ + $(top_builddir)/extensions/openpower-pels/paths.o \ + $(top_builddir)/extensions/openpower-pels/mru.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o \ + $(top_builddir)/extensions/openpower-pels/pel_values.o \ + $(top_builddir)/extensions/openpower-pels/registry.o \ + $(top_builddir)/extensions/openpower-pels/src.o +src_test_LDFLAGS = $(test_ldflags) + +extended_user_header_test_SOURCES = \ + %reldir%/extended_user_header_test.cpp \ + %reldir%/pel_utils.cpp +extended_user_header_test_CPPFLAGS = $(test_cppflags) +extended_user_header_test_CXXFLAGS = $(test_cxxflags) +extended_user_header_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/ascii_string.o \ + $(top_builddir)/extensions/openpower-pels/bcd_time.o \ + $(top_builddir)/extensions/openpower-pels/callout.o \ + $(top_builddir)/extensions/openpower-pels/callouts.o \ + $(top_builddir)/extensions/openpower-pels/data_interface.o \ + $(top_builddir)/extensions/openpower-pels/extended_user_header.o \ + $(top_builddir)/extensions/openpower-pels/fru_identity.o \ + $(top_builddir)/extensions/openpower-pels/json_utils.o \ + $(top_builddir)/extensions/openpower-pels/mru.o \ + $(top_builddir)/extensions/openpower-pels/mtms.o \ + $(top_builddir)/extensions/openpower-pels/paths.o \ + $(top_builddir)/extensions/openpower-pels/pel_values.o \ + $(top_builddir)/extensions/openpower-pels/pce_identity.o \ + $(top_builddir)/extensions/openpower-pels/registry.o \ + $(top_builddir)/extensions/openpower-pels/src.o +extended_user_header_test_LDFLAGS = $(test_ldflags) + +pel_rules_test_SOURCES = %reldir%/pel_rules_test.cpp +pel_rules_test_CPPFLAGS = $(test_cppflags) +pel_rules_test_CXXFLAGS = $(test_cxxflags) +pel_rules_test_LDADD = \ + $(test_ldflags) \ + $(top_builddir)/extensions/openpower-pels/pel_rules.o +pel_rules_test_LDFLAGS = $(test_ldflags) + +host_notifier_test_SOURCES = \ + %reldir%/host_notifier_test.cpp \ + %reldir%/paths.cpp \ + %reldir%/pel_utils.cpp +host_notifier_test_CPPFLAGS = $(test_cppflags) +host_notifier_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS) +host_notifier_test_LDADD = \ + $(test_ldflags) \ + $(pel_objects) \ + $(top_builddir)/extensions/openpower-pels/host_notifier.o \ + $(top_builddir)/extensions/openpower-pels/repository.o +host_notifier_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS) + +json_utils_test_SOURCES = %reldir%/json_utils_test.cpp +json_utils_test_CPPFLAGS = $(test_cppflags) +json_utils_test_CXXFLAGS = $(test_cxxflags) +json_utils_test_LDADD = \ + $(test_ldadd) \ + $(top_builddir)/extensions/openpower-pels/json_utils.o +json_utils_test_LDFLAGS = $(test_ldflags) + +event_logger_test_SOURCES = \ + %reldir%/event_logger_test.cpp +event_logger_test_CPPFLAGS = $(test_cppflags) +event_logger_test_CXXFLAGS = $(test_cxxflags) $(SDEVENTPLUS_CFLAGS) +event_logger_test_LDADD = \ + $(test_ldadd) +event_logger_test_LDFLAGS = $(test_ldflags) $(SDEVENTPLUS_LIBS) diff --git a/test/openpower-pels/additional_data_test.cpp b/test/openpower-pels/additional_data_test.cpp new file mode 100644 index 0000000..c93fdf2 --- /dev/null +++ b/test/openpower-pels/additional_data_test.cpp @@ -0,0 +1,65 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/additional_data.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(AdditionalDataTest, GetKeywords) +{ + std::vector<std::string> data{"KEY1=VALUE1", "KEY2=VALUE2", + "KEY3=", "HELLOWORLD", "=VALUE5"}; + AdditionalData ad{data}; + + EXPECT_TRUE(ad.getValue("KEY1")); + EXPECT_EQ(*(ad.getValue("KEY1")), "VALUE1"); + + EXPECT_TRUE(ad.getValue("KEY2")); + EXPECT_EQ(*(ad.getValue("KEY2")), "VALUE2"); + + EXPECT_FALSE(ad.getValue("x")); + + auto value3 = ad.getValue("KEY3"); + EXPECT_TRUE(value3); + EXPECT_TRUE((*value3).empty()); + + EXPECT_FALSE(ad.getValue("HELLOWORLD")); + EXPECT_FALSE(ad.getValue("VALUE5")); + + auto json = ad.toJSON(); + std::string expected = R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":""})"; + EXPECT_EQ(json.dump(), expected); + + ad.remove("KEY1"); + EXPECT_FALSE(ad.getValue("KEY1")); +} + +TEST(AdditionalDataTest, AddData) +{ + AdditionalData ad; + + ad.add("KEY1", "VALUE1"); + EXPECT_EQ(*(ad.getValue("KEY1")), "VALUE1"); + + ad.add("KEY2", "VALUE2"); + EXPECT_EQ(*(ad.getValue("KEY2")), "VALUE2"); + + std::map<std::string, std::string> expected{{"KEY1", "VALUE1"}, + {"KEY2", "VALUE2"}}; + + EXPECT_EQ(expected, ad.getData()); +} diff --git a/test/openpower-pels/ascii_string_test.cpp b/test/openpower-pels/ascii_string_test.cpp new file mode 100644 index 0000000..96ee4ad --- /dev/null +++ b/test/openpower-pels/ascii_string_test.cpp @@ -0,0 +1,86 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/ascii_string.hpp" +#include "extensions/openpower-pels/registry.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(AsciiStringTest, AsciiStringTest) +{ + // Build the ASCII string from a message registry entry + message::Entry entry; + entry.src.type = 0xBD; + entry.src.reasonCode = 0xABCD; + entry.subsystem = 0x37; + + src::AsciiString as{entry}; + + auto data = as.get(); + + EXPECT_EQ(data, "BD37ABCD "); + + // Now flatten it + std::vector<uint8_t> flattenedData; + Stream stream{flattenedData}; + + as.flatten(stream); + + for (size_t i = 0; i < 32; i++) + { + EXPECT_EQ(data[i], flattenedData[i]); + } +} + +// A 0x11 power SRC doesn't have the subsystem in it +TEST(AsciiStringTest, PowerErrorTest) +{ + message::Entry entry; + entry.src.type = 0x11; + entry.src.reasonCode = 0xABCD; + entry.subsystem = 0x37; + + src::AsciiString as{entry}; + auto data = as.get(); + + EXPECT_EQ(data, "1100ABCD "); +} + +TEST(AsciiStringTest, UnflattenTest) +{ + std::vector<uint8_t> rawData{'B', 'D', '5', '6', '1', '2', 'A', 'B'}; + + for (int i = 8; i < 32; i++) + { + rawData.push_back(' '); + } + + Stream stream{rawData}; + src::AsciiString as{stream}; + + auto data = as.get(); + + EXPECT_EQ(data, "BD5612AB "); +} + +TEST(AsciiStringTest, UnderflowTest) +{ + std::vector<uint8_t> rawData{'B', 'D', '5', '6'}; + Stream stream{rawData}; + + EXPECT_THROW(src::AsciiString as{stream}, std::out_of_range); +} diff --git a/test/openpower-pels/bcd_time_test.cpp b/test/openpower-pels/bcd_time_test.cpp new file mode 100644 index 0000000..6fe761c --- /dev/null +++ b/test/openpower-pels/bcd_time_test.cpp @@ -0,0 +1,105 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/bcd_time.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(BCDTimeTest, ToBCDTest) +{ + EXPECT_EQ(toBCD(0), 0x00); + EXPECT_EQ(toBCD(1), 0x01); + EXPECT_EQ(toBCD(10), 0x10); + EXPECT_EQ(toBCD(99), 0x99); + EXPECT_EQ(toBCD(37), 0x37); + EXPECT_EQ(toBCD(60), 0x60); + EXPECT_EQ(toBCD(12345678), 0x12345678); + EXPECT_EQ(toBCD(0xF), 0x15); +} + +TEST(BCDTimeTest, FlattenUnflattenTest) +{ + std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; + Stream stream{data}; + BCDTime bcd; + + // Unflatten + stream >> bcd; + + EXPECT_EQ(bcd.yearMSB, 1); + EXPECT_EQ(bcd.yearLSB, 2); + EXPECT_EQ(bcd.month, 3); + EXPECT_EQ(bcd.day, 4); + EXPECT_EQ(bcd.hour, 5); + EXPECT_EQ(bcd.minutes, 6); + EXPECT_EQ(bcd.seconds, 7); + EXPECT_EQ(bcd.hundredths, 8); + + // Flatten + uint8_t val = 0x20; + bcd.yearMSB = val++; + bcd.yearLSB = val++; + bcd.month = val++; + bcd.day = val++; + bcd.hour = val++; + bcd.minutes = val++; + bcd.seconds = val++; + bcd.hundredths = val++; + + stream.offset(0); + stream << bcd; + + for (size_t i = 0; i < 8; i++) + { + EXPECT_EQ(data[i], 0x20 + i); + } +} + +TEST(BCDTimeTest, ConvertTest) +{ + // Convert a time_point into BCDTime + tm time_tm; + time_tm.tm_year = 125; + time_tm.tm_mon = 11; + time_tm.tm_mday = 31; + time_tm.tm_hour = 15; + time_tm.tm_min = 23; + time_tm.tm_sec = 42; + time_tm.tm_isdst = 0; + + auto timepoint = std::chrono::system_clock::from_time_t(mktime(&time_tm)); + auto timeInBCD = getBCDTime(timepoint); + + EXPECT_EQ(timeInBCD.yearMSB, 0x20); + EXPECT_EQ(timeInBCD.yearLSB, 0x25); + EXPECT_EQ(timeInBCD.month, 0x12); + EXPECT_EQ(timeInBCD.day, 0x31); + EXPECT_EQ(timeInBCD.hour, 0x15); + EXPECT_EQ(timeInBCD.minutes, 0x23); + EXPECT_EQ(timeInBCD.seconds, 0x42); + EXPECT_EQ(timeInBCD.hundredths, 0x00); +} + +TEST(BCDTimeTest, ConvertFromMSTest) +{ + auto now = std::chrono::system_clock::now(); + uint64_t ms = std::chrono::duration_cast<std::chrono::milliseconds>( + now.time_since_epoch()) + .count(); + + ASSERT_EQ(getBCDTime(now), getBCDTime(ms)); +} diff --git a/test/openpower-pels/event_logger_test.cpp b/test/openpower-pels/event_logger_test.cpp new file mode 100644 index 0000000..1a4d8fc --- /dev/null +++ b/test/openpower-pels/event_logger_test.cpp @@ -0,0 +1,126 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/event_logger.hpp" +#include "log_manager.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace phosphor::logging; + +class CreateHelper +{ + public: + void create(const std::string& name, Entry::Level level, + const EventLogger::ADMap& ad) + { + _createCount++; + _prevName = name; + _prevLevel = level; + _prevAD = ad; + + // Try to create another event from within the creation + // function. Should never work or else we could get stuck + // infinitely creating events. + if (_eventLogger) + { + AdditionalData d; + _eventLogger->log(name, level, d); + } + } + + size_t _createCount = 0; + std::string _prevName; + Entry::Level _prevLevel; + EventLogger::ADMap _prevAD; + EventLogger* _eventLogger = nullptr; +}; + +void runEvents(sd_event* event, size_t numEvents) +{ + sdeventplus::Event e{event}; + + for (size_t i = 0; i < numEvents; i++) + { + e.run(std::chrono::milliseconds(1)); + } +} + +TEST(EventLoggerTest, TestCreateEvents) +{ + sd_event* sdEvent = nullptr; + auto r = sd_event_default(&sdEvent); + ASSERT_TRUE(r >= 0); + + CreateHelper ch; + + EventLogger eventLogger( + sdEvent, std::bind(std::mem_fn(&CreateHelper::create), &ch, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)); + + ch._eventLogger = &eventLogger; + + AdditionalData ad; + ad.add("key1", "value1"); + + eventLogger.log("one", Entry::Level::Error, ad); + EXPECT_EQ(eventLogger.queueSize(), 1); + + runEvents(sdEvent, 1); + + // Verify 1 event was created + EXPECT_EQ(eventLogger.queueSize(), 0); + EXPECT_EQ(ch._prevName, "one"); + EXPECT_EQ(ch._prevLevel, Entry::Level::Error); + EXPECT_EQ(ch._prevAD, ad.getData()); + EXPECT_EQ(ch._createCount, 1); + + // Create 2 more, and run 1 event loop at a time and check the results + eventLogger.log("two", Entry::Level::Error, ad); + eventLogger.log("three", Entry::Level::Error, ad); + + EXPECT_EQ(eventLogger.queueSize(), 2); + + runEvents(sdEvent, 1); + + EXPECT_EQ(ch._createCount, 2); + EXPECT_EQ(ch._prevName, "two"); + EXPECT_EQ(eventLogger.queueSize(), 1); + + runEvents(sdEvent, 1); + EXPECT_EQ(ch._createCount, 3); + EXPECT_EQ(ch._prevName, "three"); + EXPECT_EQ(eventLogger.queueSize(), 0); + + // Add them all again and run them all at once + eventLogger.log("three", Entry::Level::Error, ad); + eventLogger.log("two", Entry::Level::Error, ad); + eventLogger.log("one", Entry::Level::Error, ad); + runEvents(sdEvent, 3); + + EXPECT_EQ(ch._createCount, 6); + EXPECT_EQ(ch._prevName, "one"); + EXPECT_EQ(eventLogger.queueSize(), 0); + + // Run extra events - doesn't do anything + runEvents(sdEvent, 1); + EXPECT_EQ(ch._createCount, 6); + EXPECT_EQ(ch._prevName, "one"); + EXPECT_EQ(eventLogger.queueSize(), 0); + + sd_event_unref(sdEvent); +} diff --git a/test/openpower-pels/extended_user_header_test.cpp b/test/openpower-pels/extended_user_header_test.cpp new file mode 100644 index 0000000..5cb3e9f --- /dev/null +++ b/test/openpower-pels/extended_user_header_test.cpp @@ -0,0 +1,301 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/extended_user_header.hpp" +#include "mocks.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using ::testing::Return; + +const std::vector<uint8_t> sectionData{ + // section header + 'E', 'H', 0x00, 0x60, // ID and Size + 0x01, 0x00, // version, subtype + 0x03, 0x04, // comp ID + + // MTMS + 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', + + // Server FW version + 'S', 'E', 'R', 'V', 'E', 'R', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', + '\0', + + // Subsystem FW Version + 'B', 'M', 'C', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', '\0', '\0', + '\0', '\0', + + // Reserved + 0x00, 0x00, 0x00, 0x00, + + // Reference time + 0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, + + // Reserved + 0x00, 0x00, 0x00, + + // SymptomID length + 20, + + // SymptomID + 'B', 'D', '8', 'D', '4', '2', '0', '0', '_', '1', '2', '3', '4', '5', '6', + '7', '8', '\0', '\0', '\0'}; + +// The section size without the symptom ID +const size_t baseSectionSize = 76; + +TEST(ExtUserHeaderTest, StreamConstructorTest) +{ + auto data = sectionData; + Stream stream{data}; + ExtendedUserHeader euh{stream}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + EXPECT_EQ(euh.header().size, sectionData.size()); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x0304); + + EXPECT_EQ(euh.flattenedSize(), sectionData.size()); + EXPECT_EQ(euh.machineTypeModel(), "TTTT-MMM"); + EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC"); + EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION"); + EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION"); + EXPECT_EQ(euh.symptomID(), "BD8D4200_12345678"); + + BCDTime time{0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + EXPECT_EQ(time, euh.refTime()); + + // Flatten it and make sure nothing changes + std::vector<uint8_t> newData; + Stream newStream{newData}; + + euh.flatten(newStream); + EXPECT_EQ(sectionData, newData); +} + +// Same as above, with with symptom ID empty +TEST(ExtUserHeaderTest, StreamConstructorNoIDTest) +{ + auto data = sectionData; + data.resize(baseSectionSize); + data[3] = baseSectionSize; // The size in the header + data.back() = 0; // Symptom ID length + + Stream stream{data}; + ExtendedUserHeader euh{stream}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + EXPECT_EQ(euh.header().size, baseSectionSize); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x0304); + + EXPECT_EQ(euh.flattenedSize(), baseSectionSize); + EXPECT_EQ(euh.machineTypeModel(), "TTTT-MMM"); + EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC"); + EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION"); + EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION"); + EXPECT_EQ(euh.symptomID(), ""); + + BCDTime time{0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60}; + EXPECT_EQ(time, euh.refTime()); + + // Flatten it and make sure nothing changes + std::vector<uint8_t> newData; + Stream newStream{newData}; + + euh.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(ExtUserHeaderTest, ConstructorTest) +{ + auto srcData = pelDataFactory(TestPELType::primarySRCSection); + Stream srcStream{srcData}; + SRC src{srcStream}; + + message::Entry entry; // Empty Symptom ID vector + + { + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getMachineTypeModel()) + .WillOnce(Return("AAAA-BBB")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()) + .WillOnce(Return("123456789ABC")); + + EXPECT_CALL(dataIface, getServerFWVersion()) + .WillOnce(Return("SERVER_VERSION")); + + EXPECT_CALL(dataIface, getBMCFWVersion()) + .WillOnce(Return("BMC_VERSION")); + + ExtendedUserHeader euh{dataIface, entry, src}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + + // The symptom ID accounts for the extra 20 bytes + EXPECT_EQ(euh.header().size, baseSectionSize + 20); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x2000); + + EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 20); + EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB"); + EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC"); + EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION"); + EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION"); + + // The default symptom ID is the ascii string + word 3 + EXPECT_EQ(euh.symptomID(), "BD8D5678_03030310"); + + BCDTime time; + EXPECT_EQ(time, euh.refTime()); + } + + { + MockDataInterface dataIface; + + // These 4 items are too long and will get truncated + // in the section. + EXPECT_CALL(dataIface, getMachineTypeModel()) + .WillOnce(Return("AAAA-BBBBBBBBBBB")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()) + .WillOnce(Return("123456789ABC123456789")); + + EXPECT_CALL(dataIface, getServerFWVersion()) + .WillOnce(Return("SERVER_VERSION_WAY_TOO_LONG")); + + EXPECT_CALL(dataIface, getBMCFWVersion()) + .WillOnce(Return("BMC_VERSION_WAY_TOO_LONG")); + + // Use SRC words 3 through 9 + entry.src.symptomID = {3, 4, 5, 6, 7, 8, 9}; + ExtendedUserHeader euh{dataIface, entry, src}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + EXPECT_EQ(euh.header().size, baseSectionSize + 72); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x2000); + + EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 72); + EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB"); + EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC"); + EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION_"); + EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION_WAY"); + + EXPECT_EQ(euh.symptomID(), "BD8D5678_03030310_04040404_05050505_" + "06060606_07070707_08080808_09090909"); + BCDTime time; + EXPECT_EQ(time, euh.refTime()); + } + + { + MockDataInterface dataIface; + + // Empty fields + EXPECT_CALL(dataIface, getMachineTypeModel()).WillOnce(Return("")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()).WillOnce(Return("")); + + EXPECT_CALL(dataIface, getServerFWVersion()).WillOnce(Return("")); + + EXPECT_CALL(dataIface, getBMCFWVersion()).WillOnce(Return("")); + + entry.src.symptomID = {8, 9}; + ExtendedUserHeader euh{dataIface, entry, src}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + EXPECT_EQ(euh.header().size, baseSectionSize + 28); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x2000); + + EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 28); + EXPECT_EQ(euh.machineTypeModel(), ""); + EXPECT_EQ(euh.machineSerialNumber(), ""); + EXPECT_EQ(euh.serverFWVersion(), ""); + EXPECT_EQ(euh.subsystemFWVersion(), ""); + + EXPECT_EQ(euh.symptomID(), "BD8D5678_08080808_09090909"); + + BCDTime time; + EXPECT_EQ(time, euh.refTime()); + } + + { + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getMachineTypeModel()) + .WillOnce(Return("AAAA-BBB")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()) + .WillOnce(Return("123456789ABC")); + + EXPECT_CALL(dataIface, getServerFWVersion()) + .WillOnce(Return("SERVER_VERSION")); + + EXPECT_CALL(dataIface, getBMCFWVersion()) + .WillOnce(Return("BMC_VERSION")); + + // Way too long, will be truncated + entry.src.symptomID = {9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9}; + + ExtendedUserHeader euh{dataIface, entry, src}; + + EXPECT_EQ(euh.valid(), true); + EXPECT_EQ(euh.header().id, 0x4548); // EH + EXPECT_EQ(euh.header().size, baseSectionSize + 80); + EXPECT_EQ(euh.header().version, 0x01); + EXPECT_EQ(euh.header().subType, 0x00); + EXPECT_EQ(euh.header().componentID, 0x2000); + + EXPECT_EQ(euh.flattenedSize(), baseSectionSize + 80); + EXPECT_EQ(euh.machineTypeModel(), "AAAA-BBB"); + EXPECT_EQ(euh.machineSerialNumber(), "123456789ABC"); + EXPECT_EQ(euh.serverFWVersion(), "SERVER_VERSION"); + EXPECT_EQ(euh.subsystemFWVersion(), "BMC_VERSION"); + + EXPECT_EQ(euh.symptomID(), + "BD8D5678_09090909_09090909_09090909_09090909_09090909_" + "09090909_09090909_0909090"); + + BCDTime time; + EXPECT_EQ(time, euh.refTime()); + } +} + +TEST(ExtUserHeaderTest, BadDataTest) +{ + auto data = sectionData; + data.resize(20); + + Stream stream{data}; + ExtendedUserHeader euh{stream}; + + EXPECT_EQ(euh.valid(), false); +} diff --git a/test/openpower-pels/failing_mtms_test.cpp b/test/openpower-pels/failing_mtms_test.cpp new file mode 100644 index 0000000..e377482 --- /dev/null +++ b/test/openpower-pels/failing_mtms_test.cpp @@ -0,0 +1,133 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/failing_mtms.hpp" +#include "mocks.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using ::testing::Return; + +TEST(FailingMTMSTest, SizeTest) +{ + EXPECT_EQ(FailingMTMS::flattenedSize(), 28); +} + +TEST(FailingMTMSTest, ConstructorTest) +{ + // Note: the TypeModel field is 8B, and the SN field is 12B + { + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getMachineTypeModel()) + .WillOnce(Return("AAAA-BBB")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()) + .WillOnce(Return("123456789ABC")); + + FailingMTMS fm{dataIface}; + + // Check the section header + EXPECT_EQ(fm.header().id, 0x4D54); + EXPECT_EQ(fm.header().size, FailingMTMS::flattenedSize()); + EXPECT_EQ(fm.header().version, 0x01); + EXPECT_EQ(fm.header().subType, 0x00); + EXPECT_EQ(fm.header().componentID, 0x2000); + + EXPECT_EQ(fm.getMachineTypeModel(), "AAAA-BBB"); + EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC"); + } + + // longer than the max - will truncate + { + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getMachineTypeModel()) + .WillOnce(Return("AAAA-BBBTOOLONG")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()) + .WillOnce(Return("123456789ABCTOOLONG")); + + FailingMTMS fm{dataIface}; + + EXPECT_EQ(fm.getMachineTypeModel(), "AAAA-BBB"); + EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC"); + } + + // shorter than the max + { + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getMachineTypeModel()).WillOnce(Return("A")); + + EXPECT_CALL(dataIface, getMachineSerialNumber()).WillOnce(Return("1")); + + FailingMTMS fm{dataIface}; + + EXPECT_EQ(fm.getMachineTypeModel(), "A"); + EXPECT_EQ(fm.getMachineSerialNumber(), "1"); + } +} + +TEST(FailingMTMSTest, StreamConstructorTest) +{ + std::vector<uint8_t> data{0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, + 0x00, 'T', 'T', 'T', 'T', '-', 'M', + 'M', 'M', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C'}; + Stream stream{data}; + FailingMTMS fm{stream}; + + EXPECT_EQ(fm.valid(), true); + + EXPECT_EQ(fm.header().id, 0x4D54); + EXPECT_EQ(fm.header().size, FailingMTMS::flattenedSize()); + EXPECT_EQ(fm.header().version, 0x01); + EXPECT_EQ(fm.header().subType, 0x00); + EXPECT_EQ(fm.header().componentID, 0x2000); + + EXPECT_EQ(fm.getMachineTypeModel(), "TTTT-MMM"); + EXPECT_EQ(fm.getMachineSerialNumber(), "123456789ABC"); +} + +TEST(FailingMTMSTest, BadStreamConstructorTest) +{ + // too short + std::vector<uint8_t> data{ + 0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, 0x00, 'T', 'T', + }; + Stream stream{data}; + FailingMTMS fm{stream}; + + EXPECT_EQ(fm.valid(), false); +} + +TEST(FailingMTMSTest, FlattenTest) +{ + std::vector<uint8_t> data{0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, + 0x00, 'T', 'T', 'T', 'T', '-', 'M', + 'M', 'M', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C'}; + Stream stream{data}; + FailingMTMS fm{stream}; + + // flatten and check results + std::vector<uint8_t> newData; + Stream newStream{newData}; + + fm.flatten(newStream); + EXPECT_EQ(data, newData); +} diff --git a/test/openpower-pels/fru_identity_test.cpp b/test/openpower-pels/fru_identity_test.cpp new file mode 100644 index 0000000..a6839b8 --- /dev/null +++ b/test/openpower-pels/fru_identity_test.cpp @@ -0,0 +1,89 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/fru_identity.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace openpower::pels::src; + +// Unflatten a FRUIdentity that is a HW FRU callout +TEST(FRUIdentityTest, TestHardwareFRU) +{ + // Has PN, SN, CCIN + std::vector<uint8_t> data{'I', 'D', 0x1C, 0x1D, // type, size, flags + '1', '2', '3', '4', // PN + '5', '6', '7', 0x00, 'A', 'A', 'A', 'A', // CCIN + '1', '2', '3', '4', '5', '6', '7', '8', // SN + '9', 'A', 'B', 'C'}; + + Stream stream{data}; + + FRUIdentity fru{stream}; + + EXPECT_EQ(fru.failingComponentType(), FRUIdentity::hardwareFRU); + EXPECT_EQ(fru.flattenedSize(), data.size()); + + EXPECT_EQ(fru.getPN().value(), "1234567"); + EXPECT_EQ(fru.getCCIN().value(), "AAAA"); + EXPECT_EQ(fru.getSN().value(), "123456789ABC"); + EXPECT_FALSE(fru.getMaintProc()); + + // Flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + fru.flatten(newStream); + EXPECT_EQ(data, newData); +} + +// Unflatten a FRUIdentity that is a Maintenance Procedure callout +TEST(FRUIdentityTest, TestMaintProcedure) +{ + // Only contains the maintenance procedure + std::vector<uint8_t> data{ + 0x49, 0x44, 0x0C, 0x42, // type, size, flags + '1', '2', '3', '4', '5', '6', '7', 0x00 // Procedure + }; + + Stream stream{data}; + + FRUIdentity fru{stream}; + + EXPECT_EQ(fru.failingComponentType(), FRUIdentity::maintenanceProc); + EXPECT_EQ(fru.flattenedSize(), data.size()); + + EXPECT_EQ(fru.getMaintProc().value(), "1234567"); + EXPECT_FALSE(fru.getPN()); + EXPECT_FALSE(fru.getCCIN()); + EXPECT_FALSE(fru.getSN()); + + // Flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + fru.flatten(newStream); + EXPECT_EQ(data, newData); +} + +// Try to unflatten garbage data +TEST(FRUIdentityTest, BadDataTest) +{ + std::vector<uint8_t> data{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + + Stream stream{data}; + + EXPECT_THROW(FRUIdentity fru{stream}, std::out_of_range); +} diff --git a/test/openpower-pels/generic_section_test.cpp b/test/openpower-pels/generic_section_test.cpp new file mode 100644 index 0000000..7c80894 --- /dev/null +++ b/test/openpower-pels/generic_section_test.cpp @@ -0,0 +1,64 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/generic.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(GenericSectionTest, UnflattenFlattenTest) +{ + // Use the private header data + auto data = pelDataFactory(TestPELType::privateHeaderSection); + + Stream stream(data); + Generic section(stream); + + EXPECT_EQ(section.header().id, 0x5048); + EXPECT_EQ(section.header().size, data.size()); + EXPECT_EQ(section.header().version, 0x01); + EXPECT_EQ(section.header().subType, 0x02); + EXPECT_EQ(section.header().componentID, 0x0304); + + const auto& sectionData = section.data(); + + // The data itself starts after the header + EXPECT_EQ(sectionData.size(), data.size() - 8); + + for (size_t i = 0; i < sectionData.size(); i++) + { + EXPECT_EQ(sectionData[i], (data)[i + 8]); + } + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream(newData); + section.flatten(newStream); + + EXPECT_EQ(data, newData); +} + +TEST(GenericSectionTest, BadDataTest) +{ + // Use the private header data to start with + auto data = pelDataFactory(TestPELType::privateHeaderSection); + data.resize(4); + + Stream stream(data); + Generic section(stream); + ASSERT_FALSE(section.valid()); +} diff --git a/test/openpower-pels/host_notifier_test.cpp b/test/openpower-pels/host_notifier_test.cpp new file mode 100644 index 0000000..c51d560 --- /dev/null +++ b/test/openpower-pels/host_notifier_test.cpp @@ -0,0 +1,680 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/data_interface.hpp" +#include "extensions/openpower-pels/host_notifier.hpp" +#include "mocks.hpp" +#include "pel_utils.hpp" + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include <chrono> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using ::testing::_; +using ::testing::Invoke; +using ::testing::NiceMock; +using ::testing::Return; +namespace fs = std::filesystem; +using namespace std::chrono; + +const size_t actionFlags0Offset = 66; +const size_t actionFlags1Offset = 67; + +class HostNotifierTest : public CleanPELFiles +{ + public: + HostNotifierTest() : repo(repoPath) + { + auto r = sd_event_default(&event); + EXPECT_TRUE(r >= 0); + + hostIface = + std::make_unique<NiceMock<MockHostInterface>>(event, dataIface); + + mockHostIface = reinterpret_cast<MockHostInterface*>(hostIface.get()); + + auto send = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(0); + }; + + // Unless otherwise specified, sendNewLogCmd should always pass. + ON_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillByDefault(Invoke(send)); + } + + ~HostNotifierTest() + { + sd_event_unref(event); + } + + protected: + sd_event* event; + Repository repo; + NiceMock<MockDataInterface> dataIface; + std::unique_ptr<HostInterface> hostIface; + MockHostInterface* mockHostIface; +}; + +/** + * @brief Create PEL with the specified action flags + * + * @param[in] actionFlagsMask - Optional action flags to use + * + * @return std::unique_ptr<PEL> + */ +std::unique_ptr<PEL> makePEL(uint16_t actionFlagsMask = 0) +{ + static uint32_t obmcID = 1; + auto data = pelDataFactory(TestPELType::pelSimple); + + data[actionFlags0Offset] |= actionFlagsMask >> 8; + data[actionFlags1Offset] |= actionFlagsMask & 0xFF; + + auto pel = std::make_unique<PEL>(data, obmcID++); + pel->assignID(); + pel->setCommitTime(); + return pel; +} + +/** + * @brief Run an iteration of the event loop. + * + * An event loop is used for: + * 1) timer expiration callbacks + * 2) Dispatches + * 3) host interface receive callbacks + * + * @param[in] event - The event object + * @param[in] numEvents - number of times to call Event::run() + * @param[in] timeout - timeout value for run() + */ +void runEvents(sdeventplus::Event& event, size_t numEvents, + milliseconds timeout = milliseconds(1)) +{ + for (size_t i = 0; i < numEvents; i++) + { + event.run(timeout); + } +} + +// Test that host state change callbacks work +TEST_F(HostNotifierTest, TestHostStateChange) +{ + bool hostState = false; + bool called = false; + DataInterfaceBase::HostStateChangeFunc func = [&hostState, + &called](bool state) { + hostState = state; + called = true; + }; + + dataIface.subscribeToHostStateChange("test", func); + + // callback called + dataIface.changeHostState(true); + EXPECT_TRUE(called); + EXPECT_TRUE(hostState); + + // No change, not called + called = false; + dataIface.changeHostState(true); + EXPECT_FALSE(called); + + // Called again + dataIface.changeHostState(false); + EXPECT_FALSE(hostState); + EXPECT_TRUE(called); + + // Shouldn't get called after an unsubscribe + dataIface.unsubscribeFromHostStateChange("test"); + + called = false; + + dataIface.changeHostState(true); + EXPECT_FALSE(called); +} + +// Test dealing with how acked PELs are put on the +// notification queue. +TEST_F(HostNotifierTest, TestPolicyAckedPEL) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + auto pel = makePEL(); + repo.add(pel); + + // This is required + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + EXPECT_TRUE(notifier.notifyRequired(pel->id())); + + // Not in the repo + EXPECT_FALSE(notifier.enqueueRequired(42)); + EXPECT_FALSE(notifier.notifyRequired(42)); + + // Now set this PEL to host acked + repo.setPELHostTransState(pel->id(), TransmissionState::acked); + + // Since it's acked, doesn't need to be enqueued or transmitted + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); + EXPECT_FALSE(notifier.notifyRequired(pel->id())); +} + +// Test the 'don't report' PEL flag +TEST_F(HostNotifierTest, TestPolicyDontReport) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // dontReportToHostFlagBit + auto pel = makePEL(0x1000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(dontReportToHostFlagBit)); + + repo.add(pel); + + // Don't need to send this to the host + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); +} + +// Test that hidden PELs need notification when there +// is no HMC. +TEST_F(HostNotifierTest, TestPolicyHiddenNoHMC) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(hiddenFlagBit)); + + repo.add(pel); + + // Still need to enqueue this + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + + // Still need to send it + EXPECT_TRUE(notifier.notifyRequired(pel->id())); +} + +// Don't need to enqueue a hidden log already acked by the HMC +TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCAcked) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + // Double check the action flag is still set + std::bitset<16> actionFlags = pel->userHeader().actionFlags(); + EXPECT_TRUE(actionFlags.test(hiddenFlagBit)); + + repo.add(pel); + + // No HMC yet, so required + EXPECT_TRUE(notifier.enqueueRequired(pel->id())); + + repo.setPELHMCTransState(pel->id(), TransmissionState::acked); + + // Not required anymore + EXPECT_FALSE(notifier.enqueueRequired(pel->id())); +} + +// Test that changing the HMC manage status affects +// the policy with hidden log notification. +TEST_F(HostNotifierTest, TestPolicyHiddenWithHMCManaged) +{ + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // hiddenFlagBit + auto pel = makePEL(0x4000); + + repo.add(pel); + + // The first time, the HMC managed is false + EXPECT_TRUE(notifier.notifyRequired(pel->id())); + + dataIface.setHMCManaged(true); + + // This time, HMC managed is true so no need to notify + EXPECT_FALSE(notifier.notifyRequired(pel->id())); +} + +// Test that PELs are enqueued on startup +TEST_F(HostNotifierTest, TestStartup) +{ + // Give the repo 10 PELs to start with + for (int i = 0; i < 10; i++) + { + auto pel = makePEL(); + repo.add(pel); + } + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + ASSERT_EQ(notifier.queueSize(), 10); + + // Now add 10 more after the notifier is watching + for (int i = 0; i < 10; i++) + { + auto pel = makePEL(); + repo.add(pel); + } + + ASSERT_EQ(notifier.queueSize(), 20); +} + +// Test the simple path were PELs get sent to the host +TEST_F(HostNotifierTest, TestSendCmd) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Add a PEL with the host off + auto pel = makePEL(); + repo.add(pel); + + EXPECT_EQ(notifier.queueSize(), 1); + + dataIface.changeHostState(true); + + runEvents(sdEvent, 1); + + // It was sent up + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Verify the state was written to the PEL. + Repository::LogID id{Repository::LogID::Pel{pel->id()}}; + auto data = repo.getPELData(id); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::sent); + + // Add a few more PELs. They will get sent. + pel = makePEL(); + repo.add(pel); + + // Dispatch it by hitting the event loop (no commands sent yet) + // Don't need to test this step discretely in the future + runEvents(sdEvent, 1); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Send the command + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + pel = makePEL(); + repo.add(pel); + + // dispatch and process the command + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that if the class is created with the host up, +// it will send PELs +TEST_F(HostNotifierTest, TestStartAfterHostUp) +{ + // Add PELs right away + auto pel = makePEL(); + repo.add(pel); + pel = makePEL(); + repo.add(pel); + + sdeventplus::Event sdEvent{event}; + + // Create the HostNotifier class with the host already up + dataIface.changeHostState(true); + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // It should start sending PELs right away + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that a single failure will cause a retry +TEST_F(HostNotifierTest, TestHostRetry) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + auto sendFailure = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(1); + }; + auto sendSuccess = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(0); + }; + + EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillOnce(Invoke(sendFailure)) + .WillOnce(Invoke(sendSuccess)) + .WillOnce(Invoke(sendSuccess)); + + dataIface.changeHostState(true); + + auto pel = makePEL(); + repo.add(pel); + + // Dispatch and handle the command + runEvents(sdEvent, 2); + + // The command failed, so the queue isn't empty + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 1); + + // Run the events again to let the timer expire and the + // command to be retried, which will be successful. + runEvents(sdEvent, 2, mockHostIface->getReceiveRetryDelay()); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + // This one should pass with no problems + pel = makePEL(); + repo.add(pel); + + // Dispatch and handle the command + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that all commands fail and notifier will give up +TEST_F(HostNotifierTest, TestHardFailure) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Every call will fail + auto sendFailure = [this](uint32_t id, uint32_t size) { + return this->mockHostIface->send(1); + }; + + EXPECT_CALL(*mockHostIface, sendNewLogCmd(_, _)) + .WillRepeatedly(Invoke(sendFailure)); + + dataIface.changeHostState(true); + + auto pel = makePEL(); + repo.add(pel); + + // Clock more retries than necessary + runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay()); + + // Should have stopped after the 15 Tries + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 15); + EXPECT_EQ(notifier.queueSize(), 1); + + // Now add another PEL, and it should start trying again + // though it will also eventually give up + pel = makePEL(); + repo.add(pel); + + runEvents(sdEvent, 40, mockHostIface->getReceiveRetryDelay()); + + // Tried an additional 15 times + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 30); + EXPECT_EQ(notifier.queueSize(), 2); +} + +// Cancel an in progress command +TEST_F(HostNotifierTest, TestCancelCmd) +{ + sdeventplus::Event sdEvent{event}; + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add and send one PEL, but don't enter the event loop + // so the receive function can't run. + auto pel = makePEL(); + repo.add(pel); + + // Not dispatched yet + EXPECT_EQ(notifier.queueSize(), 1); + + // Dispatch it + runEvents(sdEvent, 1); + + // It was sent and off the queue + EXPECT_EQ(notifier.queueSize(), 0); + + // This will cancel the receive + dataIface.changeHostState(false); + + // Back on the queue + EXPECT_EQ(notifier.queueSize(), 1); + + // Turn the host back on and make sure + // commands will work again + dataIface.changeHostState(true); + + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test that acking a PEL persist across power cycles +TEST_F(HostNotifierTest, TestPowerCycleAndAcks) +{ + sdeventplus::Event sdEvent{event}; + + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + // Add 2 PELs with host off + auto pel = makePEL(); + repo.add(pel); + auto id1 = pel->id(); + + pel = makePEL(); + repo.add(pel); + auto id2 = pel->id(); + + dataIface.changeHostState(true); + + runEvents(sdEvent, 2); + + // The were both sent. + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 0); + + dataIface.changeHostState(false); + + // Those PELs weren't acked, so they will get sent again + EXPECT_EQ(notifier.queueSize(), 2); + + // Power back on and send them again + dataIface.changeHostState(true); + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 4); + EXPECT_EQ(notifier.queueSize(), 0); + + // Ack them and verify the state in the PEL. + notifier.ackPEL(id1); + notifier.ackPEL(id2); + + Repository::LogID id{Repository::LogID::Pel{id1}}; + auto data = repo.getPELData(id); + PEL pelFromRepo1{*data}; + EXPECT_EQ(pelFromRepo1.hostTransmissionState(), TransmissionState::acked); + + id.pelID.id = id2; + data = repo.getPELData(id); + PEL pelFromRepo2{*data}; + EXPECT_EQ(pelFromRepo2.hostTransmissionState(), TransmissionState::acked); + + // Power back off, and they should't get re-added + dataIface.changeHostState(false); + + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test the host full condition +TEST_F(HostNotifierTest, TestHostFull) +{ + // The full interaction with the host is: + // BMC: new PEL available + // Host: ReadPELFile (not modeled here) + // Host: Ack(id) (if not full), or HostFull(id) + // BMC: if full and any new PELs come in, don't sent them + // Start a timer and try again + // Host responds with either Ack or full + // and repeat + + sdeventplus::Event sdEvent{event}; + HostNotifier notifier{repo, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add and dispatch/send one PEL + auto pel = makePEL(); + auto id = pel->id(); + repo.add(pel); + runEvents(sdEvent, 2); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // Host is full + notifier.setHostFull(id); + + // It goes back on the queue + EXPECT_EQ(notifier.queueSize(), 1); + + // The transmission state goes back to new + Repository::LogID i{Repository::LogID::Pel{id}}; + auto data = repo.getPELData(i); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), TransmissionState::newPEL); + + // Clock it, nothing should be sent still. + runEvents(sdEvent, 1); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 1); + + // Add another PEL and clock it, still nothing sent + pel = makePEL(); + repo.add(pel); + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 2); + + // Let the host full timer expire to trigger a retry. + // Add some extra event passes just to be sure nothing new is sent. + runEvents(sdEvent, 5, mockHostIface->getHostFullRetryDelay()); + + // The timer expiration will send just the 1, not both + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 2); + EXPECT_EQ(notifier.queueSize(), 1); + + // Host still full + notifier.setHostFull(id); + + // Let the host full timer attempt again + runEvents(sdEvent, 2, mockHostIface->getHostFullRetryDelay()); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + + // Add yet another PEL with the retry timer expired. + // It shouldn't get sent out. + pel = makePEL(); + repo.add(pel); + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 3); + + // The last 2 PELs still on the queue + EXPECT_EQ(notifier.queueSize(), 2); + + // Host no longer full, it finally acks the first PEL + notifier.ackPEL(id); + + // Now the remaining 2 PELs will be dispatched + runEvents(sdEvent, 3); + + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 5); + EXPECT_EQ(notifier.queueSize(), 0); +} + +// Test when the host says it was send a malformed PEL +TEST_F(HostNotifierTest, TestBadPEL) +{ + sdeventplus::Event sdEvent{event}; + + { + Repository repo1{repoPath}; + HostNotifier notifier{repo1, dataIface, std::move(hostIface)}; + + dataIface.changeHostState(true); + + // Add a PEL and dispatch and send it + auto pel = makePEL(); + auto id = pel->id(); + repo1.add(pel); + + runEvents(sdEvent, 2); + EXPECT_EQ(mockHostIface->numCmdsProcessed(), 1); + EXPECT_EQ(notifier.queueSize(), 0); + + // The host rejected it. + notifier.setBadPEL(id); + + // Doesn't go back on the queue + EXPECT_EQ(notifier.queueSize(), 0); + + // Check the state was saved in the PEL itself + Repository::LogID i{Repository::LogID::Pel{id}}; + auto data = repo1.getPELData(i); + PEL pelFromRepo{*data}; + EXPECT_EQ(pelFromRepo.hostTransmissionState(), + TransmissionState::badPEL); + + dataIface.changeHostState(false); + + // Ensure it doesn't go back on the queue on a power cycle + EXPECT_EQ(notifier.queueSize(), 0); + } + + // Now restore the repo, and make sure it doesn't come back + { + Repository repo1{repoPath}; + + std::unique_ptr<HostInterface> hostIface1 = + std::make_unique<MockHostInterface>(event, dataIface); + + HostNotifier notifier{repo1, dataIface, std::move(hostIface1)}; + + EXPECT_EQ(notifier.queueSize(), 0); + } +} diff --git a/test/openpower-pels/json_utils_test.cpp b/test/openpower-pels/json_utils_test.cpp new file mode 100644 index 0000000..60ca00d --- /dev/null +++ b/test/openpower-pels/json_utils_test.cpp @@ -0,0 +1,50 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/json_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(JsonUtilsTest, TrimEndTest) +{ + std::string testStr("Test string 1"); + EXPECT_EQ(trimEnd(testStr), "Test string 1"); + testStr = "Test string 2 "; + EXPECT_EQ(trimEnd(testStr), "Test string 2"); + testStr = " Test string 3 "; + EXPECT_EQ(trimEnd(testStr), " Test string 3"); +} + +TEST(JsonUtilsTest, NumberToStringTest) +{ + size_t number = 123; + EXPECT_EQ(getNumberString("%d", number), "123"); + EXPECT_EQ(getNumberString("%03X", number), "07B"); + EXPECT_EQ(getNumberString("0x%X", number), "0x7B"); + ASSERT_EXIT((getNumberString("%123", number), exit(0)), + ::testing::KilledBySignal(SIGSEGV), ".*"); +} + +TEST(JsonUtilsTest, JsonInsertTest) +{ + std::string json; + jsonInsert(json, "Key", "Value1", 1); + EXPECT_EQ(json, " \"Key\": \"Value1\",\n"); + jsonInsert(json, "Keyxxxxxxxxxxxxxxxxxxxxxxxxxx", "Value2", 2); + EXPECT_EQ(json, " \"Key\": \"Value1\",\n" + " \"Keyxxxxxxxxxxxxxxxxxxxxxxxxxx\": \"Value2\",\n"); +} diff --git a/test/openpower-pels/log_id_test.cpp b/test/openpower-pels/log_id_test.cpp new file mode 100644 index 0000000..93dff83 --- /dev/null +++ b/test/openpower-pels/log_id_test.cpp @@ -0,0 +1,57 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/log_id.hpp" +#include "extensions/openpower-pels/paths.hpp" + +#include <arpa/inet.h> + +#include <filesystem> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +namespace fs = std::filesystem; + +TEST(LogIdTest, TimeBasedIDTest) +{ + uint32_t lastID = 0; + for (int i = 0; i < 10; i++) + { + auto id = detail::getTimeBasedLogID(); + + EXPECT_EQ(id & 0xFF000000, 0x50000000); + EXPECT_NE(id, lastID); + lastID = id; + } +} + +TEST(LogIdTest, IDTest) +{ + EXPECT_EQ(generatePELID(), 0x50000001); + EXPECT_EQ(generatePELID(), 0x50000002); + EXPECT_EQ(generatePELID(), 0x50000003); + EXPECT_EQ(generatePELID(), 0x50000004); + EXPECT_EQ(generatePELID(), 0x50000005); + EXPECT_EQ(generatePELID(), 0x50000006); + + auto backingFile = getPELIDFile(); + fs::remove(backingFile); + EXPECT_EQ(generatePELID(), 0x50000001); + EXPECT_EQ(generatePELID(), 0x50000002); + EXPECT_EQ(generatePELID(), 0x50000003); + + fs::remove_all(fs::path{backingFile}.parent_path()); +} diff --git a/test/openpower-pels/mocks.hpp b/test/openpower-pels/mocks.hpp new file mode 100644 index 0000000..8b055dd --- /dev/null +++ b/test/openpower-pels/mocks.hpp @@ -0,0 +1,239 @@ +#include "extensions/openpower-pels/data_interface.hpp" +#include "extensions/openpower-pels/host_interface.hpp" + +#include <fcntl.h> + +#include <filesystem> +#include <sdeventplus/source/io.hpp> + +#include <gmock/gmock.h> + +namespace openpower +{ +namespace pels +{ + +class MockDataInterface : public DataInterfaceBase +{ + public: + MockDataInterface() + { + } + MOCK_METHOD(std::string, getMachineTypeModel, (), (const override)); + MOCK_METHOD(std::string, getMachineSerialNumber, (), (const override)); + MOCK_METHOD(std::string, getServerFWVersion, (), (const override)); + MOCK_METHOD(std::string, getBMCFWVersion, (), (const override)); + MOCK_METHOD(std::string, getBMCFWVersionID, (), (const override)); + + void changeHostState(bool newState) + { + setHostState(newState); + } + + void setHMCManaged(bool managed) + { + _hmcManaged = managed; + } +}; + +/** + * @brief The mock HostInterface class + * + * This replaces the PLDM calls with a FIFO for the asynchronous + * responses. + */ +class MockHostInterface : public HostInterface +{ + public: + /** + * @brief Constructor + * + * @param[in] event - The sd_event object + * @param[in] dataIface - The DataInterface class + */ + MockHostInterface(sd_event* event, DataInterfaceBase& dataIface) : + HostInterface(event, dataIface) + { + char templ[] = "/tmp/cmdfifoXXXXXX"; + std::filesystem::path dir = mkdtemp(templ); + _fifo = dir / "fifo"; + } + + /** + * @brief Destructor + */ + virtual ~MockHostInterface() + { + std::filesystem::remove_all(_fifo.parent_path()); + } + + MOCK_METHOD(CmdStatus, sendNewLogCmd, (uint32_t, uint32_t), (override)); + + /** + * @brief Cancels waiting for a command response + */ + virtual void cancelCmd() override + { + _inProgress = false; + _source = nullptr; + } + + /** + * @brief Returns the amount of time to wait before retrying after + * a failed send command. + * + * @return milliseconds - The amount of time to wait + */ + virtual std::chrono::milliseconds getSendRetryDelay() const override + { + return std::chrono::milliseconds(2); + } + + /** + * @brief Returns the amount of time to wait before retrying after + * a command receive. + * + * @return milliseconds - The amount of time to wait + */ + virtual std::chrono::milliseconds getReceiveRetryDelay() const override + { + return std::chrono::milliseconds(2); + } + + /** + * @brief Returns the amount of time to wait before retrying if the + * host firmware's PEL storage was full and it can't store + * any more logs until it is freed up somehow. + * + * @return milliseconds - The amount of time to wait + */ + virtual std::chrono::milliseconds getHostFullRetryDelay() const override + { + return std::chrono::milliseconds(400); + } + + /** + * @brief Returns the number of commands processed + */ + size_t numCmdsProcessed() const + { + return _cmdsProcessed; + } + + /** + * @brief Writes the data passed in to the FIFO + * + * @param[in] hostResponse - use a 0 to indicate success + * + * @return CmdStatus - success or failure + */ + CmdStatus send(uint8_t hostResponse) + { + // Create a FIFO once. + if (!std::filesystem::exists(_fifo)) + { + if (mkfifo(_fifo.c_str(), 0622)) + { + ADD_FAILURE() << "Failed mkfifo " << _fifo << strerror(errno); + exit(-1); + } + } + + // Open it and register the reponse callback to + // be used on FD activity. + int fd = open(_fifo.c_str(), O_NONBLOCK | O_RDWR); + EXPECT_TRUE(fd >= 0) << "Unable to open FIFO"; + + auto callback = [this](sdeventplus::source::IO& source, int fd, + uint32_t events) { + this->receive(source, fd, events); + }; + + try + { + _source = std::make_unique<sdeventplus::source::IO>( + _event, fd, EPOLLIN, + std::bind(callback, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)); + } + catch (std::exception& e) + { + ADD_FAILURE() << "Event exception: " << e.what(); + close(fd); + return CmdStatus::failure; + } + + // Write the fake host reponse to the FIFO + auto bytesWritten = write(fd, &hostResponse, sizeof(hostResponse)); + EXPECT_EQ(bytesWritten, sizeof(hostResponse)); + + _inProgress = true; + + return CmdStatus::success; + } + + protected: + /** + * @brief Reads the data written to the fifo and then calls + * the subscriber's callback. + * + * Nonzero data indicates a command failure (for testing bad path). + * + * @param[in] source - The event source object + * @param[in] fd - The file descriptor used + * @param[in] events - The event bits + */ + void receive(sdeventplus::source::IO& source, int fd, + uint32_t events) override + { + if (!(events & EPOLLIN)) + { + return; + } + + _inProgress = false; + + int newFD = open(_fifo.c_str(), O_NONBLOCK | O_RDONLY); + ASSERT_TRUE(newFD >= 0) << "Failed to open FIFO"; + + // Read the host success/failure response from the FIFO. + uint8_t data; + auto bytesRead = read(newFD, &data, sizeof(data)); + EXPECT_EQ(bytesRead, sizeof(data)); + + close(newFD); + + ResponseStatus status = ResponseStatus::success; + if (data != 0) + { + status = ResponseStatus::failure; + } + + if (_responseFunc) + { + (*_responseFunc)(status); + } + + // Keep account of the number of commands responses for testing. + _cmdsProcessed++; + } + + private: + /** + * @brief The event source for the fifo + */ + std::unique_ptr<sdeventplus::source::IO> _source; + + /** + * @brief the path to the fifo + */ + std::filesystem::path _fifo; + + /** + * @brief The number of commands processed + */ + size_t _cmdsProcessed = 0; +}; + +} // namespace pels +} // namespace openpower diff --git a/test/openpower-pels/mru_test.cpp b/test/openpower-pels/mru_test.cpp new file mode 100644 index 0000000..e73c69a --- /dev/null +++ b/test/openpower-pels/mru_test.cpp @@ -0,0 +1,74 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/mru.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace openpower::pels::src; + +TEST(MRUTest, TestConstructor) +{ + std::vector<uint8_t> data{ + 'M', 'R', 0x28, 0x04, // ID, size, flags + 0x00, 0x00, 0x00, 0x00, // Reserved + 0x00, 0x00, 0x00, 'H', // priority for MRU ID 0 + 0x01, 0x01, 0x01, 0x01, // MRU ID 0 + 0x00, 0x00, 0x00, 'M', // priority for MRU ID 1 + 0x02, 0x02, 0x02, 0x02, // MRU ID 1 + 0x00, 0x00, 0x00, 'L', // priority for MRU ID 2 + 0x03, 0x03, 0x03, 0x03, // MRU ID 2 + 0x00, 0x00, 0x00, 'H', // priority for MRU ID 3 + 0x04, 0x04, 0x04, 0x04, // MRU ID 3 + }; + + Stream stream{data}; + + MRU mru{stream}; + + EXPECT_EQ(mru.flattenedSize(), data.size()); + EXPECT_EQ(mru.mrus().size(), 4); + + EXPECT_EQ(mru.mrus().at(0).priority, 'H'); + EXPECT_EQ(mru.mrus().at(0).id, 0x01010101); + EXPECT_EQ(mru.mrus().at(1).priority, 'M'); + EXPECT_EQ(mru.mrus().at(1).id, 0x02020202); + EXPECT_EQ(mru.mrus().at(2).priority, 'L'); + EXPECT_EQ(mru.mrus().at(2).id, 0x03030303); + EXPECT_EQ(mru.mrus().at(3).priority, 'H'); + EXPECT_EQ(mru.mrus().at(3).id, 0x04040404); + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + mru.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(MRUTest, TestBadData) +{ + // 4 MRUs expected, but only 1 + std::vector<uint8_t> data{ + 'M', 'R', 0x28, 0x04, // ID, size, flags + 0x00, 0x00, 0x00, 0x00, // Reserved + 0x00, 0x00, 0x00, 'H', // priority 0 + 0x01, 0x01, 0x01, 0x01, // MRU ID 0 + }; + + Stream stream{data}; + EXPECT_THROW(MRU mru{stream}, std::out_of_range); +} diff --git a/test/openpower-pels/mtms_test.cpp b/test/openpower-pels/mtms_test.cpp new file mode 100644 index 0000000..b9c0929 --- /dev/null +++ b/test/openpower-pels/mtms_test.cpp @@ -0,0 +1,122 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/mtms.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(MTMSTest, SizeTest) +{ + EXPECT_EQ(MTMS::flattenedSize(), 20); +} + +TEST(MTMSTest, ConstructorTest) +{ + { + std::string tm{"TTTT-MMM"}; + std::string sn{"123456789ABC"}; + + MTMS mtms{tm, sn}; + + std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'}; + EXPECT_EQ(t, mtms.machineTypeAndModelRaw()); + EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel()); + + std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6', + '7', '8', '9', 'A', 'B', 'C'}; + EXPECT_EQ(s, mtms.machineSerialNumberRaw()); + EXPECT_EQ("123456789ABC", mtms.machineSerialNumber()); + } + + { + // too long- truncate it + std::string tm{"TTTT-MMME"}; + std::string sn{"123456789ABCE"}; + + MTMS mtms{tm, sn}; + + std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M'}; + EXPECT_EQ(t, mtms.machineTypeAndModelRaw()); + + std::array<uint8_t, 12> s{'1', '2', '3', '4', '5', '6', + '7', '8', '9', 'A', 'B', 'C'}; + EXPECT_EQ(s, mtms.machineSerialNumberRaw()); + } + + { + // short + std::string tm{"TTTT"}; + std::string sn{"1234"}; + + MTMS mtms{tm, sn}; + + std::array<uint8_t, 8> t{'T', 'T', 'T', 'T', 0, 0, 0, 0}; + EXPECT_EQ(t, mtms.machineTypeAndModelRaw()); + EXPECT_EQ("TTTT", mtms.machineTypeAndModel()); + + std::array<uint8_t, 12> s{'1', '2', '3', '4', 0, 0, 0, 0, 0, 0, 0, 0}; + EXPECT_EQ(s, mtms.machineSerialNumberRaw()); + EXPECT_EQ("1234", mtms.machineSerialNumber()); + } + + { + // Stream constructor + std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M', + 'M', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'A', 'B', 'C'}; + Stream stream{data}; + + MTMS mtms{stream}; + + EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel()); + + EXPECT_EQ("123456789ABC", mtms.machineSerialNumber()); + } +} + +TEST(MTMSTest, OperatorExtractTest) +{ + std::string tm{"TTTT-MMM"}; + std::string sn{"123456789ABC"}; + + MTMS mtms{tm, sn}; + + // Check that we can extract the same data + std::vector<uint8_t> data; + Stream stream{data}; + stream << mtms; + + std::vector<uint8_t> expected{'T', 'T', 'T', 'T', '-', 'M', 'M', + 'M', '1', '2', '3', '4', '5', '6', + '7', '8', '9', 'A', 'B', 'C'}; + EXPECT_EQ(expected, data); +} + +TEST(MTMSTest, OperatorInsertTest) +{ + std::vector<uint8_t> data{'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', + '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'}; + Stream stream{data}; + + // Check that when we insert data it is what's expected + MTMS mtms; + stream >> mtms; + + EXPECT_EQ("TTTT-MMM", mtms.machineTypeAndModel()); + + EXPECT_EQ("123456789ABC", mtms.machineSerialNumber()); +} diff --git a/test/openpower-pels/paths.cpp b/test/openpower-pels/paths.cpp new file mode 100644 index 0000000..254d2d0 --- /dev/null +++ b/test/openpower-pels/paths.cpp @@ -0,0 +1,67 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/paths.hpp" + +#include <filesystem> + +namespace openpower +{ +namespace pels +{ + +// Use paths that work in unit tests. + +std::filesystem::path getPELIDFile() +{ + static std::string idFile; + + if (idFile.empty()) + { + char templ[] = "/tmp/logidtestXXXXXX"; + std::filesystem::path dir = mkdtemp(templ); + idFile = dir / "logid"; + } + return idFile; +} + +std::filesystem::path getPELRepoPath() +{ + static std::string repoPath; + + if (repoPath.empty()) + { + char templ[] = "/tmp/repopathtestXXXXXX"; + std::filesystem::path dir = mkdtemp(templ); + repoPath = dir; + } + return repoPath; +} + +std::filesystem::path getMessageRegistryPath() +{ + static std::string registryPath; + + if (registryPath.empty()) + { + char templ[] = "/tmp/msgregtestXXXXXX"; + registryPath = mkdtemp(templ); + } + + return registryPath; +} + +} // namespace pels +} // namespace openpower diff --git a/test/openpower-pels/pce_identity_test.cpp b/test/openpower-pels/pce_identity_test.cpp new file mode 100644 index 0000000..de41dbb --- /dev/null +++ b/test/openpower-pels/pce_identity_test.cpp @@ -0,0 +1,57 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/pce_identity.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace openpower::pels::src; + +TEST(PCEIdentityTest, TestConstructor) +{ + std::vector<uint8_t> data{ + 'P', 'E', 0x24, 0x00, // type, size, flags + 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', // MTM + '1', '2', '3', '4', '5', '6', '7', // SN + '8', '9', 'A', 'B', 'C', 'P', 'C', 'E', // Name + null padded + 'N', 'A', 'M', 'E', '1', '2', 0x00, 0x00, 0x00}; + + Stream stream{data}; + + PCEIdentity pce{stream}; + + EXPECT_EQ(pce.flattenedSize(), data.size()); + EXPECT_EQ(pce.enclosureName(), "PCENAME12"); + EXPECT_EQ(pce.mtms().machineTypeAndModel(), "TTTT-MMM"); + EXPECT_EQ(pce.mtms().machineSerialNumber(), "123456789ABC"); + + // Flatten it + std::vector<uint8_t> newData; + Stream newStream{newData}; + pce.flatten(newStream); + + EXPECT_EQ(data, newData); +} + +TEST(PCEIdentityTest, TestBadData) +{ + std::vector<uint8_t> data{ + 'P', 'E', 0x20, 0x00, 'T', 'T', 'T', 'T', '-', + }; + + Stream stream{data}; + EXPECT_THROW(PCEIdentity pce{stream}, std::out_of_range); +} diff --git a/test/openpower-pels/pel_manager_test.cpp b/test/openpower-pels/pel_manager_test.cpp new file mode 100644 index 0000000..b020d9d --- /dev/null +++ b/test/openpower-pels/pel_manager_test.cpp @@ -0,0 +1,514 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/manager.hpp" +#include "log_manager.hpp" +#include "pel_utils.hpp" + +#include <fstream> +#include <regex> +#include <xyz/openbmc_project/Common/error.hpp> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +namespace fs = std::filesystem; + +class TestLogger +{ + public: + void log(const std::string& name, phosphor::logging::Entry::Level level, + const EventLogger::ADMap& additionalData) + { + errName = name; + errLevel = level; + ad = additionalData; + } + + std::string errName; + phosphor::logging::Entry::Level errLevel; + EventLogger::ADMap ad; +}; + +class ManagerTest : public CleanPELFiles +{ + public: + ManagerTest() : logManager(bus, "logging_path") + { + sd_event_default(&sdEvent); + bus.attach_event(sdEvent, SD_EVENT_PRIORITY_NORMAL); + } + + ~ManagerTest() + { + sd_event_unref(sdEvent); + } + + sdbusplus::bus::bus bus = sdbusplus::bus::new_default(); + phosphor::logging::internal::Manager logManager; + sd_event* sdEvent; + TestLogger logger; +}; + +fs::path makeTempDir() +{ + char path[] = "/tmp/tempnameXXXXXX"; + std::filesystem::path dir = mkdtemp(path); + return dir; +} + +std::optional<fs::path> findAnyPELInRepo() +{ + // PELs are named <timestamp>_<ID> + std::regex expr{"\\d+_\\d+"}; + + for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs")) + { + if (std::regex_search(f.path().string(), expr)) + { + return f.path(); + } + } + return std::nullopt; +} + +// Test that using the RAWPEL=<file> with the Manager::create() call gets +// a PEL saved in the repository. +TEST_F(ManagerTest, TestCreateWithPEL) +{ + std::unique_ptr<DataInterfaceBase> dataIface = + std::make_unique<DataInterface>(bus); + + openpower::pels::Manager manager{ + logManager, std::move(dataIface), + std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)}; + + // Create a PEL, write it to a file, and pass that filename into + // the create function. + auto data = pelDataFactory(TestPELType::pelSimple); + + fs::path pelFilename = makeTempDir() / "rawpel"; + std::ofstream pelFile{pelFilename}; + pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); + pelFile.close(); + + std::string adItem = "RAWPEL=" + pelFilename.string(); + std::vector<std::string> additionalData{adItem}; + std::vector<std::string> associations; + + manager.create("error message", 42, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + // Find the file in the PEL repository directory + auto pelPathInRepo = findAnyPELInRepo(); + + EXPECT_TRUE(pelPathInRepo); + + // Now remove it based on its OpenBMC event log ID + manager.erase(42); + + pelPathInRepo = findAnyPELInRepo(); + + EXPECT_FALSE(pelPathInRepo); + + fs::remove_all(pelFilename.parent_path()); +} + +TEST_F(ManagerTest, TestCreateWithInvalidPEL) +{ + std::unique_ptr<DataInterfaceBase> dataIface = + std::make_unique<DataInterface>(bus); + + openpower::pels::Manager manager{ + logManager, std::move(dataIface), + std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)}; + + // Create a PEL, write it to a file, and pass that filename into + // the create function. + auto data = pelDataFactory(TestPELType::pelSimple); + + // Truncate it to make it invalid. + data.resize(200); + + fs::path pelFilename = makeTempDir() / "rawpel"; + std::ofstream pelFile{pelFilename}; + pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); + pelFile.close(); + + std::string adItem = "RAWPEL=" + pelFilename.string(); + std::vector<std::string> additionalData{adItem}; + std::vector<std::string> associations; + + manager.create("error message", 42, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + // Run the event loop to log the bad PEL event + sdeventplus::Event e{sdEvent}; + e.run(std::chrono::milliseconds(1)); + + PEL invalidPEL{data}; + EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL"); + EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error); + EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid()); + EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42"); + EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString()); + EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size())); + + fs::remove_all(pelFilename.parent_path()); +} + +// Test that the message registry can be used to build a PEL. +TEST_F(ManagerTest, TestCreateWithMessageRegistry) +{ + const auto registry = R"( +{ + "PELs": + [ + { + "Name": "xyz.openbmc_project.Error.Test", + "Subsystem": "power_supply", + "ActionFlags": ["service_action", "report"], + "SRC": + { + "ReasonCode": "0x2030" + }, + "Documentation": + { + "Description": "A PGOOD Fault", + "Message": "PS had a PGOOD Fault" + } + } + ] +} +)"; + + auto path = getMessageRegistryPath(); + fs::create_directories(path); + path /= "message_registry.json"; + + std::ofstream registryFile{path}; + registryFile << registry; + registryFile.close(); + + std::unique_ptr<DataInterfaceBase> dataIface = + std::make_unique<DataInterface>(logManager.getBus()); + + openpower::pels::Manager manager{ + logManager, std::move(dataIface), + std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)}; + + std::vector<std::string> additionalData; + std::vector<std::string> associations; + + // Create the event log to create the PEL from. + manager.create("xyz.openbmc_project.Error.Test", 33, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + // Ensure a PEL was created in the repository + auto pelFile = findAnyPELInRepo(); + ASSERT_TRUE(pelFile); + + auto data = readPELFile(*pelFile); + PEL pel(*data); + + // Spot check it. Other testcases cover the details. + EXPECT_TRUE(pel.valid()); + EXPECT_EQ(pel.obmcLogID(), 33); + EXPECT_EQ(pel.primarySRC().value()->asciiString(), + "BD612030 "); + + // Remove it + manager.erase(33); + pelFile = findAnyPELInRepo(); + EXPECT_FALSE(pelFile); + + // Create an event log that can't be found in the registry. + manager.create("xyz.openbmc_project.Error.Foo", 33, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + // Currently, no PEL should be created. Eventually, a 'missing registry + // entry' PEL will be there. + pelFile = findAnyPELInRepo(); + EXPECT_FALSE(pelFile); +} + +TEST_F(ManagerTest, TestDBusMethods) +{ + std::unique_ptr<DataInterfaceBase> dataIface = + std::make_unique<DataInterface>(bus); + + Manager manager{logManager, std::move(dataIface), + std::bind(std::mem_fn(&TestLogger::log), &logger, + std::placeholders::_1, std::placeholders::_2, + std::placeholders::_3)}; + + // Create a PEL, write it to a file, and pass that filename into + // the create function so there's one in the repo. + auto data = pelDataFactory(TestPELType::pelSimple); + + fs::path pelFilename = makeTempDir() / "rawpel"; + std::ofstream pelFile{pelFilename}; + pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); + pelFile.close(); + + std::string adItem = "RAWPEL=" + pelFilename.string(); + std::vector<std::string> additionalData{adItem}; + std::vector<std::string> associations; + + manager.create("error message", 42, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + // getPELFromOBMCID + auto newData = manager.getPELFromOBMCID(42); + EXPECT_EQ(newData.size(), data.size()); + + // Read the PEL to get the ID for later + PEL pel{newData}; + auto id = pel.id(); + + EXPECT_THROW( + manager.getPELFromOBMCID(id + 1), + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); + + // getPEL + auto unixfd = manager.getPEL(id); + + // Get the size + struct stat s; + int r = fstat(unixfd, &s); + ASSERT_EQ(r, 0); + auto size = s.st_size; + + // Open the FD and check the contents + FILE* fp = fdopen(unixfd, "r"); + ASSERT_NE(fp, nullptr); + + std::vector<uint8_t> fdData; + fdData.resize(size); + r = fread(fdData.data(), 1, size, fp); + EXPECT_EQ(r, size); + + EXPECT_EQ(newData, fdData); + + fclose(fp); + + // Run the event loop to close the FD + sdeventplus::Event e{sdEvent}; + e.run(std::chrono::milliseconds(1)); + + EXPECT_THROW( + manager.getPEL(id + 1), + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); + + // hostAck + manager.hostAck(id); + + EXPECT_THROW( + manager.hostAck(id + 1), + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); + + // hostReject + manager.hostReject(id, Manager::RejectionReason::BadPEL); + + // Run the event loop to log the bad PEL event + e.run(std::chrono::milliseconds(1)); + + EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost"); + EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16)); + + manager.hostReject(id, Manager::RejectionReason::HostFull); + + EXPECT_THROW( + manager.hostReject(id + 1, Manager::RejectionReason::BadPEL), + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); + + fs::remove_all(pelFilename.parent_path()); +} + +// An ESEL from the wild +const std::string esel{ + "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 " + "50 48 00 30 01 00 33 00 00 00 00 07 5c 69 cc 0d 00 00 00 07 5c d5 50 db " + "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e " + "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 " + "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 " + "00 00 00 00 00 20 00 00 00 0c 00 02 00 00 00 fa 00 00 0c e4 00 00 00 12 " + "42 43 38 41 33 33 32 44 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 " + "20 20 20 20 20 20 20 20 55 44 00 1c 01 06 01 00 02 54 41 4b 00 00 00 06 " + "00 00 00 55 00 01 f9 20 00 00 00 00 55 44 00 24 01 06 01 00 01 54 41 4b " + "00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 23 01 00 02 00 05 00 00 " + "55 44 00 0c 01 0b 01 00 0f 01 00 00 55 44 00 10 01 04 01 00 0f 9f de 6a " + "00 01 00 00 55 44 00 7c 00 0c 01 00 00 13 0c 02 00 fa 0c e4 16 00 01 2c " + "0c 1c 16 00 00 fa 0a f0 14 00 00 fa 0b b8 14 00 00 be 09 60 12 00 01 2c " + "0d 7a 12 00 00 fa 0c 4e 10 00 00 fa 0c e4 10 00 00 be 0a 8c 16 00 01 2c " + "0c 1c 16 00 01 09 09 f6 16 00 00 fa 09 f6 14 00 00 fa 0b b8 14 00 00 fa " + "0a f0 14 00 00 be 08 ca 12 00 01 2c 0c e4 12 00 00 fa 0b 54 10 00 00 fa " + "0c 2d 10 00 00 be 08 ca 55 44 00 58 01 03 01 00 00 00 00 00 00 05 31 64 " + "00 00 00 00 00 05 0d d4 00 00 00 00 40 5f 06 e0 00 00 00 00 40 5d d2 00 " + "00 00 00 00 40 57 d3 d0 00 00 00 00 40 58 f6 a0 00 00 00 00 40 54 c9 34 " + "00 00 00 00 40 55 9a 10 00 00 00 00 40 4c 0a 80 00 00 00 00 00 00 27 14 " + "55 44 01 84 01 01 01 00 48 6f 73 74 62 6f 6f 74 20 42 75 69 6c 64 20 49 " + "44 3a 20 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 34 64 66 2d 70 30 61 38 " + "37 64 63 34 2f 68 62 69 63 6f 72 65 2e 62 69 6e 00 49 42 4d 2d 77 69 74 " + "68 65 72 73 70 6f 6f 6e 2d 4f 50 39 2d 76 32 2e 34 2d 39 2e 32 33 34 0a " + "09 6f 70 2d 62 75 69 6c 64 2d 38 32 66 34 63 66 30 0a 09 62 75 69 6c 64 " + "72 6f 6f 74 2d 32 30 31 39 2e 30 35 2e 32 2d 31 30 2d 67 38 39 35 39 31 " + "31 34 0a 09 73 6b 69 62 6f 6f 74 2d 76 36 2e 35 2d 31 38 2d 67 34 37 30 " + "66 66 62 35 66 32 39 64 37 0a 09 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 " + "34 64 66 2d 70 30 61 38 37 64 63 34 0a 09 6f 63 63 2d 65 34 35 39 37 61 " + "62 0a 09 6c 69 6e 75 78 2d 35 2e 32 2e 31 37 2d 6f 70 65 6e 70 6f 77 65 " + "72 31 2d 70 64 64 63 63 30 33 33 0a 09 70 65 74 69 74 62 6f 6f 74 2d 76 " + "31 2e 31 30 2e 34 0a 09 6d 61 63 68 69 6e 65 2d 78 6d 6c 2d 63 36 32 32 " + "63 62 35 2d 70 37 65 63 61 62 33 64 0a 09 68 6f 73 74 62 6f 6f 74 2d 62 " + "69 6e 61 72 69 65 73 2d 36 36 65 39 61 36 30 0a 09 63 61 70 70 2d 75 63 " + "6f 64 65 2d 70 39 2d 64 64 32 2d 76 34 0a 09 73 62 65 2d 36 30 33 33 30 " + "65 30 0a 09 68 63 6f 64 65 2d 68 77 30 39 32 31 31 39 61 2e 6f 70 6d 73 " + "74 0a 00 00 55 44 00 70 01 04 01 00 0f 9f de 6a 00 05 00 00 07 5f 1d f4 " + "30 32 43 59 34 37 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " + "0b ac 54 02 59 41 31 39 33 34 36 39 37 30 35 38 00 00 00 00 00 00 05 22 " + "a1 58 01 8a 00 58 40 20 17 18 4d 2c 00 00 00 fc 01 a1 00 00 55 44 00 14 " + "01 08 01 00 00 00 00 01 00 00 00 5a 00 00 00 05 55 44 03 fc 01 15 31 00 " + "01 28 00 42 46 41 50 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 f4 " + "00 00 00 00 00 00 03 f4 00 00 00 0b 00 00 00 00 00 00 00 3d 2c 9b c2 84 " + "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 09 " + "00 00 00 00 00 11 bd 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 01 2c " + "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c 00 00 00 64 00 00 00 3d " + "2c 9b d1 11 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 " + "00 00 00 0a 00 00 00 00 00 13 b5 a0 00 00 00 00 00 01 f8 80 00 00 00 00 " + "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 " + "00 00 00 be 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a 8c 00 00 00 64 " + "00 00 00 3d 2c 9b df 98 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 " + "00 00 00 00 00 00 00 0b 00 00 00 00 00 15 ae 20 00 00 00 00 00 01 f8 80 " + "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 " + "00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c e4 " + "00 00 00 64 00 00 00 3d 2c 9b ea b7 00 00 01 e4 00 48 43 4f fb ed 70 b1 " + "00 00 02 01 00 00 00 00 00 00 00 0c 00 00 00 00 00 17 a6 a0 00 00 00 00 " + "00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 " + "00 00 00 12 00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 " + "00 00 0c 4e 00 00 00 64 00 00 00 3d 2c 9b f6 27 00 00 01 e4 00 48 43 4f " + "fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0d 00 00 00 00 00 19 9f 20 " + "00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 " + "00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 " + "00 00 00 00 00 00 0d 7a 00 00 00 64 00 00 00 3d 2c 9c 05 75 00 00 01 e4 " + "00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0e 00 00 00 00 " + "00 1b 97 a0 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 " + "00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 be 00 00 00 00 " + "00 00 07 d0 00 00 00 00 00 00 09 60 00 00 00 64 00 00 00 3d 2c 9c 11 29 " + "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0f " + "00 00 00 00 00 1d 90 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 " + "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 fa " + "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0b b8 00 00 00 64 00 00 00 3d " + "2c 9c 1c 45 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 " + "00 00 00 10 00 00 00 00 00 1f 88 a0 00 00 00 00 00 01 f8 80 00 00 00 00 " + "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 " + "00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a f0 00 00 00 64 " + "00 00 00 3d 2c 9c 2b 14 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 " + "00 00 00 00 00 00 00 11 00 00 00 00 00 21 81 20 00 00 00 00 00 01 f8 80 " + "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 " + "00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c " + "00 00 00 64 00 00 00 3d 2d 6d 8f 9e 00 00 01 e4 00 00 43 4f 52 d7 9c 36 " + "00 00 04 73 00 00 00 1c 00 00 00 3d 2d 6d 99 ac 00 00 01 e4 00 10 43 4f " + "3f f2 02 3d 00 00 05 58 00 00 00 00 02 00 00 01 00 00 00 00 00 00 00 40 " + "00 00 00 2c 55 44 00 30 01 15 31 00 01 28 00 42 46 41 50 49 5f 44 42 47 " + "00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00 00 " + "00 00 00 00 55 44 01 74 01 15 31 00 01 28 00 42 46 41 50 49 5f 49 00 00 " + "00 00 00 00 00 00 00 00 00 00 01 6c 00 00 00 00 00 00 01 6c 00 00 00 0b " + "00 00 00 00 00 00 00 3c 0d 52 18 5e 00 00 01 e4 00 08 43 4f 46 79 94 13 " + "00 00 0a 5b 00 00 00 00 00 00 2c 00 00 00 00 24 00 00 00 3c 0d 6b 26 6c " + "00 00 01 e4 00 00 43 4f 4e 9b 18 74 00 00 01 03 00 00 00 1c 00 00 00 3c " + "12 b9 2d 13 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c " + "00 00 00 3c 13 02 73 53 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 " + "00 00 00 1c 00 00 00 3c 13 04 7c 94 00 00 01 e4 00 00 43 4f ea 31 ed d4 " + "00 00 05 c4 00 00 00 1c 00 00 00 3c 13 06 ad e1 00 00 01 e4 00 00 43 4f " + "ea 31 ed d4 00 00 05 c4 00 00 00 1c 00 00 00 3c 13 07 3f 77 00 00 01 e4 " + "00 00 43 4f 5e 4a 55 32 00 00 10 f2 00 00 00 1c 00 00 00 3c 13 07 4e e4 " + "00 00 01 e4 00 00 43 4f 5e 4a 55 32 00 00 0d 68 00 00 00 1c 00 00 00 3c " + "13 36 79 18 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c " + "00 00 00 3d 2c 9c 36 70 00 00 01 e4 00 00 43 4f 23 45 90 97 00 00 02 47 " + "00 00 00 1c 00 00 00 3d 2d 6d a3 ed 00 00 01 e4 00 08 43 4f 74 3a 5b 1a " + "00 00 04 cc 00 00 00 00 02 00 00 01 00 00 00 24 55 44 00 30 01 15 31 00 " + "01 28 00 42 53 43 41 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 " + "00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00"}; + +TEST_F(ManagerTest, TestESELToRawData) +{ + auto data = Manager::eselToRawData(esel); + + EXPECT_EQ(data.size(), 2464); + + PEL pel{data}; + EXPECT_TRUE(pel.valid()); +} + +TEST_F(ManagerTest, TestCreateWithESEL) +{ + std::unique_ptr<DataInterfaceBase> dataIface = + std::make_unique<DataInterface>(bus); + + openpower::pels::Manager manager{ + logManager, std::move(dataIface), + std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, + std::placeholders::_2, std::placeholders::_3)}; + + { + std::string adItem = "ESEL=" + esel; + std::vector<std::string> additionalData{adItem}; + std::vector<std::string> associations; + + manager.create("error message", 37, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + auto data = manager.getPELFromOBMCID(37); + PEL pel{data}; + EXPECT_TRUE(pel.valid()); + } + + // Now an invalid one + { + std::string adItem = "ESEL=" + esel; + + // Crop it + adItem.resize(adItem.size() - 300); + + std::vector<std::string> additionalData{adItem}; + std::vector<std::string> associations; + + manager.create("error message", 38, 0, + phosphor::logging::Entry::Level::Error, additionalData, + associations); + + EXPECT_THROW( + manager.getPELFromOBMCID(38), + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); + + // Run the event loop to log the bad PEL event + sdeventplus::Event e{sdEvent}; + e.run(std::chrono::milliseconds(1)); + + EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL"); + EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error); + } +} diff --git a/test/openpower-pels/pel_rules_test.cpp b/test/openpower-pels/pel_rules_test.cpp new file mode 100644 index 0000000..38e5b58 --- /dev/null +++ b/test/openpower-pels/pel_rules_test.cpp @@ -0,0 +1,72 @@ +#include "extensions/openpower-pels/pel_rules.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +struct CheckParams +{ + // pel_rules::check() inputs + uint16_t actionFlags; + uint8_t eventType; + uint8_t severity; + + // pel_rules::check() expected outputs + uint16_t expectedActionFlags; + uint8_t expectedEventType; +}; + +const uint8_t sevInfo = 0x00; +const uint8_t sevRecovered = 0x10; +const uint8_t sevPredictive = 0x20; +const uint8_t sevUnrecov = 0x40; +const uint8_t sevCrit = 0x50; +const uint8_t sevDiagnostic = 0x60; +const uint8_t sevSymptom = 0x70; + +const uint8_t typeNA = 0x00; +const uint8_t typeMisc = 0x01; +const uint8_t typeTracing = 0x02; +const uint8_t typeDumpNotif = 0x08; + +TEST(PELRulesTest, TestCheckRules) +{ + // Impossible to cover all combinations, but + // do some interesting ones. + std::vector<CheckParams> testParams{ + // Informational errors w/ empty action flags + // and different event types. + {0, typeNA, sevInfo, 0x6000, typeMisc}, + {0, typeMisc, sevInfo, 0x6000, typeMisc}, + {0, typeTracing, sevInfo, 0x6000, typeTracing}, + {0, typeDumpNotif, sevInfo, 0x2000, typeDumpNotif}, + + // Informational errors with wrong action flags + {0x8900, typeNA, sevInfo, 0x6000, typeMisc}, + + // Informational errors with extra valid action flags + {0x00C0, typeMisc, sevInfo, 0x60C0, typeMisc}, + + // Informational - don't report + {0x1000, typeMisc, sevInfo, 0x5000, typeMisc}, + + // Recovered will report as hidden + {0, typeNA, sevRecovered, 0x6000, typeNA}, + + // The 5 error severities will have: + // service action, report, call home + {0, typeNA, sevPredictive, 0xA800, typeNA}, + {0, typeNA, sevUnrecov, 0xA800, typeNA}, + {0, typeNA, sevCrit, 0xA800, typeNA}, + {0, typeNA, sevDiagnostic, 0xA800, typeNA}, + {0, typeNA, sevSymptom, 0xA800, typeNA}}; + + for (const auto& entry : testParams) + { + auto [actionFlags, type] = pel_rules::check( + entry.actionFlags, entry.eventType, entry.severity); + + EXPECT_EQ(actionFlags, entry.expectedActionFlags); + EXPECT_EQ(type, entry.expectedEventType); + } +} diff --git a/test/openpower-pels/pel_test.cpp b/test/openpower-pels/pel_test.cpp new file mode 100644 index 0000000..2cf58d7 --- /dev/null +++ b/test/openpower-pels/pel_test.cpp @@ -0,0 +1,387 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "elog_entry.hpp" +#include "extensions/openpower-pels/generic.hpp" +#include "extensions/openpower-pels/pel.hpp" +#include "mocks.hpp" +#include "pel_utils.hpp" + +#include <filesystem> +#include <fstream> + +#include <gtest/gtest.h> + +namespace fs = std::filesystem; +using namespace openpower::pels; +using ::testing::Return; + +class PELTest : public CleanLogID +{ +}; + +TEST_F(PELTest, FlattenTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + + // Check a few fields + EXPECT_TRUE(pel->valid()); + EXPECT_EQ(pel->id(), 0x80818283); + EXPECT_EQ(pel->plid(), 0x50515253); + EXPECT_EQ(pel->userHeader().subsystem(), 0x10); + EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0); + + // Test that data in == data out + auto flattenedData = pel->data(); + EXPECT_EQ(data, flattenedData); + EXPECT_EQ(flattenedData.size(), pel->size()); +} + +TEST_F(PELTest, CommitTimeTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + + auto origTime = pel->commitTime(); + pel->setCommitTime(); + auto newTime = pel->commitTime(); + + EXPECT_NE(origTime, newTime); + + // Make a new PEL and check new value is still there + auto newData = pel->data(); + auto newPel = std::make_unique<PEL>(newData); + EXPECT_EQ(newTime, newPel->commitTime()); +} + +TEST_F(PELTest, AssignIDTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + + auto origID = pel->id(); + pel->assignID(); + auto newID = pel->id(); + + EXPECT_NE(origID, newID); + + // Make a new PEL and check new value is still there + auto newData = pel->data(); + auto newPel = std::make_unique<PEL>(newData); + EXPECT_EQ(newID, newPel->id()); +} + +TEST_F(PELTest, WithLogIDTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data, 0x42); + + EXPECT_TRUE(pel->valid()); + EXPECT_EQ(pel->obmcLogID(), 0x42); +} + +TEST_F(PELTest, InvalidPELTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + + // Too small + data.resize(PrivateHeader::flattenedSize()); + + auto pel = std::make_unique<PEL>(data); + + EXPECT_TRUE(pel->privateHeader().valid()); + EXPECT_FALSE(pel->userHeader().valid()); + EXPECT_FALSE(pel->valid()); + + // Now corrupt the private header + data = pelDataFactory(TestPELType::pelSimple); + data.at(0) = 0; + pel = std::make_unique<PEL>(data); + + EXPECT_FALSE(pel->privateHeader().valid()); + EXPECT_TRUE(pel->userHeader().valid()); + EXPECT_FALSE(pel->valid()); +} + +TEST_F(PELTest, EmptyDataTest) +{ + std::vector<uint8_t> data; + auto pel = std::make_unique<PEL>(data); + + EXPECT_FALSE(pel->privateHeader().valid()); + EXPECT_FALSE(pel->userHeader().valid()); + EXPECT_FALSE(pel->valid()); +} + +TEST_F(PELTest, CreateFromRegistryTest) +{ + message::Entry regEntry; + uint64_t timestamp = 5; + + regEntry.name = "test"; + regEntry.subsystem = 5; + regEntry.actionFlags = 0xC000; + regEntry.src.type = 0xBD; + regEntry.src.reasonCode = 0x1234; + + std::vector<std::string> data{"KEY1=VALUE1"}; + AdditionalData ad{data}; + MockDataInterface dataIface; + + PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, ad, + dataIface}; + + EXPECT_TRUE(pel.valid()); + EXPECT_EQ(pel.privateHeader().obmcLogID(), 42); + EXPECT_EQ(pel.userHeader().severity(), 0x40); + + EXPECT_EQ(pel.primarySRC().value()->asciiString(), + "BD051234 "); + + // Check that certain optional sections have been created + size_t mtmsCount = 0; + size_t euhCount = 0; + size_t udCount = 0; + + for (const auto& section : pel.optionalSections()) + { + if (section->header().id == + static_cast<uint16_t>(SectionID::failingMTMS)) + { + mtmsCount++; + } + else if (section->header().id == + static_cast<uint16_t>(SectionID::extendedUserHeader)) + { + euhCount++; + } + else if (section->header().id == + static_cast<uint16_t>(SectionID::userData)) + { + udCount++; + } + } + + EXPECT_EQ(mtmsCount, 1); + EXPECT_EQ(euhCount, 1); + EXPECT_EQ(udCount, 2); // AD section and sysInfo section +} + +// Test that we'll create Generic optional sections for sections that +// there aren't explicit classes for. +TEST_F(PELTest, GenericSectionTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + + std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX' + 0x00, 0x18, // Size + 0x01, 0x02, // version, subtype + 0x03, 0x04, // comp ID + + // some data + 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, + 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, + 0x00}; + + std::vector<uint8_t> section2{ + 0x59, 0x59, // ID 'YY' + 0x00, 0x20, // Size + 0x01, 0x02, // version, subtype + 0x03, 0x04, // comp ID + + // some data + 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, + 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + + // Add the new sections at the end + data.insert(data.end(), section1.begin(), section1.end()); + data.insert(data.end(), section2.begin(), section2.end()); + + // Increment the section count + data.at(27) += 2; + auto origData = data; + + PEL pel{data}; + + const auto& sections = pel.optionalSections(); + + bool foundXX = false; + bool foundYY = false; + + // Check that we can find these 2 Generic sections + for (const auto& section : sections) + { + if (section->header().id == 0x5858) + { + foundXX = true; + EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); + } + else if (section->header().id == 0x5959) + { + foundYY = true; + EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); + } + } + + EXPECT_TRUE(foundXX); + EXPECT_TRUE(foundYY); + + // Now flatten and check + auto newData = pel.data(); + + EXPECT_EQ(origData, newData); +} + +// Test that an invalid section will still get a Generic object +TEST_F(PELTest, InvalidGenericTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + + // Not a valid section + std::vector<uint8_t> section1{0x01, 0x02, 0x03}; + + data.insert(data.end(), section1.begin(), section1.end()); + + // Increment the section count + data.at(27) += 1; + + PEL pel{data}; + EXPECT_FALSE(pel.valid()); + + const auto& sections = pel.optionalSections(); + + bool foundGeneric = false; + for (const auto& section : sections) + { + if (dynamic_cast<Generic*>(section.get()) != nullptr) + { + foundGeneric = true; + EXPECT_EQ(section->valid(), false); + break; + } + } + + EXPECT_TRUE(foundGeneric); +} + +// Create a UserData section out of AdditionalData +TEST_F(PELTest, MakeUDSectionTest) +{ + std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3", + "ESEL=TEST"}; + AdditionalData additionalData{ad}; + + auto ud = util::makeADUserDataSection(additionalData); + + EXPECT_TRUE(ud->valid()); + EXPECT_EQ(ud->header().id, 0x5544); + EXPECT_EQ(ud->header().version, 0x01); + EXPECT_EQ(ud->header().subType, 0x01); + EXPECT_EQ(ud->header().componentID, 0x2000); + + const auto& d = ud->data(); + + std::string jsonString{d.begin(), d.end()}; + + std::string expectedJSON = + R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})"; + + // The actual data is null padded to a 4B boundary. + std::vector<uint8_t> expectedData; + expectedData.resize(52, '\0'); + memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size()); + + EXPECT_EQ(d, expectedData); + + // Ensure we can read this as JSON + auto newJSON = nlohmann::json::parse(jsonString); + EXPECT_EQ(newJSON["KEY1"], "VALUE1"); + EXPECT_EQ(newJSON["KEY2"], "VALUE2"); + EXPECT_EQ(newJSON["KEY3"], "VALUE3"); +} + +// Create the UserData section that contains system info +TEST_F(PELTest, SysInfoSectionTest) +{ + MockDataInterface dataIface; + + EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234")); + + std::string pid = "_PID=" + std::to_string(getpid()); + std::vector<std::string> ad{pid}; + AdditionalData additionalData{ad}; + + auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); + + EXPECT_TRUE(ud->valid()); + EXPECT_EQ(ud->header().id, 0x5544); + EXPECT_EQ(ud->header().version, 0x01); + EXPECT_EQ(ud->header().subType, 0x01); + EXPECT_EQ(ud->header().componentID, 0x2000); + + // Pull out the JSON data and check it. + const auto& d = ud->data(); + std::string jsonString{d.begin(), d.end()}; + auto json = nlohmann::json::parse(jsonString); + + // Ensure the 'Process Name' entry contains 'pel_test' + auto name = json["Process Name"].get<std::string>(); + EXPECT_NE(name.find("pel_test"), std::string::npos); + + auto version = json["BMC Version ID"].get<std::string>(); + EXPECT_EQ(version, "ABCD1234"); +} + +// Test that the sections that override +// virtual std::optional<std::string> Section::getJSON() const +// return valid JSON. +TEST_F(PELTest, SectionJSONTest) +{ + auto data = pelDataFactory(TestPELType::pelSimple); + PEL pel{data}; + + // Check that all JSON returned from the sections is + // parseable by nlohmann::json, which will throw an + // exception and fail the test if there is a problem. + + // The getJSON() response needs to be wrapped in a { } to make + // actual valid JSON (PEL::toJSON() usually handles that). + + auto jsonString = pel.privateHeader().getJSON(); + + // PrivateHeader always prints JSON + ASSERT_TRUE(jsonString); + *jsonString = '{' + *jsonString + '}'; + auto json = nlohmann::json::parse(*jsonString); + + jsonString = pel.userHeader().getJSON(); + + // UserHeader always prints JSON + ASSERT_TRUE(jsonString); + *jsonString = '{' + *jsonString + '}'; + json = nlohmann::json::parse(*jsonString); + + for (const auto& section : pel.optionalSections()) + { + // The optional sections may or may not have implemented getJSON(). + jsonString = section->getJSON(); + if (jsonString) + { + *jsonString = '{' + *jsonString + '}'; + auto json = nlohmann::json::parse(*jsonString); + } + } +} diff --git a/test/openpower-pels/pel_utils.cpp b/test/openpower-pels/pel_utils.cpp new file mode 100644 index 0000000..4560b2f --- /dev/null +++ b/test/openpower-pels/pel_utils.cpp @@ -0,0 +1,297 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "pel_utils.hpp" + +#include "extensions/openpower-pels/private_header.hpp" +#include "extensions/openpower-pels/user_header.hpp" + +#include <fstream> + +#include <gtest/gtest.h> + +namespace fs = std::filesystem; +using namespace openpower::pels; + +std::filesystem::path CleanLogID::pelIDFile{}; +std::filesystem::path CleanPELFiles::pelIDFile{}; +std::filesystem::path CleanPELFiles::repoPath{}; +std::filesystem::path CleanPELFiles::registryPath{}; + +const std::vector<uint8_t> privateHeaderSection{ + // section header + 0x50, 0x48, // ID 'PH' + 0x00, 0x30, // Size + 0x01, 0x02, // version, subtype + 0x03, 0x04, // comp ID + + 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, // create timestamp + 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, 0x00, // commit timestamp + 0xAA, // creatorID + 0x00, // logtype + 0x00, // reserved + 0x02, // section count + 0x90, 0x91, 0x92, 0x93, // OpenBMC log ID + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0, // creator version + 0x50, 0x51, 0x52, 0x53, // plid + 0x80, 0x81, 0x82, 0x83}; + +const std::vector<uint8_t> userHeaderSection{ + // section header + 0x55, 0x48, // ID 'UH' + 0x00, 0x18, // Size + 0x01, 0x0A, // version, subtype + 0x0B, 0x0C, // comp ID + + 0x10, 0x04, // subsystem, scope + 0x20, 0x00, // severity, type + 0x00, 0x00, 0x00, 0x00, // reserved + 0x03, 0x04, // problem domain, vector + 0x80, 0xC0, // action flags + 0x00, 0x00, 0x00, 0x00 // reserved +}; + +const std::vector<uint8_t> srcSectionNoCallouts{ + + // Header + 'P', 'S', 0x00, 0x50, 0x01, 0x01, 0x02, 0x02, + + 0x02, 0x00, 0x00, // version, flags, reserved + 0x09, 0x00, 0x00, // hex word count, reserved2B + 0x00, 0x48, // SRC structure size + + // Hex words 2 - 9 + 0x02, 0x02, 0x02, 0x55, 0x03, 0x03, 0x03, 0x10, 0x04, 0x04, 0x04, 0x04, + 0x05, 0x05, 0x05, 0x05, 0x06, 0x06, 0x06, 0x06, 0x07, 0x07, 0x07, 0x07, + 0x08, 0x08, 0x08, 0x08, 0x09, 0x09, 0x09, 0x09, + // ASCII string + 'B', 'D', '8', 'D', '5', '6', '7', '8', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', + ' ', ' '}; + +const std::vector<uint8_t> failingMTMSSection{ + // Header + 0x4D, 0x54, 0x00, 0x1C, 0x01, 0x00, 0x20, 0x00, + + 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', + '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C'}; + +const std::vector<uint8_t> UserDataSection{ + // Header + 0x55, 0x44, 0x00, 0x10, 0x00, 0x00, 0x20, 0x00, + + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; + +const std::vector<uint8_t> ExtUserHeaderSection{ + // Header + 'E', 'H', 0x00, 0x60, 0x01, 0x00, 0x03, 0x04, + + // MTMS + 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', + + // Server FW version + 'S', 'E', 'R', 'V', 'E', 'R', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', + '\0', + + // Subsystem FW Version + 'B', 'M', 'C', '_', 'V', 'E', 'R', 'S', 'I', 'O', 'N', '\0', '\0', '\0', + '\0', '\0', + + 0x00, 0x00, 0x00, 0x00, // Reserved + 0x20, 0x25, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, // Ref time + 0x00, 0x00, 0x00, // Reserved + + // SymptomID length and symptom ID + 20, 'B', 'D', '8', 'D', '4', '2', '0', '0', '_', '1', '2', '3', '4', '5', + '6', '7', '8', '\0', '\0', '\0'}; + +const std::vector<uint8_t> srcFRUIdentityCallout{ + 'I', 'D', 0x1C, 0x1D, // type, size, flags + '1', '2', '3', '4', // PN + '5', '6', '7', 0x00, 'A', 'A', 'A', 'A', // CCIN + '1', '2', '3', '4', '5', '6', '7', '8', // SN + '9', 'A', 'B', 'C'}; + +const std::vector<uint8_t> srcPCEIdentityCallout{ + 'P', 'E', 0x24, 0x00, // type, size, flags + 'T', 'T', 'T', 'T', '-', 'M', 'M', 'M', // MTM + '1', '2', '3', '4', '5', '6', '7', // SN + '8', '9', 'A', 'B', 'C', 'P', 'C', 'E', // Name + null padded + 'N', 'A', 'M', 'E', '1', '2', 0x00, 0x00, 0x00}; + +const std::vector<uint8_t> srcMRUCallout{ + 'M', 'R', 0x28, 0x04, // ID, size, flags + 0x00, 0x00, 0x00, 0x00, // Reserved + 0x00, 0x00, 0x00, 'H', // priority 0 + 0x01, 0x01, 0x01, 0x01, // MRU ID 0 + 0x00, 0x00, 0x00, 'M', // priority 1 + 0x02, 0x02, 0x02, 0x02, // MRU ID 1 + 0x00, 0x00, 0x00, 'L', // priority 2 + 0x03, 0x03, 0x03, 0x03, // MRU ID 2 + 0x00, 0x00, 0x00, 'H', // priority 3 + 0x04, 0x04, 0x04, 0x04, // MRU ID 3 +}; + +constexpr size_t sectionCountOffset = 27; + +std::vector<uint8_t> pelDataFactory(TestPELType type) +{ + std::vector<uint8_t> data; + + switch (type) + { + case TestPELType::pelSimple: + data.insert(data.end(), privateHeaderSection.begin(), + privateHeaderSection.end()); + data.insert(data.end(), userHeaderSection.begin(), + userHeaderSection.end()); + data.insert(data.end(), srcSectionNoCallouts.begin(), + srcSectionNoCallouts.end()); + data.insert(data.end(), failingMTMSSection.begin(), + failingMTMSSection.end()); + data.insert(data.end(), UserDataSection.begin(), + UserDataSection.end()); + data.insert(data.end(), ExtUserHeaderSection.begin(), + ExtUserHeaderSection.end()); + data.at(sectionCountOffset) = 6; + break; + case TestPELType::privateHeaderSection: + data.insert(data.end(), privateHeaderSection.begin(), + privateHeaderSection.end()); + break; + case TestPELType::userHeaderSection: + data.insert(data.end(), userHeaderSection.begin(), + userHeaderSection.end()); + break; + case TestPELType::primarySRCSection: + data.insert(data.end(), srcSectionNoCallouts.begin(), + srcSectionNoCallouts.end()); + break; + case TestPELType::primarySRCSection2Callouts: + { + // Start with the no-callouts SRC, and add the callouts section + // from above. + auto src = srcSectionNoCallouts; + auto callouts = + srcDataFactory(TestSRCType::calloutSection2Callouts); + + src.insert(src.end(), callouts.begin(), callouts.end()); + + // Set the flag that says there are callouts + // One byte after the 8B header + src[8 + 1] |= 0x01; + + // Set the new sizes + uint16_t size = src.size(); + Stream stream{src}; + + stream.offset(2); // In the header + stream << size; + + // In the SRC - the size field doesn't include the header + size -= 8; + stream.offset(8 + 6); + stream << size; + + data.insert(data.end(), src.begin(), src.end()); + break; + } + case TestPELType::failingMTMSSection: + data.insert(data.end(), failingMTMSSection.begin(), + failingMTMSSection.end()); + } + return data; +} + +std::vector<uint8_t> srcDataFactory(TestSRCType type) +{ + switch (type) + { + case TestSRCType::fruIdentityStructure: + return srcFRUIdentityCallout; + + case TestSRCType::pceIdentityStructure: + return srcPCEIdentityCallout; + + case TestSRCType::mruStructure: + return srcMRUCallout; + + case TestSRCType::calloutStructureA: + { + // Add just the FRU identity substructure to the base structure + std::vector<uint8_t> data{ + 0xFF, 0x28, 'H', 4, // size, flags, priority, LC length + 'U', '4', '2', 0x00 // LC + }; + + data.insert(data.end(), srcFRUIdentityCallout.begin(), + srcFRUIdentityCallout.end()); + + // The final size + data[0] = data.size(); + return data; + } + case TestSRCType::calloutStructureB: + { + // Add all 3 substructures to the base structure + + std::vector<uint8_t> data{ + 0xFF, 0x2F, 'L', 8, // size, flags, priority, LC length + 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC + }; + data.insert(data.end(), srcFRUIdentityCallout.begin(), + srcFRUIdentityCallout.end()); + data.insert(data.end(), srcPCEIdentityCallout.begin(), + srcPCEIdentityCallout.end()); + data.insert(data.end(), srcMRUCallout.begin(), srcMRUCallout.end()); + + // The final size + data[0] = data.size(); + return data; + } + case TestSRCType::calloutSection2Callouts: + { + std::vector<uint8_t> data{0xC0, 0x00, 0x00, + 0x00}; // ID, flags, length in words + + // Add 2 callouts + auto callout = srcDataFactory(TestSRCType::calloutStructureA); + data.insert(data.end(), callout.begin(), callout.end()); + + callout = srcDataFactory(TestSRCType::calloutStructureB); + data.insert(data.end(), callout.begin(), callout.end()); + + // Set the actual word length value at offset 2 + Stream stream{data}; + uint16_t wordLength = data.size() / 4; + stream.offset(2); + stream << wordLength; + stream.offset(0); + + return data; + } + } + return {}; +} + +std::unique_ptr<std::vector<uint8_t>> readPELFile(const fs::path& path) +{ + std::ifstream file{path}; + + auto pel = std::make_unique<std::vector<uint8_t>>( + std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()); + return pel; +} diff --git a/test/openpower-pels/pel_utils.hpp b/test/openpower-pels/pel_utils.hpp new file mode 100644 index 0000000..fae62cd --- /dev/null +++ b/test/openpower-pels/pel_utils.hpp @@ -0,0 +1,106 @@ +#include "extensions/openpower-pels/paths.hpp" + +#include <filesystem> +#include <memory> +#include <vector> + +#include <gtest/gtest.h> + +/** + * @brief Test fixture to remove the pelID file that PELs use. + */ +class CleanLogID : public ::testing::Test +{ + protected: + static void SetUpTestCase() + { + pelIDFile = openpower::pels::getPELIDFile(); + } + + static void TearDownTestCase() + { + std::filesystem::remove_all( + std::filesystem::path{pelIDFile}.parent_path()); + } + + static std::filesystem::path pelIDFile; +}; + +class CleanPELFiles : public ::testing::Test +{ + protected: + void SetUp() override + { + pelIDFile = openpower::pels::getPELIDFile(); + repoPath = openpower::pels::getPELRepoPath(); + registryPath = openpower::pels::getMessageRegistryPath(); + } + + void TearDown() override + { + std::filesystem::remove_all( + std::filesystem::path{pelIDFile}.parent_path()); + std::filesystem::remove_all(repoPath); + std::filesystem::remove_all(registryPath); + } + + static std::filesystem::path pelIDFile; + static std::filesystem::path repoPath; + static std::filesystem::path registryPath; +}; + +/** + * @brief Tells the factory which PEL to create + */ +enum class TestPELType +{ + pelSimple, + privateHeaderSection, + userHeaderSection, + primarySRCSection, + primarySRCSection2Callouts, + failingMTMSSection +}; + +/** + * @brief Tells the SRC factory which data to create + */ +enum class TestSRCType +{ + fruIdentityStructure, + pceIdentityStructure, + mruStructure, + calloutStructureA, + calloutStructureB, + calloutSection2Callouts +}; + +/** + * @brief PEL data factory, for testing + * + * @param[in] type - the type of data to create + * + * @return std::vector<uint8_t> - the PEL data + */ +std::vector<uint8_t> pelDataFactory(TestPELType type); + +/** + * @brief SRC data factory, for testing + * + * Provides pieces of the SRC PEL section, such as a callout. + * + * @param[in] type - the type of data to create + * + * @return std::vector<uint8_t> - The SRC data + */ +std::vector<uint8_t> srcDataFactory(TestSRCType type); + +/** + * @brief Helper function to read raw PEL data from a file + * + * @param[in] path - the path to read + * + * @return std::unique_ptr<std::vector<uint8_t>> - the data from the file + */ +std::unique_ptr<std::vector<uint8_t>> + readPELFile(const std::filesystem::path& path); diff --git a/test/openpower-pels/pel_values_test.cpp b/test/openpower-pels/pel_values_test.cpp new file mode 100644 index 0000000..843cb07 --- /dev/null +++ b/test/openpower-pels/pel_values_test.cpp @@ -0,0 +1,40 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/pel_values.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels::pel_values; + +TEST(PELFieldsTest, TestFindFields) +{ + auto s = findByValue(0x5D, subsystemValues); + ASSERT_NE(s, subsystemValues.end()); + ASSERT_EQ(0x5D, std::get<fieldValuePos>(*s)); + ASSERT_EQ("cec_service_network", std::get<registryNamePos>(*s)); + + s = findByName("cec_clocks", subsystemValues); + ASSERT_NE(s, subsystemValues.end()); + ASSERT_EQ(0x58, std::get<fieldValuePos>(*s)); + ASSERT_EQ("cec_clocks", std::get<registryNamePos>(*s)); + ASSERT_EQ("CEC Hardware: Clock", std::get<descriptionPos>(*s)); + + s = findByValue(0xFF, subsystemValues); + ASSERT_EQ(s, subsystemValues.end()); + + s = findByName("foo", subsystemValues); + ASSERT_EQ(s, subsystemValues.end()); +} diff --git a/test/openpower-pels/private_header_test.cpp b/test/openpower-pels/private_header_test.cpp new file mode 100644 index 0000000..0349a94 --- /dev/null +++ b/test/openpower-pels/private_header_test.cpp @@ -0,0 +1,194 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/private_header.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +class PrivateHeaderTest : public CleanLogID +{ +}; + +TEST_F(PrivateHeaderTest, SizeTest) +{ + EXPECT_EQ(PrivateHeader::flattenedSize(), 48); +} + +TEST_F(PrivateHeaderTest, UnflattenFlattenTest) +{ + auto data = pelDataFactory(TestPELType::privateHeaderSection); + + Stream stream(data); + PrivateHeader ph(stream); + EXPECT_EQ(ph.valid(), true); + + EXPECT_EQ(ph.header().id, 0x5048); + EXPECT_EQ(ph.header().size, PrivateHeader::flattenedSize()); + EXPECT_EQ(ph.header().version, 0x01); + EXPECT_EQ(ph.header().subType, 0x02); + EXPECT_EQ(ph.header().componentID, 0x0304); + + auto ct = ph.createTimestamp(); + EXPECT_EQ(ct.yearMSB, 0x20); + EXPECT_EQ(ct.yearLSB, 0x30); + EXPECT_EQ(ct.month, 0x05); + EXPECT_EQ(ct.day, 0x09); + EXPECT_EQ(ct.hour, 0x11); + EXPECT_EQ(ct.minutes, 0x1E); + EXPECT_EQ(ct.seconds, 0x01); + EXPECT_EQ(ct.hundredths, 0x63); + + auto mt = ph.commitTimestamp(); + EXPECT_EQ(mt.yearMSB, 0x20); + EXPECT_EQ(mt.yearLSB, 0x31); + EXPECT_EQ(mt.month, 0x06); + EXPECT_EQ(mt.day, 0x0F); + EXPECT_EQ(mt.hour, 0x09); + EXPECT_EQ(mt.minutes, 0x22); + EXPECT_EQ(mt.seconds, 0x3A); + EXPECT_EQ(mt.hundredths, 0x00); + + EXPECT_EQ(ph.creatorID(), 0xAA); + EXPECT_EQ(ph.logType(), 0x00); + EXPECT_EQ(ph.sectionCount(), 0x02); + EXPECT_EQ(ph.obmcLogID(), 0x90919293); + + char expected[] = {0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x00}; + EXPECT_TRUE(memcmp(ph.creatorVersion().version, expected, 8) == 0); + + EXPECT_EQ(ph.plid(), 0x50515253); + EXPECT_EQ(ph.id(), 0x80818283); + + // Now flatten into a vector and check that this vector + // matches the original one. + std::vector<uint8_t> newData; + Stream newStream(newData); + + ph.flatten(newStream); + EXPECT_EQ(data, newData); + + // Change a field, then flatten and unflatten again + ph.setID(0x55); + + newStream.offset(0); + newData.clear(); + ph.flatten(newStream); + EXPECT_NE(data, newData); + + newStream.offset(0); + PrivateHeader newPH(newStream); + + EXPECT_TRUE(newPH.valid()); + EXPECT_EQ(newPH.id(), 0x55); +} + +TEST_F(PrivateHeaderTest, ShortDataTest) +{ + auto data = pelDataFactory(TestPELType::privateHeaderSection); + data.resize(PrivateHeader::flattenedSize() - 1); + Stream stream(data); + + PrivateHeader ph(stream); + + EXPECT_EQ(ph.valid(), false); +} + +TEST_F(PrivateHeaderTest, CorruptDataTest1) +{ + auto data = pelDataFactory(TestPELType::privateHeaderSection); + Stream stream(data); + + data.at(0) = 0; // corrupt the section ID + + PrivateHeader ph(stream); + + EXPECT_EQ(ph.valid(), false); +} + +TEST_F(PrivateHeaderTest, CorruptDataTest2) +{ + auto data = pelDataFactory(TestPELType::privateHeaderSection); + Stream stream(data); + + data.at(4) = 0x22; // corrupt the version + + PrivateHeader ph(stream); + + EXPECT_EQ(ph.valid(), false); +} + +TEST_F(PrivateHeaderTest, CorruptDataTest3) +{ + auto data = pelDataFactory(TestPELType::privateHeaderSection); + Stream stream(data); + + data.at(27) = 1; // corrupt the section count + + PrivateHeader ph(stream); + + EXPECT_EQ(ph.valid(), false); +} + +// Construct a PrivateHeader from scratch +TEST_F(PrivateHeaderTest, ConstructionTest) +{ + tm time_tm; + time_tm.tm_year = 125; + time_tm.tm_mon = 11; + time_tm.tm_mday = 31; + time_tm.tm_hour = 15; + time_tm.tm_min = 23; + time_tm.tm_sec = 42; + time_tm.tm_isdst = 0; + + // Convert the above time into a uint64_t in ms since the epoch time + auto timepoint = std::chrono::system_clock::from_time_t(mktime(&time_tm)); + auto timestamp = std::chrono::duration_cast<std::chrono::milliseconds>( + timepoint.time_since_epoch()) + .count(); + + PrivateHeader ph(0x3300, 42, timestamp); + + EXPECT_TRUE(ph.valid()); + EXPECT_EQ(ph.header().id, 0x5048); + EXPECT_EQ(ph.header().size, PrivateHeader::flattenedSize()); + EXPECT_EQ(ph.header().version, 0x01); + EXPECT_EQ(ph.header().subType, 0x00); + EXPECT_EQ(ph.header().componentID, 0x3300); + + auto& ct = ph.createTimestamp(); + EXPECT_EQ(ct.yearMSB, 0x20); + EXPECT_EQ(ct.yearLSB, 0x25); + EXPECT_EQ(ct.month, 0x12); + EXPECT_EQ(ct.day, 0x31); + EXPECT_EQ(ct.hour, 0x15); + EXPECT_EQ(ct.minutes, 0x23); + EXPECT_EQ(ct.seconds, 0x42); + EXPECT_EQ(ct.hundredths, 0x00); + + EXPECT_EQ(ph.creatorID(), 'O'); + EXPECT_EQ(ph.logType(), 0x00); + EXPECT_EQ(ph.sectionCount(), 0x01); + EXPECT_EQ(ph.obmcLogID(), 42); + + char expected[] = {0, 0, 0, 0, 0, 0, 0, 0}; + EXPECT_TRUE(memcmp(ph.creatorVersion().version, expected, 8) == 0); + + EXPECT_EQ(ph.id(), 0x50000001); + EXPECT_EQ(ph.id(), ph.plid()); +} diff --git a/test/openpower-pels/real_pel_test.cpp b/test/openpower-pels/real_pel_test.cpp new file mode 100644 index 0000000..a170812 --- /dev/null +++ b/test/openpower-pels/real_pel_test.cpp @@ -0,0 +1,570 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "elog_entry.hpp" +#include "extensions/openpower-pels/pel.hpp" +#include "pel_utils.hpp" + +#include <filesystem> +#include <fstream> + +#include <gtest/gtest.h> + +namespace fs = std::filesystem; +using namespace openpower::pels; + +class PELTest : public CleanLogID +{ +}; + +// A PEL from a real system +const std::vector<uint8_t> realPELData{ + 0x50, 0x48, 0x0, 0x30, 0x1, 0x0, 0xA8, 0x0, 0x20, 0x19, 0x6, 0x14, + 0x12, 0x0, 0x41, 0x51, 0x20, 0x19, 0x6, 0x14, 0x12, 0x0, 0x41, 0x56, + 0x45, 0x0, 0x1, 0x14, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x50, 0x1, 0xF, 0xA5, 0x50, 0x1, 0xF, 0xA5, + 0x55, 0x48, 0x0, 0x18, 0x1, 0x0, 0xF1, 0x0, 0x81, 0x3, 0x0, 0x1, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x0, 0x0, 0x1, 0x44, 0x0, + 0x50, 0x53, 0x0, 0x74, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x1, 0x0, 0x9, + 0x0, 0x0, 0x0, 0x6C, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x1B, 0x10, + 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x10, 0x69, 0x14, 0xD8, + 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x0, + 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xC0, 0x0, 0x0, 0x9, + 0x10, 0x28, 0x48, 0x0, 0x49, 0x44, 0xC, 0x42, 0x46, 0x53, 0x50, 0x53, + 0x50, 0x30, 0x34, 0x0, 0x10, 0x28, 0x4C, 0x0, 0x49, 0x44, 0xC, 0x42, + 0x46, 0x53, 0x50, 0x53, 0x50, 0x30, 0x36, 0x0, 0x45, 0x48, 0x0, 0x60, + 0x1, 0x0, 0x31, 0x0, 0x38, 0x34, 0x30, 0x38, 0x2D, 0x45, 0x38, 0x45, + 0x31, 0x30, 0x36, 0x37, 0x41, 0x44, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x54, 0x56, 0x38, 0x36, 0x30, 0x5F, 0x32, 0x30, 0x37, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x62, 0x30, 0x36, 0x31, 0x33, 0x61, 0x5F, 0x31, + 0x39, 0x32, 0x34, 0x2E, 0x38, 0x36, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x14, + 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x5F, 0x32, 0x43, 0x43, + 0x36, 0x31, 0x42, 0x31, 0x30, 0x0, 0x0, 0x0, 0x55, 0x44, 0x0, 0x9C, + 0x2, 0x4, 0x31, 0x0, 0x0, 0x0, 0xB, 0x53, 0x2F, 0x6F, 0x70, 0x74, + 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x66, 0x77, + 0x64, 0x62, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x66, 0x69, 0x70, 0x73, + 0x38, 0x36, 0x31, 0x2F, 0x62, 0x30, 0x36, 0x31, 0x33, 0x61, 0x5F, 0x31, + 0x39, 0x32, 0x34, 0x2E, 0x38, 0x36, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x2, + 0x50, 0x0, 0x0, 0x2, 0x20, 0x0, 0x1, 0xC, 0x0, 0x0, 0x0, 0x9, + 0x0, 0x4, 0x70, 0xD0, 0x0, 0x0, 0x0, 0x0, 0x4D, 0x54, 0x0, 0x1C, + 0x1, 0x0, 0x31, 0x0, 0x38, 0x34, 0x30, 0x38, 0x2D, 0x45, 0x38, 0x45, + 0x31, 0x30, 0x36, 0x37, 0x41, 0x44, 0x56, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x55, 0x44, 0x1, 0xF8, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, + 0x46, 0x57, 0x44, 0x42, 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xF0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0xF0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, + 0x0, 0x0, 0x5, 0x1C, 0x2D, 0x86, 0x75, 0xD0, 0x0, 0x0, 0xC, 0x9B, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, + 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xE, 0xE3, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x33, 0x1F, 0xEA, 0xE7, 0x12, + 0x0, 0x0, 0xD, 0x9D, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, + 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, + 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, + 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xA, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x38, + 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C, 0x0, 0x0, 0x23, 0x33, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, + 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88, 0x20, 0xE1, 0xE0, 0x7, + 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, + 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, + 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, + 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x9B, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89, + 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x55, 0x44, 0x0, 0x40, 0x1, 0x1, 0xA8, 0x0, 0x6E, 0x6F, 0x20, 0x73, + 0x75, 0x63, 0x68, 0x20, 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63, + 0x62, 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, + 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, + 0x69, 0x65, 0x77, 0x0, 0x55, 0x44, 0x1, 0xE8, 0x1, 0xC, 0x31, 0x0, + 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x53, 0x52, 0x56, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xB, 0x0, 0x0, 0x5, 0x89, 0x11, 0x9B, 0xBA, 0x9, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x18, 0x43, 0x4F, 0x80, 0x10, 0xFA, 0x28, + 0x0, 0x0, 0xC, 0xFD, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98, + 0x0, 0x1, 0xD4, 0xC0, 0x0, 0x0, 0x0, 0x34, 0x0, 0x0, 0x5, 0x89, + 0x12, 0x4, 0x1A, 0xF1, 0x0, 0x0, 0x23, 0x88, 0x0, 0x18, 0x43, 0x4F, + 0xCB, 0x1C, 0xBA, 0x66, 0x0, 0x0, 0x1F, 0x6D, 0x63, 0x68, 0x61, 0x6E, + 0x67, 0x65, 0x55, 0x73, 0x65, 0x43, 0x6E, 0x74, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xC, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x34, + 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0x78, 0xA9, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x0, 0x43, 0x4F, 0xCB, 0x12, 0xB6, 0x5D, 0x0, 0x0, 0x9, 0xF2, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xB8, 0x93, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, 0x89, 0x7D, 0x0, 0x8C, + 0x0, 0x0, 0x23, 0x5D, 0x73, 0x65, 0x72, 0x76, 0x44, 0x65, 0x74, 0x61, + 0x63, 0x68, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x2C, + 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xD2, 0x7A, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x0, 0x43, 0x4F, 0xBD, 0xF0, 0x6E, 0xE3, 0x0, 0x0, 0x9, 0xF4, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xAA, 0x51, 0x96, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0x8F, 0x6A, 0x83, 0x3, + 0x0, 0x0, 0x9, 0xA9, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x12, 0xAE, 0x7B, 0x85, 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, + 0xF3, 0x35, 0x3F, 0x8D, 0x0, 0x0, 0x22, 0x35, 0x73, 0x65, 0x72, 0x76, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3, + 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA5, 0x23, 0xFA, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0xFC, 0x5A, 0x7F, 0x97, + 0x0, 0x0, 0x9, 0xB3, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x14, 0x5B, 0x57, 0xCE, 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, + 0x4, 0x88, 0x89, 0x7B, 0x0, 0x0, 0x9, 0x3B, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x14, 0x5F, 0xCC, 0x54, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x50, 0x43, 0x4F, 0x94, 0xA2, 0x26, 0x7C, 0x0, 0x0, 0x14, 0xE1, + 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x53, 0x74, 0x6D, 0x74, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x6E, 0x6F, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20, + 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, + 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x0, + 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x6C, + 0x55, 0x44, 0x3, 0xC0, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, + 0x46, 0x57, 0x44, 0x42, 0x53, 0x51, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, + 0x0, 0x0, 0x5, 0x89, 0x5, 0xFC, 0x1E, 0x6C, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, + 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, + 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, + 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, + 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, + 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, + 0x6, 0xB1, 0x42, 0x3B, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, + 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, + 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, + 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, + 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, + 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, + 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, + 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x14, 0xD2, 0x68, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, + 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, + 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, + 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, + 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, + 0x0, 0x0, 0x5, 0x89, 0x7, 0x9A, 0x9D, 0x6F, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, + 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, + 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, + 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, + 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, + 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, + 0x7, 0xD4, 0xF8, 0x2D, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, + 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, + 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, + 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, + 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, + 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, + 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, + 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xB4, 0xBC, 0xA6, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x73, 0x79, 0x73, + 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x79, 0x73, 0x3B, 0xA, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x12, 0xBC, 0xD8, 0x8A, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x30, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x30, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x30, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0x97, 0x51, 0x64, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x31, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x31, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA2, 0x85, 0x51, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x33, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x33, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x33, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x14, 0x5B, 0x84, 0x7B, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x58, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, + 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, + 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, + 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, + 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x74, + 0x55, 0x44, 0x0, 0xC, 0x1, 0xC, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0x53, 0x0, 0x50, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x0, 0x0, 0x9, + 0x0, 0x0, 0x0, 0x48, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x19, 0x10, + 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x9D, 0x86, 0xE6, 0xD3, + 0x0, 0x0, 0x0, 0x3, 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x0, + 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x45, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x44, 0x1, 0xF4, + 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, + 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xEC, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x5, 0x33, + 0x1F, 0xEA, 0xE7, 0x12, 0x0, 0x0, 0xD, 0x9D, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0xA, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x0, 0x0, 0x5, 0x38, 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, + 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C, + 0x0, 0x0, 0x23, 0x33, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, + 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, + 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, + 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88, + 0x20, 0xE1, 0xE0, 0x7, 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0x9B, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x0, 0x0, 0x5, 0x89, 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, + 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89, 0x15, 0x7A, 0x13, 0xA4, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x2C, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, + 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, + 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x72, 0x76, + 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x0, 0x0, 0x0, 0x0, 0x1B, + 0x0, 0x0, 0xA8, 0xE, 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x48, 0x55, 0x44, 0x1, 0xE8, 0x1, 0xC, 0x31, 0x0, + 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x53, 0x52, 0x56, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x9, 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xB8, 0x93, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, 0x89, 0x7D, 0x0, 0x8C, + 0x0, 0x0, 0x23, 0x5D, 0x73, 0x65, 0x72, 0x76, 0x44, 0x65, 0x74, 0x61, + 0x63, 0x68, 0x0, 0x0, 0xDB, 0xFB, 0xCB, 0x98, 0x0, 0x0, 0x0, 0x2C, + 0x0, 0x0, 0x5, 0x89, 0x12, 0x36, 0xD2, 0x7A, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x0, 0x43, 0x4F, 0xBD, 0xF0, 0x6E, 0xE3, 0x0, 0x0, 0x9, 0xF4, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xAA, 0x51, 0x96, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0x8F, 0x6A, 0x83, 0x3, + 0x0, 0x0, 0x9, 0xA9, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x12, 0xAE, 0x7B, 0x85, 0x0, 0x0, 0x23, 0x88, 0x0, 0x10, 0x43, 0x4F, + 0xF3, 0x35, 0x3F, 0x8D, 0x0, 0x0, 0x22, 0x35, 0x73, 0x65, 0x72, 0x76, + 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3, + 0x0, 0x0, 0x0, 0x2C, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA5, 0x23, 0xFA, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, 0xFC, 0x5A, 0x7F, 0x97, + 0x0, 0x0, 0x9, 0xB3, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x14, 0x5B, 0x57, 0xCE, 0x0, 0x0, 0x23, 0x88, 0x0, 0x0, 0x43, 0x4F, + 0x4, 0x88, 0x89, 0x7B, 0x0, 0x0, 0x9, 0x3B, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x14, 0x5F, 0xCC, 0x54, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x50, 0x43, 0x4F, 0x94, 0xA2, 0x26, 0x7C, 0x0, 0x0, 0x14, 0xE1, + 0x70, 0x72, 0x65, 0x70, 0x61, 0x72, 0x65, 0x53, 0x74, 0x6D, 0x74, 0x0, + 0x0, 0x0, 0x0, 0x1, 0x6E, 0x6F, 0x20, 0x73, 0x75, 0x63, 0x68, 0x20, + 0x74, 0x61, 0x62, 0x6C, 0x65, 0x3A, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, + 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, + 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, + 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x0, + 0x10, 0x69, 0x14, 0xD8, 0x0, 0x0, 0x0, 0x3, 0x0, 0x0, 0x0, 0x6C, + 0x0, 0x0, 0x5, 0x89, 0x15, 0x79, 0xCE, 0xB1, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x68, 0x43, 0x4F, 0xF9, 0xB, 0x8E, 0x3C, 0x0, 0x0, 0x21, 0x3E, + 0x73, 0x65, 0x72, 0x76, 0x45, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x0, + 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0x0, 0x3, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, + 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, + 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, + 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, + 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x84, + 0x55, 0x44, 0x3, 0xC0, 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, + 0x46, 0x57, 0x44, 0x42, 0x53, 0x51, 0x4C, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x3, 0xB8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xB, + 0x0, 0x0, 0x5, 0x89, 0x5, 0xFC, 0x1E, 0x6C, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, + 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, + 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, + 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, + 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, + 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, + 0x6, 0xB1, 0x42, 0x3B, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, + 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, + 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, + 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, + 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, + 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, + 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, + 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x14, 0xD2, 0x68, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, + 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, + 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, + 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, + 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, + 0x0, 0x0, 0x5, 0x89, 0x7, 0x9A, 0x9D, 0x6F, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x40, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, + 0x14, 0x9, 0xC2, 0x11, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, + 0x6C, 0x69, 0x63, 0x79, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, + 0x61, 0x6D, 0x65, 0x20, 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, + 0x2D, 0x64, 0x65, 0x66, 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, + 0x65, 0x73, 0x27, 0x0, 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, + 0x7, 0xD4, 0xF8, 0x2D, 0x0, 0x0, 0x23, 0x88, 0x0, 0x40, 0x43, 0x4F, + 0x92, 0x41, 0x1B, 0xD4, 0x0, 0x0, 0x21, 0x6, 0x14, 0x9, 0xC2, 0x11, + 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x2A, 0x20, 0x66, 0x72, 0x6F, + 0x6D, 0x20, 0x73, 0x79, 0x73, 0x2E, 0x70, 0x6F, 0x6C, 0x69, 0x63, 0x79, + 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x6E, 0x61, 0x6D, 0x65, 0x20, + 0x3D, 0x20, 0x27, 0x70, 0x6F, 0x77, 0x65, 0x72, 0x2D, 0x64, 0x65, 0x66, + 0x2D, 0x66, 0x69, 0x6C, 0x65, 0x6E, 0x61, 0x6D, 0x65, 0x73, 0x27, 0x0, + 0x0, 0x0, 0x0, 0x5C, 0x0, 0x0, 0x5, 0x89, 0x12, 0xB4, 0xBC, 0xA6, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x73, 0x79, 0x73, + 0x27, 0x20, 0x41, 0x53, 0x20, 0x73, 0x79, 0x73, 0x3B, 0xA, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x12, 0xBC, 0xD8, 0x8A, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x30, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x30, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x30, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0x97, 0x51, 0x64, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x31, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x31, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x31, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA2, 0x85, 0x51, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x38, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x41, 0x54, 0x54, 0x41, + 0x43, 0x48, 0x20, 0x44, 0x41, 0x54, 0x41, 0x42, 0x41, 0x53, 0x45, 0x20, + 0x27, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x70, 0x33, 0x2F, 0x66, 0x77, 0x73, + 0x6D, 0x2F, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6D, 0x2E, 0x70, 0x33, 0x27, + 0x20, 0x41, 0x53, 0x20, 0x70, 0x33, 0x3B, 0xA, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x54, 0x0, 0x0, 0x5, 0x89, 0x14, 0x5B, 0x84, 0x7B, + 0x0, 0x0, 0x23, 0x88, 0x0, 0x58, 0x43, 0x4F, 0x92, 0x41, 0x1B, 0xD4, + 0x0, 0x0, 0x21, 0x6, 0x9D, 0x86, 0xE6, 0xD3, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, + 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, + 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, + 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, + 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x0, 0x0, 0x0, 0x74, + 0x55, 0x44, 0x0, 0xC, 0x1, 0xC, 0x31, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x53, 0x53, 0x0, 0x50, 0x1, 0x1, 0xA8, 0x0, 0x2, 0x0, 0x0, 0x9, + 0x0, 0x0, 0x0, 0x48, 0x3, 0x1, 0x0, 0xF0, 0x2C, 0xC6, 0x67, 0x10, + 0xC1, 0x39, 0x20, 0x0, 0x40, 0x0, 0x0, 0xFF, 0x0, 0x0, 0x0, 0x3, + 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x42, 0x31, 0x38, 0x31, 0x41, 0x38, 0x30, 0x35, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, + 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x55, 0x44, 0x1, 0xF4, + 0x1, 0xC, 0x31, 0x0, 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, + 0x45, 0x52, 0x52, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x1, 0xEC, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xEC, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0x0, 0x0, 0x5, 0x38, + 0x26, 0x43, 0xFB, 0x66, 0x0, 0x0, 0xB, 0x7D, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0xE, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x0, 0x0, 0x5, 0x72, 0x13, 0xB8, 0x3D, 0x1C, 0x0, 0x0, 0x23, 0x33, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, + 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, + 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x5D, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x88, 0x20, 0xE1, 0xE0, 0x7, + 0x0, 0x0, 0xD, 0x82, 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, + 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, + 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x74, 0x42, + 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, 0x75, 0x74, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, 0x50, 0x1, 0xF, 0x9B, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, 0x0, 0x0, 0x5, 0x89, + 0x12, 0xE, 0xFE, 0x6E, 0x0, 0x0, 0xB, 0x69, 0x0, 0x30, 0x43, 0x4F, + 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, 0x66, 0x77, 0x64, 0x62, + 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, 0x6E, 0x66, 0x6F, 0x0, + 0x73, 0x65, 0x74, 0x42, 0x75, 0x73, 0x79, 0x54, 0x69, 0x6D, 0x65, 0x6F, + 0x75, 0x74, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1A, 0x0, 0x0, 0xA8, 0x9, + 0x50, 0x1, 0xF, 0xA4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x4C, + 0x0, 0x0, 0x5, 0x89, 0x15, 0x7A, 0x13, 0xA4, 0x0, 0x0, 0x23, 0x88, + 0x0, 0x2C, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x73, 0x65, 0x72, 0x76, 0x45, 0x78, 0x65, 0x63, + 0x75, 0x74, 0x65, 0x0, 0x0, 0x0, 0x0, 0x1B, 0x0, 0x0, 0xA8, 0xE, + 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x48, + 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0xF7, 0xAC, 0x0, 0x0, 0xB, 0x69, + 0x0, 0x30, 0x43, 0x4F, 0x27, 0x67, 0x44, 0xB7, 0x0, 0x0, 0x0, 0xB5, + 0x66, 0x77, 0x64, 0x62, 0x54, 0x72, 0x61, 0x63, 0x45, 0x72, 0x72, 0x49, + 0x6E, 0x66, 0x6F, 0x0, 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4E, + 0x6F, 0x46, 0x6D, 0x74, 0x53, 0x74, 0x72, 0x0, 0x0, 0x0, 0x0, 0x1B, + 0x0, 0x0, 0xA8, 0xE, 0x50, 0x1, 0xF, 0xA5, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4C, 0x55, 0x44, 0x1, 0xEC, 0x1, 0xC, 0x31, 0x0, + 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x43, 0x4D, 0x44, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE4, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xE4, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xD, 0x0, 0x0, 0x5, 0x89, 0x5, 0xF9, 0x4B, 0x5C, + 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, + 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x6, 0x36, 0xBE, 0xE8, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, + 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x6, 0xAE, 0x6C, 0x5C, 0x0, 0x0, 0x23, 0x72, + 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x6, 0xC3, 0x48, 0x7A, + 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E, + 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x7, 0x12, 0x50, 0xB6, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, + 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x7, 0x2C, 0x43, 0xFE, 0x0, 0x0, 0x23, 0x72, + 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x7, 0x97, 0x92, 0x5F, + 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, + 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x7, 0xAF, 0x7C, 0xC6, 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, + 0xC9, 0x49, 0xE2, 0x7E, 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x7, 0xD2, 0x84, 0xF, 0x0, 0x0, 0x23, 0x72, + 0x0, 0x0, 0x43, 0x4F, 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, + 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, 0x8, 0x6E, 0xBC, 0x17, + 0x0, 0x0, 0x23, 0x72, 0x0, 0x0, 0x43, 0x4F, 0xC9, 0x49, 0xE2, 0x7E, + 0x0, 0x0, 0x1, 0xD6, 0x0, 0x0, 0x0, 0x1C, 0x0, 0x0, 0x5, 0x89, + 0x13, 0xA8, 0x5C, 0x6F, 0x0, 0x0, 0xB, 0x69, 0x0, 0x0, 0x43, 0x4F, + 0x32, 0x5F, 0xF6, 0x1, 0x0, 0x0, 0x1, 0x59, 0x0, 0x0, 0x0, 0x1C, + 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0x8F, 0x96, 0x0, 0x0, 0xB, 0x69, + 0x0, 0x6C, 0x43, 0x4F, 0x9A, 0x1E, 0xAD, 0xA5, 0x0, 0x0, 0x1, 0x9D, + 0x65, 0x78, 0x65, 0x63, 0x75, 0x74, 0x65, 0x4E, 0x6F, 0x46, 0x6D, 0x74, + 0x53, 0x74, 0x72, 0x0, 0x0, 0x0, 0x0, 0x3, 0x73, 0x65, 0x6C, 0x65, + 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, + 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, + 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, + 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, + 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, + 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, + 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, 0x9D, 0x86, 0xE6, 0xD3, + 0x0, 0x0, 0x0, 0x88, 0x55, 0x44, 0x1, 0xD0, 0x1, 0xC, 0x31, 0x0, + 0x1, 0x28, 0x4, 0x42, 0x46, 0x57, 0x44, 0x42, 0x55, 0x54, 0x49, 0x4C, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xC8, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0xC8, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x4, 0x0, 0x0, 0x5, 0x89, 0x13, 0xA7, 0xC0, 0xB6, + 0x0, 0x0, 0xB, 0x69, 0x0, 0x3C, 0x43, 0x4F, 0x4C, 0x53, 0x3A, 0xE0, + 0x0, 0x0, 0x1, 0xF7, 0x45, 0x58, 0x49, 0x54, 0x0, 0x0, 0x0, 0x0, + 0x41, 0x54, 0x54, 0x41, 0x43, 0x48, 0x0, 0x0, 0x9D, 0x86, 0xE6, 0xD3, + 0x0, 0x0, 0xB, 0x60, 0x0, 0x0, 0xB, 0x69, 0x2F, 0x6F, 0x70, 0x74, + 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x68, 0x65, + 0x61, 0x6C, 0x74, 0x68, 0x6D, 0x6F, 0x6E, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x43, 0x8F, 0x0, 0x0, 0x0, 0x58, 0x0, 0x0, 0x5, 0x89, + 0x13, 0xA8, 0xF8, 0x3A, 0x0, 0x0, 0xB, 0x69, 0x0, 0x88, 0x43, 0x4F, + 0x4C, 0x53, 0x3A, 0xE0, 0x0, 0x0, 0x1, 0xF7, 0x45, 0x4E, 0x54, 0x52, + 0x0, 0x0, 0x0, 0x0, 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x65, + 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x66, 0x72, 0x6F, + 0x6D, 0x20, 0x63, 0x62, 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65, + 0x5F, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69, + 0x63, 0x5F, 0x76, 0x69, 0x65, 0x77, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, + 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x21, + 0x3D, 0x20, 0x30, 0x0, 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0xB, 0x60, + 0x0, 0x0, 0xB, 0x69, 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x66, 0x69, 0x70, + 0x73, 0x2F, 0x62, 0x69, 0x6E, 0x2F, 0x68, 0x65, 0x61, 0x6C, 0x74, 0x68, + 0x6D, 0x6F, 0x6E, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0xA4, 0x0, 0x0, 0x5, 0x89, 0x16, 0xA5, 0x62, 0xFB, + 0x0, 0x0, 0xB, 0x69, 0x0, 0x88, 0x43, 0x4F, 0x4C, 0x53, 0x3A, 0xE0, + 0x0, 0x0, 0x1, 0xF7, 0x45, 0x58, 0x49, 0x54, 0x0, 0x0, 0x0, 0x0, + 0x73, 0x65, 0x6C, 0x65, 0x63, 0x74, 0x20, 0x65, 0x72, 0x72, 0x6F, 0x72, + 0x5F, 0x65, 0x69, 0x64, 0x20, 0x66, 0x72, 0x6F, 0x6D, 0x20, 0x63, 0x62, + 0x6C, 0x76, 0x5F, 0x63, 0x61, 0x62, 0x6C, 0x65, 0x5F, 0x63, 0x6F, 0x6E, + 0x6E, 0x65, 0x63, 0x74, 0x69, 0x6F, 0x6E, 0x5F, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x5F, 0x70, 0x75, 0x62, 0x6C, 0x69, 0x63, 0x5F, 0x76, 0x69, + 0x65, 0x77, 0x20, 0x77, 0x68, 0x65, 0x72, 0x65, 0x20, 0x65, 0x72, 0x72, + 0x6F, 0x72, 0x5F, 0x65, 0x69, 0x64, 0x20, 0x21, 0x3D, 0x20, 0x30, 0x0, + 0x9D, 0x86, 0xE6, 0xD3, 0x0, 0x0, 0xB, 0x60, 0x0, 0x0, 0xB, 0x69, + 0x2F, 0x6F, 0x70, 0x74, 0x2F, 0x66, 0x69, 0x70, 0x73, 0x2F, 0x62, 0x69, + 0x6E, 0x2F, 0x68, 0x65, 0x61, 0x6C, 0x74, 0x68, 0x6D, 0x6F, 0x6E, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xC5, 0x63, 0x0, 0x0, 0x0, 0xA4}; + +TEST_F(PELTest, RealPELTest) +{ + auto origData = realPELData; + PEL pel{origData}; + + EXPECT_TRUE(pel.valid()); + + // Check that the flat data is correct + auto flat = pel.data(); + EXPECT_EQ(realPELData, flat); + EXPECT_EQ(realPELData.size(), pel.size()); + + // Check that the code can extract an object for every section. + //(The PrivateHeader and UserHeader account for the + 2 below.) + const auto& sections = pel.optionalSections(); + EXPECT_EQ(pel.privateHeader().sectionCount(), sections.size() + 2); + + auto src = pel.primarySRC(); + EXPECT_EQ(src.value()->asciiString(), "B181A80E "); + + // Check that the last section (a 'UD' section) is indeed the last + // section object by checking the ID and the last byte. + auto& last = pel.optionalSections().back(); + EXPECT_EQ(last->header().id, 0x5544); // "UD" + + std::vector<uint8_t> lastSectionData; + Stream stream{lastSectionData}; + last->flatten(stream); + EXPECT_EQ(lastSectionData.back(), 0xA4); +} diff --git a/test/openpower-pels/registry_test.cpp b/test/openpower-pels/registry_test.cpp new file mode 100644 index 0000000..2944ce0 --- /dev/null +++ b/test/openpower-pels/registry_test.cpp @@ -0,0 +1,336 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/registry.hpp" + +#include <filesystem> +#include <fstream> +#include <nlohmann/json.hpp> + +#include <gtest/gtest.h> + +using namespace openpower::pels::message; +namespace fs = std::filesystem; + +const auto registryData = R"( +{ + "PELs": + [ + { + "Name": "xyz.openbmc_project.Power.Fault", + "Subsystem": "power_supply", + + "SRC": + { + "ReasonCode": "0x2030" + }, + + "Documentation": + { + "Description": "A PGOOD Fault", + "Message": "PS had a PGOOD Fault" + } + }, + + { + "Name": "xyz.openbmc_project.Power.OverVoltage", + "Subsystem": "power_control_hw", + "Severity": "unrecoverable", + "MfgSeverity": "non_error", + "ActionFlags": ["service_action", "report", "call_home"], + "MfgActionFlags": ["hidden"], + + "SRC": + { + "ReasonCode": "0x2333", + "Type": "BD", + "SymptomIDFields": ["SRCWord5", "SRCWord6", "SRCWord7"], + "PowerFault": true, + "Words6To9": + { + "6": + { + "description": "Failing unit number", + "AdditionalDataPropSource": "PS_NUM" + }, + + "7": + { + "description": "bad voltage", + "AdditionalDataPropSource": "VOLTAGE" + } + } + }, + + "Documentation": + { + "Description": "A PGOOD Fault", + "Message": "PS %1 had a PGOOD Fault", + "MessageArgSources": + [ + "SRCWord6" + ], + "Notes": [ + "In the UserData section there is a JSON", + "dump that provides debug information." + ] + } + } + ] +} +)"; + +class RegistryTest : public ::testing::Test +{ + protected: + static void SetUpTestCase() + { + char path[] = "/tmp/regtestXXXXXX"; + regDir = mkdtemp(path); + } + + static void TearDownTestCase() + { + fs::remove_all(regDir); + } + + static std::string writeData(const char* data) + { + fs::path path = regDir / "registry.json"; + std::ofstream stream{path}; + stream << data; + return path; + } + + static fs::path regDir; +}; + +fs::path RegistryTest::regDir{}; + +TEST_F(RegistryTest, TestNoEntry) +{ + auto path = RegistryTest::writeData(registryData); + Registry registry{path}; + + auto entry = registry.lookup("foo", LookupType::name); + EXPECT_FALSE(entry); +} + +TEST_F(RegistryTest, TestFindEntry) +{ + auto path = RegistryTest::writeData(registryData); + Registry registry{path}; + + auto entry = registry.lookup("xyz.openbmc_project.Power.OverVoltage", + LookupType::name); + ASSERT_TRUE(entry); + EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage"); + EXPECT_EQ(entry->subsystem, 0x62); + EXPECT_EQ(*(entry->severity), 0x40); + EXPECT_EQ(*(entry->mfgSeverity), 0x00); + EXPECT_EQ(*(entry->actionFlags), 0xA800); + EXPECT_EQ(*(entry->mfgActionFlags), 0x4000); + EXPECT_EQ(entry->componentID, 0x2300); + EXPECT_FALSE(entry->eventType); + EXPECT_FALSE(entry->eventScope); + + EXPECT_EQ(entry->src.type, 0xBD); + EXPECT_EQ(entry->src.reasonCode, 0x2333); + EXPECT_EQ(*(entry->src.powerFault), true); + + auto& hexwords = entry->src.hexwordADFields; + EXPECT_TRUE(hexwords); + EXPECT_EQ((*hexwords).size(), 2); + + auto word = (*hexwords).find(6); + EXPECT_NE(word, (*hexwords).end()); + EXPECT_EQ(word->second, "PS_NUM"); + + word = (*hexwords).find(7); + EXPECT_NE(word, (*hexwords).end()); + EXPECT_EQ(word->second, "VOLTAGE"); + + auto& sid = entry->src.symptomID; + EXPECT_TRUE(sid); + EXPECT_EQ((*sid).size(), 3); + EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 5), (*sid).end()); + EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 6), (*sid).end()); + EXPECT_NE(std::find((*sid).begin(), (*sid).end(), 7), (*sid).end()); + + EXPECT_EQ(entry->doc.description, "A PGOOD Fault"); + EXPECT_EQ(entry->doc.message, "PS %1 had a PGOOD Fault"); + auto& hexwordSource = entry->doc.messageArgSources; + EXPECT_TRUE(hexwordSource); + EXPECT_EQ((*hexwordSource).size(), 1); + EXPECT_EQ((*hexwordSource).front(), "SRCWord6"); + + entry = registry.lookup("0x2333", LookupType::reasonCode); + ASSERT_TRUE(entry); + EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.OverVoltage"); +} + +// Check the entry that mostly uses defaults +TEST_F(RegistryTest, TestFindEntryMinimal) +{ + auto path = RegistryTest::writeData(registryData); + Registry registry{path}; + + auto entry = + registry.lookup("xyz.openbmc_project.Power.Fault", LookupType::name); + ASSERT_TRUE(entry); + EXPECT_EQ(entry->name, "xyz.openbmc_project.Power.Fault"); + EXPECT_EQ(entry->subsystem, 0x61); + EXPECT_FALSE(entry->severity); + EXPECT_FALSE(entry->mfgSeverity); + EXPECT_FALSE(entry->mfgActionFlags); + EXPECT_FALSE(entry->actionFlags); + EXPECT_EQ(entry->componentID, 0x2000); + EXPECT_FALSE(entry->eventType); + EXPECT_FALSE(entry->eventScope); + + EXPECT_EQ(entry->src.reasonCode, 0x2030); + EXPECT_EQ(entry->src.type, 0xBD); + EXPECT_FALSE(entry->src.powerFault); + EXPECT_FALSE(entry->src.hexwordADFields); + EXPECT_FALSE(entry->src.symptomID); +} + +TEST_F(RegistryTest, TestBadJSON) +{ + auto path = RegistryTest::writeData("bad {} json"); + + Registry registry{path}; + + EXPECT_FALSE(registry.lookup("foo", LookupType::name)); +} + +// Test the helper functions the use the pel_values data. +TEST_F(RegistryTest, TestHelperFunctions) +{ + using namespace openpower::pels::message::helper; + EXPECT_EQ(getSubsystem("input_power_source"), 0xA1); + EXPECT_THROW(getSubsystem("foo"), std::runtime_error); + + EXPECT_EQ(getSeverity("symptom_recovered"), 0x71); + EXPECT_THROW(getSeverity("foo"), std::runtime_error); + + EXPECT_EQ(getEventType("dump_notification"), 0x08); + EXPECT_THROW(getEventType("foo"), std::runtime_error); + + EXPECT_EQ(getEventScope("possibly_multiple_platforms"), 0x04); + EXPECT_THROW(getEventScope("foo"), std::runtime_error); + + std::vector<std::string> flags{"service_action", "dont_report", + "termination"}; + EXPECT_EQ(getActionFlags(flags), 0x9100); + + flags.clear(); + flags.push_back("foo"); + EXPECT_THROW(getActionFlags(flags), std::runtime_error); +} + +TEST_F(RegistryTest, TestGetSRCReasonCode) +{ + using namespace openpower::pels::message::helper; + EXPECT_EQ(getSRCReasonCode(R"({"ReasonCode": "0x5555"})"_json, "foo"), + 0x5555); + + EXPECT_THROW(getSRCReasonCode(R"({"ReasonCode": "ZZZZ"})"_json, "foo"), + std::runtime_error); +} + +TEST_F(RegistryTest, TestGetSRCType) +{ + using namespace openpower::pels::message::helper; + EXPECT_EQ(getSRCType(R"({"Type": "11"})"_json, "foo"), 0x11); + EXPECT_EQ(getSRCType(R"({"Type": "BF"})"_json, "foo"), 0xBF); + + EXPECT_THROW(getSRCType(R"({"Type": "1"})"_json, "foo"), + std::runtime_error); + + EXPECT_THROW(getSRCType(R"({"Type": "111"})"_json, "foo"), + std::runtime_error); +} + +TEST_F(RegistryTest, TestGetSRCHexwordFields) +{ + using namespace openpower::pels::message::helper; + const auto hexwords = R"( + {"Words6To9": + { + "8": + { + "AdditionalDataPropSource": "TEST" + } + } + })"_json; + + auto fields = getSRCHexwordFields(hexwords, "foo"); + EXPECT_TRUE(fields); + auto word = fields->find(8); + EXPECT_NE(word, fields->end()); + + const auto theInvalidRWord = R"( + {"Words6To9": + { + "R": + { + "AdditionalDataPropSource": "TEST" + } + } + })"_json; + + EXPECT_THROW(getSRCHexwordFields(theInvalidRWord, "foo"), + std::runtime_error); +} + +TEST_F(RegistryTest, TestGetSRCSymptomIDFields) +{ + using namespace openpower::pels::message::helper; + const auto sID = R"( + { + "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord5"] + })"_json; + + auto fields = getSRCSymptomIDFields(sID, "foo"); + EXPECT_NE(std::find(fields->begin(), fields->end(), 3), fields->end()); + EXPECT_NE(std::find(fields->begin(), fields->end(), 4), fields->end()); + EXPECT_NE(std::find(fields->begin(), fields->end(), 5), fields->end()); + + const auto badField = R"( + { + "SymptomIDFields": ["SRCWord3", "SRCWord4", "SRCWord"] + })"_json; + + EXPECT_THROW(getSRCSymptomIDFields(badField, "foo"), std::runtime_error); +} + +TEST_F(RegistryTest, TestGetComponentID) +{ + using namespace openpower::pels::message::helper; + + // Get it from the JSON + auto id = + getComponentID(0xBD, 0x4200, R"({"ComponentID":"0x4200"})"_json, "foo"); + EXPECT_EQ(id, 0x4200); + + // Get it from the reason code on a 0xBD SRC + id = getComponentID(0xBD, 0x6700, R"({})"_json, "foo"); + EXPECT_EQ(id, 0x6700); + + // Not present on a 0x11 SRC + EXPECT_THROW(getComponentID(0x11, 0x8800, R"({})"_json, "foo"), + std::runtime_error); +} diff --git a/test/openpower-pels/repository_test.cpp b/test/openpower-pels/repository_test.cpp new file mode 100644 index 0000000..446e10e --- /dev/null +++ b/test/openpower-pels/repository_test.cpp @@ -0,0 +1,436 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/paths.hpp" +#include "extensions/openpower-pels/repository.hpp" +#include "pel_utils.hpp" + +#include <ext/stdio_filebuf.h> + +#include <filesystem> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +namespace fs = std::filesystem; + +/** + * Clean the Repo after every testcase. + * And because we have PEL object, also clean up + * the log ID. + */ +class RepositoryTest : public CleanLogID +{ + protected: + void SetUp() override + { + repoPath = getPELRepoPath(); + } + + void TearDown() override + { + fs::remove_all(repoPath); + } + + fs::path repoPath; +}; + +TEST_F(RepositoryTest, FilenameTest) +{ + BCDTime date = {0x20, 0x30, 0x11, 0x28, 0x13, 0x6, 0x7, 0x8}; + + EXPECT_EQ(Repository::getPELFilename(0x12345678, date), + "2030112813060708_12345678"); + + EXPECT_EQ(Repository::getPELFilename(0xAABBCCDD, date), + "2030112813060708_AABBCCDD"); + + EXPECT_EQ(Repository::getPELFilename(0x3AFF1, date), + "2030112813060708_0003AFF1"); + + EXPECT_EQ(Repository::getPELFilename(100, date), + "2030112813060708_00000064"); + + EXPECT_EQ(Repository::getPELFilename(0, date), "2030112813060708_00000000"); +} + +TEST_F(RepositoryTest, AddTest) +{ + Repository repo{repoPath}; + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + + repo.add(pel); + + // Check that the PEL was stored where it was supposed to be, + // and that it wrote the PEL data. + const auto ts = pel->privateHeader().commitTimestamp(); + auto name = Repository::getPELFilename(pel->id(), ts); + + fs::path file = repoPath / "logs" / name; + EXPECT_TRUE(fs::exists(file)); + + auto newData = readPELFile(file); + auto pelData = pel->data(); + EXPECT_EQ(*newData, pelData); +} + +TEST_F(RepositoryTest, RestoreTest) +{ + using pelID = Repository::LogID::Pel; + using obmcID = Repository::LogID::Obmc; + + std::vector<Repository::LogID> ids; + + { + Repository repo{repoPath}; + + // Add some PELs to the repository + { + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data, 1); + pel->assignID(); + repo.add(pel); + ids.emplace_back(pelID(pel->id()), obmcID(1)); + } + { + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data, 2); + pel->assignID(); + repo.add(pel); + ids.emplace_back(pelID(pel->id()), obmcID(2)); + } + + // Check they're there + EXPECT_TRUE(repo.hasPEL(ids[0])); + EXPECT_TRUE(repo.hasPEL(ids[1])); + + // Do some other search tests while we're here. + + // Search based on PEL ID + Repository::LogID id(pelID(ids[0].pelID)); + EXPECT_TRUE(repo.hasPEL(id)); + + // Search based on OBMC log ID + id.pelID.id = 0; + id.obmcID = ids[0].obmcID; + EXPECT_TRUE(repo.hasPEL(id)); + + // ... based on the other PEL ID + id.pelID = ids[1].pelID; + id.obmcID.id = 0; + EXPECT_TRUE(repo.hasPEL(id)); + + // Not found + id.pelID.id = 99; + id.obmcID.id = 100; + EXPECT_FALSE(repo.hasPEL(id)); + } + + { + // Restore and check they're still there, then + // remove them. + Repository repo{repoPath}; + EXPECT_TRUE(repo.hasPEL(ids[0])); + EXPECT_TRUE(repo.hasPEL(ids[1])); + + repo.remove(ids[0]); + EXPECT_FALSE(repo.hasPEL(ids[0])); + + repo.remove(ids[1]); + EXPECT_FALSE(repo.hasPEL(ids[1])); + } +} + +TEST_F(RepositoryTest, TestGetPELData) +{ + using ID = Repository::LogID; + Repository repo{repoPath}; + + ID badID{ID::Pel(42)}; + auto noData = repo.getPELData(badID); + EXPECT_FALSE(noData); + + // Add a PEL to the repo, and get the data back with getPELData. + auto data = pelDataFactory(TestPELType::pelSimple); + auto dataCopy = data; + auto pel = std::make_unique<PEL>(data); + auto pelID = pel->id(); + repo.add(pel); + + ID id{ID::Pel(pelID)}; + auto pelData = repo.getPELData(id); + + ASSERT_TRUE(pelData); + EXPECT_EQ(dataCopy, *pelData); +} + +TEST_F(RepositoryTest, TestForEach) +{ + Repository repo{repoPath}; + + // Add 2 PELs + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + repo.add(pel); + + pel = std::make_unique<PEL>(data); + pel->assignID(); + pel->setCommitTime(); + repo.add(pel); + + // Make a function that saves the IDs + std::vector<uint32_t> ids; + Repository::ForEachFunc f1 = [&ids](const PEL& pel) { + ids.push_back(pel.id()); + return false; + }; + + repo.for_each(f1); + + EXPECT_EQ(ids.size(), 2); + + // Stop after the first time in. + Repository::ForEachFunc f2 = [&ids](const PEL& pel) { + ids.push_back(pel.id()); + return true; + }; + + ids.clear(); + repo.for_each(f2); + EXPECT_EQ(ids.size(), 1); +} + +TEST_F(RepositoryTest, TestSubscriptions) +{ + std::vector<uint32_t> added; + std::vector<uint32_t> removed; + + Repository::AddCallback ac = [&added](const PEL& pel) { + added.push_back(pel.id()); + }; + + Repository::DeleteCallback dc = [&removed](uint32_t id) { + removed.push_back(id); + }; + + Repository repo{repoPath}; + repo.subscribeToAdds("test", ac); + repo.subscribeToDeletes("test", dc); + + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + auto pelID = pel->id(); + repo.add(pel); + + EXPECT_EQ(added.size(), 1); + + using ID = Repository::LogID; + ID id{ID::Pel(pelID)}; + repo.remove(id); + + EXPECT_EQ(removed.size(), 1); + + repo.unsubscribeFromAdds("test"); + repo.unsubscribeFromDeletes("test"); + + added.clear(); + removed.clear(); + + repo.add(pel); + EXPECT_EQ(added.size(), 0); + + repo.remove(id); + EXPECT_EQ(removed.size(), 0); +} + +TEST_F(RepositoryTest, TestGetAttributes) +{ + uint32_t pelID = 0; + std::bitset<16> actionFlags; + + { + Repository repo{repoPath}; + + // Add a PEL to the repo + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + repo.add(pel); + + pelID = pel->id(); + actionFlags = pel->userHeader().actionFlags(); + + using ID = Repository::LogID; + ID id{ID::Pel(pelID)}; + + auto a = repo.getPELAttributes(id); + EXPECT_TRUE(a); + EXPECT_EQ((*a).get().actionFlags, actionFlags); + + id.pelID.id = 0; + a = repo.getPELAttributes(id); + EXPECT_FALSE(a); + } + + { + // Restore the repository and check again + Repository repo{repoPath}; + + using ID = Repository::LogID; + ID id{ID::Pel(pelID)}; + + auto a = repo.getPELAttributes(id); + EXPECT_TRUE(a); + EXPECT_EQ((*a).get().actionFlags, actionFlags); + + id.pelID.id = 0; + a = repo.getPELAttributes(id); + EXPECT_FALSE(a); + } +} + +TEST_F(RepositoryTest, TestSetHostState) +{ + // Add a PEL to the repo + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + using ID = Repository::LogID; + ID id{ID::Pel(pel->id())}; + + { + Repository repo{repoPath}; + + repo.add(pel); + + auto a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hostState, TransmissionState::newPEL); + + repo.setPELHostTransState(pel->id(), TransmissionState::acked); + + // First, check the attributes + a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hostState, TransmissionState::acked); + + // Next, check the PEL data itself + auto pelData = repo.getPELData(id); + PEL newPEL{*pelData}; + EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked); + } + + { + // Now restore, and check again + Repository repo{repoPath}; + + // First, check the attributes + auto a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hostState, TransmissionState::acked); + + // Next, check the PEL data itself + auto pelData = repo.getPELData(id); + PEL newPEL{*pelData}; + EXPECT_EQ(newPEL.hostTransmissionState(), TransmissionState::acked); + } +} + +TEST_F(RepositoryTest, TestSetHMCState) +{ + // Add a PEL to the repo + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + using ID = Repository::LogID; + ID id{ID::Pel(pel->id())}; + + { + Repository repo{repoPath}; + + repo.add(pel); + + auto a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hmcState, TransmissionState::newPEL); + + repo.setPELHMCTransState(pel->id(), TransmissionState::acked); + + // First, check the attributes + a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hmcState, TransmissionState::acked); + + // Next, check the PEL data itself + auto pelData = repo.getPELData(id); + PEL newPEL{*pelData}; + EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked); + } + + { + // Now restore, and check again + Repository repo{repoPath}; + + // First, check the attributes + auto a = repo.getPELAttributes(id); + EXPECT_EQ((*a).get().hmcState, TransmissionState::acked); + + // Next, check the PEL data itself + auto pelData = repo.getPELData(id); + PEL newPEL{*pelData}; + EXPECT_EQ(newPEL.hmcTransmissionState(), TransmissionState::acked); + } +} + +TEST_F(RepositoryTest, TestGetPELFD) +{ + Repository repo{repoPath}; + + auto data = pelDataFactory(TestPELType::pelSimple); + auto pel = std::make_unique<PEL>(data); + pel->setCommitTime(); + pel->assignID(); + + repo.add(pel); + + using ID = Repository::LogID; + ID id{ID::Pel(pel->id())}; + + auto fd = repo.getPELFD(id); + + EXPECT_TRUE(fd); + + // Get the size + struct stat s; + int r = fstat(*fd, &s); + ASSERT_EQ(r, 0); + + auto size = s.st_size; + + // Read the PEL data out of the FD + FILE* fp = fdopen(*fd, "r"); + ASSERT_NE(fp, nullptr); + + std::vector<uint8_t> newData; + newData.resize(size); + r = fread(newData.data(), 1, size, fp); + EXPECT_EQ(r, size); + + PEL newPEL{newData}; + + EXPECT_TRUE(newPEL.valid()); + EXPECT_EQ(newPEL.id(), pel->id()); + + fclose(fp); + + // Call getPELFD again, this time with a bad ID + id.pelID.id = 42; + fd = repo.getPELFD(id); + + EXPECT_FALSE(fd); +} diff --git a/test/openpower-pels/section_header_test.cpp b/test/openpower-pels/section_header_test.cpp new file mode 100644 index 0000000..b8ff732 --- /dev/null +++ b/test/openpower-pels/section_header_test.cpp @@ -0,0 +1,55 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "extensions/openpower-pels/section_header.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(SectionHeaderTest, SizeTest) +{ + EXPECT_EQ(SectionHeader::flattenedSize(), 8); +} + +TEST(SectionHeaderTest, UnflattenTest) +{ + std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + Stream reader{data}; + SectionHeader header; + + reader >> header; + + EXPECT_EQ(header.id, 0x1122); + EXPECT_EQ(header.size, 0x3344); + EXPECT_EQ(header.version, 0x55); + EXPECT_EQ(header.subType, 0x66); + EXPECT_EQ(header.componentID, 0x7788); +} + +TEST(SectionHeaderTest, FlattenTest) +{ + SectionHeader header{0xAABB, 0xCCDD, 0xEE, 0xFF, 0xA0A0}; + + std::vector<uint8_t> data; + Stream writer{data}; + + writer << header; + + std::vector<uint8_t> expected{0xAA, 0xBB, 0xCC, 0xDD, + 0xEE, 0xFF, 0xA0, 0xA0}; + EXPECT_EQ(data, expected); +} diff --git a/test/openpower-pels/severity_test.cpp b/test/openpower-pels/severity_test.cpp new file mode 100644 index 0000000..29c3bc3 --- /dev/null +++ b/test/openpower-pels/severity_test.cpp @@ -0,0 +1,33 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/severity.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using LogSeverity = phosphor::logging::Entry::Level; + +TEST(SeverityTest, SeverityMapTest) +{ + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Informational), 0x00); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Notice), 0x00); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Debug), 0x00); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Warning), 0x20); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Critical), 0x50); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Emergency), 0x40); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Alert), 0x40); + ASSERT_EQ(convertOBMCSeverityToPEL(LogSeverity::Error), 0x40); +} diff --git a/test/openpower-pels/src_callout_test.cpp b/test/openpower-pels/src_callout_test.cpp new file mode 100644 index 0000000..3fd2a5c --- /dev/null +++ b/test/openpower-pels/src_callout_test.cpp @@ -0,0 +1,161 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/callout.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace openpower::pels::src; + +// Unflatten the callout section with all three substructures +TEST(CalloutTest, TestUnflattenAllSubstructures) +{ + // The base data. + std::vector<uint8_t> data{ + 0xFF, 0x2F, 'H', 8, // size, flags, priority, LC length + 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC + }; + + auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure); + auto pceIdentity = srcDataFactory(TestSRCType::pceIdentityStructure); + auto mrus = srcDataFactory(TestSRCType::mruStructure); + + // Add all 3 substructures + data.insert(data.end(), fruIdentity.begin(), fruIdentity.end()); + data.insert(data.end(), pceIdentity.begin(), pceIdentity.end()); + data.insert(data.end(), mrus.begin(), mrus.end()); + + // The final size + data[0] = data.size(); + + Stream stream{data}; + Callout callout{stream}; + + EXPECT_EQ(callout.flattenedSize(), data.size()); + EXPECT_EQ(callout.priority(), 'H'); + EXPECT_EQ(callout.locationCode(), "U12-P1"); + + // Spot check the 3 substructures + EXPECT_TRUE(callout.fruIdentity()); + EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC"); + + EXPECT_TRUE(callout.pceIdentity()); + EXPECT_EQ(callout.pceIdentity()->enclosureName(), "PCENAME12"); + + EXPECT_TRUE(callout.mru()); + EXPECT_EQ(callout.mru()->mrus().size(), 4); + EXPECT_EQ(callout.mru()->mrus().at(3).id, 0x04040404); + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + callout.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(CalloutTest, TestUnflattenOneSubstructure) +{ + std::vector<uint8_t> data{ + 0xFF, 0x28, 'H', 0x08, // size, flags, priority, LC length + 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC + }; + + auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure); + + data.insert(data.end(), fruIdentity.begin(), fruIdentity.end()); + + // The final size + data[0] = data.size(); + + Stream stream{data}; + Callout callout{stream}; + + EXPECT_EQ(callout.flattenedSize(), data.size()); + + // Spot check the substructure + EXPECT_TRUE(callout.fruIdentity()); + EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC"); + + // Not present + EXPECT_FALSE(callout.pceIdentity()); + EXPECT_FALSE(callout.mru()); + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + callout.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(CalloutTest, TestUnflattenTwoSubstructures) +{ + std::vector<uint8_t> data{ + 0xFF, 0x2B, 'H', 0x08, // size, flags, priority, LC length + 'U', '1', '2', '-', 'P', '1', 0x00, 0x00 // LC + }; + + auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure); + auto pceIdentity = srcDataFactory(TestSRCType::pceIdentityStructure); + + data.insert(data.end(), fruIdentity.begin(), fruIdentity.end()); + data.insert(data.end(), pceIdentity.begin(), pceIdentity.end()); + + // The final size + data[0] = data.size(); + + Stream stream{data}; + Callout callout{stream}; + + EXPECT_EQ(callout.flattenedSize(), data.size()); + + // Spot check the 2 substructures + EXPECT_TRUE(callout.fruIdentity()); + EXPECT_EQ(callout.fruIdentity()->getSN(), "123456789ABC"); + + EXPECT_TRUE(callout.pceIdentity()); + EXPECT_EQ(callout.pceIdentity()->enclosureName(), "PCENAME12"); + + // Not present + EXPECT_FALSE(callout.mru()); + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + callout.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(CalloutTest, TestNoLocationCode) +{ + std::vector<uint8_t> data{ + 0xFF, 0x2B, 'H', 0x00 // size, flags, priority, LC length + }; + + auto fruIdentity = srcDataFactory(TestSRCType::fruIdentityStructure); + data.insert(data.end(), fruIdentity.begin(), fruIdentity.end()); + + // The final size + data[0] = data.size(); + + Stream stream{data}; + Callout callout{stream}; + + EXPECT_TRUE(callout.locationCode().empty()); +} diff --git a/test/openpower-pels/src_callouts_test.cpp b/test/openpower-pels/src_callouts_test.cpp new file mode 100644 index 0000000..f192c15 --- /dev/null +++ b/test/openpower-pels/src_callouts_test.cpp @@ -0,0 +1,91 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/callouts.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; +using namespace openpower::pels::src; + +TEST(CalloutsTest, UnflattenFlattenTest) +{ + std::vector<uint8_t> data{0xC0, 0x00, 0x00, + 0x00}; // ID, flags, length in words + + // Add 2 callouts + auto callout = srcDataFactory(TestSRCType::calloutStructureA); + data.insert(data.end(), callout.begin(), callout.end()); + + callout = srcDataFactory(TestSRCType::calloutStructureB); + data.insert(data.end(), callout.begin(), callout.end()); + + Stream stream{data}; + + // Set the actual word length value at offset 2 + uint16_t wordLength = data.size() / 4; + stream.offset(2); + stream << wordLength; + stream.offset(0); + + Callouts callouts{stream}; + + EXPECT_EQ(callouts.flattenedSize(), data.size()); + EXPECT_EQ(callouts.callouts().size(), 2); + + // spot check that each callout has the right substructures + EXPECT_TRUE(callouts.callouts().front()->fruIdentity()); + EXPECT_FALSE(callouts.callouts().front()->pceIdentity()); + EXPECT_FALSE(callouts.callouts().front()->mru()); + + EXPECT_TRUE(callouts.callouts().back()->fruIdentity()); + EXPECT_TRUE(callouts.callouts().back()->pceIdentity()); + EXPECT_TRUE(callouts.callouts().back()->mru()); + + // Flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + callouts.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(CalloutsTest, BadDataTest) +{ + // Start out with a valid 2 callout object, then truncate it. + std::vector<uint8_t> data{0xC0, 0x00, 0x00, + 0x00}; // ID, flags, length in words + + // Add 2 callouts + auto callout = srcDataFactory(TestSRCType::calloutStructureA); + data.insert(data.end(), callout.begin(), callout.end()); + + callout = srcDataFactory(TestSRCType::calloutStructureB); + data.insert(data.end(), callout.begin(), callout.end()); + + Stream stream{data}; + + // Set the actual word length value at offset 2 + uint16_t wordLength = data.size() / 4; + stream.offset(2); + stream << wordLength; + stream.offset(0); + + // Shorten the data by an arbitrary amount so unflattening goes awry. + data.resize(data.size() - 37); + + EXPECT_THROW(Callouts callouts{stream}, std::out_of_range); +} diff --git a/test/openpower-pels/src_test.cpp b/test/openpower-pels/src_test.cpp new file mode 100644 index 0000000..b966344 --- /dev/null +++ b/test/openpower-pels/src_test.cpp @@ -0,0 +1,261 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/src.hpp" +#include "pel_utils.hpp" + +#include <fstream> + +#include <gtest/gtest.h> + +using namespace openpower::pels; +namespace fs = std::filesystem; + +const auto testRegistry = R"( +{ +"PELs": +[ + { + "Name": "xyz.openbmc_project.Error.Test", + "Subsystem": "bmc_firmware", + "SRC": + { + "ReasonCode": "0xABCD", + "Words6To9": + { + "6": + { + "Description": "Component ID", + "AdditionalDataPropSource": "COMPID" + }, + "7": + { + "Description": "Failure count", + "AdditionalDataPropSource": "FREQUENCY" + }, + "8": + { + "Description": "Time period", + "AdditionalDataPropSource": "DURATION" + }, + "9": + { + "Description": "Error code", + "AdditionalDataPropSource": "ERRORCODE" + } + } + }, + "Documentation": + { + "Description": "A Component Fault", + "Message": "Comp %1 failed %2 times over %3 secs with ErrorCode %4", + "MessageArgSources": + [ + "SRCWord6", "SRCWord7", "SRCWord8", "SRCWord9" + ] + } + } +] +} +)"; + +class SRCTest : public ::testing::Test +{ + protected: + static void SetUpTestCase() + { + char path[] = "/tmp/srctestXXXXXX"; + regDir = mkdtemp(path); + } + + static void TearDownTestCase() + { + fs::remove_all(regDir); + } + + static std::string writeData(const char* data) + { + fs::path path = regDir / "registry.json"; + std::ofstream stream{path}; + stream << data; + return path; + } + + static fs::path regDir; +}; + +fs::path SRCTest::regDir{}; + +TEST_F(SRCTest, UnflattenFlattenTestNoCallouts) +{ + auto data = pelDataFactory(TestPELType::primarySRCSection); + + Stream stream{data}; + SRC src{stream}; + + EXPECT_TRUE(src.valid()); + + EXPECT_EQ(src.header().id, 0x5053); + EXPECT_EQ(src.header().size, 0x50); + EXPECT_EQ(src.header().version, 0x01); + EXPECT_EQ(src.header().subType, 0x01); + EXPECT_EQ(src.header().componentID, 0x0202); + + EXPECT_EQ(src.version(), 0x02); + EXPECT_EQ(src.flags(), 0x00); + EXPECT_EQ(src.hexWordCount(), 9); + EXPECT_EQ(src.size(), 0x48); + + const auto& hexwords = src.hexwordData(); + EXPECT_EQ(0x02020255, hexwords[0]); + EXPECT_EQ(0x03030310, hexwords[1]); + EXPECT_EQ(0x04040404, hexwords[2]); + EXPECT_EQ(0x05050505, hexwords[3]); + EXPECT_EQ(0x06060606, hexwords[4]); + EXPECT_EQ(0x07070707, hexwords[5]); + EXPECT_EQ(0x08080808, hexwords[6]); + EXPECT_EQ(0x09090909, hexwords[7]); + + EXPECT_EQ(src.asciiString(), "BD8D5678 "); + EXPECT_FALSE(src.callouts()); + + // Flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + src.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST_F(SRCTest, UnflattenFlattenTest2Callouts) +{ + auto data = pelDataFactory(TestPELType::primarySRCSection2Callouts); + + Stream stream{data}; + SRC src{stream}; + + EXPECT_TRUE(src.valid()); + EXPECT_EQ(src.flags(), 0x01); // Additional sections within the SRC. + + // Spot check the SRC fields, but they're the same as above + EXPECT_EQ(src.asciiString(), "BD8D5678 "); + + // There should be 2 callouts + const auto& calloutsSection = src.callouts(); + ASSERT_TRUE(calloutsSection); + const auto& callouts = calloutsSection->callouts(); + EXPECT_EQ(callouts.size(), 2); + + // spot check that each callout has the right substructures + EXPECT_TRUE(callouts.front()->fruIdentity()); + EXPECT_FALSE(callouts.front()->pceIdentity()); + EXPECT_FALSE(callouts.front()->mru()); + + EXPECT_TRUE(callouts.back()->fruIdentity()); + EXPECT_TRUE(callouts.back()->pceIdentity()); + EXPECT_TRUE(callouts.back()->mru()); + + // Flatten + std::vector<uint8_t> newData; + Stream newStream{newData}; + + src.flatten(newStream); + EXPECT_EQ(data, newData); +} + +// Create an SRC from the message registry +TEST_F(SRCTest, CreateTestNoCallouts) +{ + message::Entry entry; + entry.src.type = 0xBD; + entry.src.reasonCode = 0xABCD; + entry.subsystem = 0x42; + entry.src.powerFault = true; + entry.src.hexwordADFields = {{5, "TEST1"}, // Not a user defined word + {6, "TEST1"}, + {7, "TEST2"}, + {8, "TEST3"}, + {9, "TEST4"}}; + + // Values for the SRC words pointed to above + std::vector<std::string> adData{"TEST1=0x12345678", "TEST2=12345678", + "TEST3=0XDEF", "TEST4=Z"}; + AdditionalData ad{adData}; + SRC src{entry, ad}; + + EXPECT_TRUE(src.valid()); + EXPECT_TRUE(src.isPowerFaultEvent()); + EXPECT_EQ(src.size(), baseSRCSize); + + const auto& hexwords = src.hexwordData(); + + // The spec always refers to SRC words 2 - 9, and as the hexwordData() + // array index starts at 0 use the math in the [] below to make it easier + // to tell what is being accessed. + EXPECT_EQ(hexwords[2 - 2] & 0xF0000000, 0); // Partition dump status + EXPECT_EQ(hexwords[2 - 2] & 0x00F00000, 0); // Partition boot type + EXPECT_EQ(hexwords[2 - 2] & 0x000000FF, 0x55); // SRC format + EXPECT_EQ(hexwords[3 - 2] & 0x000000FF, 0x10); // BMC position + + // Validate more fields here as the code starts filling them in. + + // Ensure hex word 5 wasn't allowed to be set to TEST1's contents + EXPECT_EQ(hexwords[5 - 2], 0); + + // The user defined hex word fields specifed in the additional data. + EXPECT_EQ(hexwords[6 - 2], 0x12345678); // TEST1 + EXPECT_EQ(hexwords[7 - 2], 12345678); // TEST2 + EXPECT_EQ(hexwords[8 - 2], 0xdef); // TEST3 + EXPECT_EQ(hexwords[9 - 2], 0); // TEST4, but can't convert a 'Z' + + EXPECT_EQ(src.asciiString(), "BD42ABCD "); + + // No callouts + EXPECT_FALSE(src.callouts()); + + // May as well spot check the flatten/unflatten + std::vector<uint8_t> data; + Stream stream{data}; + src.flatten(stream); + + stream.offset(0); + SRC newSRC{stream}; + + EXPECT_TRUE(newSRC.valid()); + EXPECT_EQ(newSRC.isPowerFaultEvent(), src.isPowerFaultEvent()); + EXPECT_EQ(newSRC.asciiString(), src.asciiString()); + EXPECT_FALSE(newSRC.callouts()); +} + +// Test the getErrorDetails function +TEST_F(SRCTest, MessageSubstitutionTest) +{ + auto path = SRCTest::writeData(testRegistry); + message::Registry registry{path}; + auto entry = registry.lookup("0xABCD", message::LookupType::reasonCode); + + std::vector<std::string> adData{"COMPID=0x1", "FREQUENCY=0x4", + "DURATION=30", "ERRORCODE=0x01ABCDEF"}; + AdditionalData ad{adData}; + + SRC src{*entry, ad}; + EXPECT_TRUE(src.valid()); + + auto errorDetails = src.getErrorDetails(registry, DetailLevel::message); + ASSERT_TRUE(errorDetails); + EXPECT_EQ( + errorDetails.value(), + "Comp 0x1 failed 0x4 times over 0x1E secs with ErrorCode 0x1ABCDEF"); +} diff --git a/test/openpower-pels/stream_test.cpp b/test/openpower-pels/stream_test.cpp new file mode 100644 index 0000000..361cfc5 --- /dev/null +++ b/test/openpower-pels/stream_test.cpp @@ -0,0 +1,197 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/stream.hpp" + +#include <iostream> + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(StreamTest, TestExtract) +{ + std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 'h', 'e', 'l', 'l', 'o'}; + Stream stream{data}; + + { + uint8_t v; + stream >> v; + EXPECT_EQ(v, 0x11); + } + { + uint16_t v; + stream >> v; + EXPECT_EQ(v, 0x2233); + } + { + uint32_t v; + stream >> v; + EXPECT_EQ(v, 0x44556677); + } + { + uint64_t v; + stream >> v; + EXPECT_EQ(v, 0x0102030405060708); + } + { + char v[6] = {0}; + stream.read(v, 5); + EXPECT_EQ(memcmp(v, "hello", 5), 0); + } + + EXPECT_EQ(stream.remaining(), 0); + + // At the end, so should throw. + uint8_t v; + EXPECT_THROW(stream >> v, std::out_of_range); +} + +TEST(StreamTest, InputTestNoExpansion) +{ + std::vector<uint8_t> data(256, 0); + Stream stream(data); + uint8_t v1 = 0x11; + uint16_t v2 = 0x2233; + uint64_t v3 = 0x445566778899AABB; + uint32_t v4 = 0xCCDDEEFF; + + stream << v3 << v2 << v4 << v1; + + uint8_t e1; + uint16_t e2; + uint64_t e3; + uint32_t e4; + + stream.offset(0); + stream >> e3 >> e2 >> e4 >> e1; + + EXPECT_EQ(v1, e1); + EXPECT_EQ(v2, e2); + EXPECT_EQ(v3, e3); + EXPECT_EQ(v4, e4); +} + +TEST(StreamTest, InputTestExpansion) +{ + // The stream will expand the underlying vector + std::vector<uint8_t> data; + Stream stream(data); + + uint32_t v1 = 0xAABBCCDD; + stream << v1; + + stream.offset(0); + uint32_t e1; + stream >> e1; + EXPECT_EQ(data.size(), 4); + EXPECT_EQ(v1, e1); + + stream.offset(2); + + uint64_t v2 = 0x0102030405060708; + stream << v2; + + EXPECT_EQ(data.size(), 10); + uint64_t e2; + stream.offset(2); + stream >> e2; + + EXPECT_EQ(v2, e2); + + auto origSize = data.size(); + uint8_t v3 = 0xCC; + stream << v3; + + EXPECT_EQ(origSize + 1, data.size()); + stream.offset(stream.offset() - 1); + uint8_t e3; + stream >> e3; + EXPECT_EQ(v3, e3); +} + +TEST(StreamTest, ReadWriteTest) +{ + std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, + 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc}; + Stream stream{data}; + uint8_t buf[data.size()]; + + stream.read(buf, data.size()); + + for (size_t i = 0; i < data.size(); i++) + { + EXPECT_EQ(buf[i], data[i]); + + // for the next test + buf[i] = 0x20 + i; + } + + stream.offset(6); + stream.write(buf, 6); + for (size_t i = 0; i < 6; i++) + { + EXPECT_EQ(buf[i], data[i + 6]); + } +} + +TEST(StreamTest, TestOffsets) +{ + std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + Stream stream{data, 3}; + + { + uint8_t v; + stream >> v; + EXPECT_EQ(v, 0x44); + EXPECT_EQ(stream.offset(), 4); + } + + stream.offset(6); + + { + uint8_t v; + stream >> v; + EXPECT_EQ(v, 0x77); + EXPECT_EQ(stream.offset(), 7); + EXPECT_EQ(stream.remaining(), 0); + } + + EXPECT_THROW(stream.offset(100), std::out_of_range); +} + +TEST(StreamTest, TestVectorInsertExtract) +{ + std::vector<uint8_t> toInsert{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77}; + std::vector<uint8_t> data; + + // Insert + Stream stream{data}; + stream << toInsert; + EXPECT_EQ(data, toInsert); + + // Extract + std::vector<uint8_t> toExtract; + toExtract.resize(toInsert.size()); + stream.offset(0); + stream >> toExtract; + + EXPECT_EQ(data, toExtract); + + // Go off the end + EXPECT_THROW(stream >> toExtract, std::out_of_range); +} diff --git a/test/openpower-pels/user_data_test.cpp b/test/openpower-pels/user_data_test.cpp new file mode 100644 index 0000000..a957a4d --- /dev/null +++ b/test/openpower-pels/user_data_test.cpp @@ -0,0 +1,106 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "extensions/openpower-pels/user_data.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +std::vector<uint8_t> udSectionData{0x55, 0x44, // ID 'UD' + 0x00, 0x10, // Size + 0x01, 0x02, // version, subtype + 0x03, 0x04, // comp ID + + // Data + 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18}; + +TEST(UserDataTest, UnflattenFlattenTest) +{ + Stream stream(udSectionData); + UserData ud(stream); + + EXPECT_TRUE(ud.valid()); + EXPECT_EQ(ud.header().id, 0x5544); + EXPECT_EQ(ud.header().size, udSectionData.size()); + EXPECT_EQ(ud.header().version, 0x01); + EXPECT_EQ(ud.header().subType, 0x02); + EXPECT_EQ(ud.header().componentID, 0x0304); + + const auto& data = ud.data(); + + // The data itself starts after the header + EXPECT_EQ(data.size(), udSectionData.size() - 8); + + for (size_t i = 0; i < data.size(); i++) + { + EXPECT_EQ(data[i], udSectionData[i + 8]); + } + + // Now flatten + std::vector<uint8_t> newData; + Stream newStream(newData); + ud.flatten(newStream); + + EXPECT_EQ(udSectionData, newData); +} + +TEST(UserDataTest, BadDataTest) +{ + auto data = udSectionData; + data.resize(4); + + Stream stream(data); + UserData ud(stream); + EXPECT_FALSE(ud.valid()); +} + +TEST(UserDataTest, BadSizeFieldTest) +{ + auto data = udSectionData; + + { + data[3] = 0xFF; // Set the size field too large + Stream stream(data); + UserData ud(stream); + EXPECT_FALSE(ud.valid()); + } + { + data[3] = 0x7; // Set the size field too small + Stream stream(data); + UserData ud(stream); + EXPECT_FALSE(ud.valid()); + } +} + +TEST(UserDataTest, ConstructorTest) +{ + std::vector<uint8_t> data{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + + UserData ud(0x1112, 0x42, 0x01, data); + EXPECT_TRUE(ud.valid()); + + EXPECT_EQ(ud.header().id, 0x5544); + EXPECT_EQ(ud.header().size, 16); + EXPECT_EQ(ud.header().version, 0x01); + EXPECT_EQ(ud.header().subType, 0x42); + EXPECT_EQ(ud.header().componentID, 0x1112); + + const auto& d = ud.data(); + + EXPECT_EQ(d, data); +} diff --git a/test/openpower-pels/user_header_test.cpp b/test/openpower-pels/user_header_test.cpp new file mode 100644 index 0000000..7b8842c --- /dev/null +++ b/test/openpower-pels/user_header_test.cpp @@ -0,0 +1,163 @@ +/** + * Copyright © 2019 IBM Corporation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "elog_entry.hpp" +#include "extensions/openpower-pels/pel_types.hpp" +#include "extensions/openpower-pels/private_header.hpp" +#include "extensions/openpower-pels/user_header.hpp" +#include "pel_utils.hpp" + +#include <gtest/gtest.h> + +using namespace openpower::pels; + +TEST(UserHeaderTest, SizeTest) +{ + EXPECT_EQ(UserHeader::flattenedSize(), 24); +} + +TEST(UserHeaderTest, UnflattenFlattenTest) +{ + auto data = pelDataFactory(TestPELType::userHeaderSection); + + Stream stream(data); + UserHeader uh(stream); + EXPECT_EQ(uh.valid(), true); + + EXPECT_EQ(uh.header().id, 0x5548); + EXPECT_EQ(uh.header().size, UserHeader::flattenedSize()); + EXPECT_EQ(uh.header().version, 0x01); + EXPECT_EQ(uh.header().subType, 0x0A); + EXPECT_EQ(uh.header().componentID, 0x0B0C); + + EXPECT_EQ(uh.subsystem(), 0x10); + EXPECT_EQ(uh.scope(), 0x04); + EXPECT_EQ(uh.severity(), 0x20); + EXPECT_EQ(uh.eventType(), 0x00); + EXPECT_EQ(uh.problemDomain(), 0x03); + EXPECT_EQ(uh.problemVector(), 0x04); + EXPECT_EQ(uh.actionFlags(), 0x80C0); + + // Now flatten into a vector and check that this vector + // matches the original one. + std::vector<uint8_t> newData; + Stream newStream(newData); + + uh.flatten(newStream); + EXPECT_EQ(data, newData); +} + +TEST(UserHeaderTest, ShortDataTest) +{ + auto data = pelDataFactory(TestPELType::userHeaderSection); + data.resize(data.size() - 1); + + Stream stream(data); + UserHeader uh(stream); + + EXPECT_EQ(uh.valid(), false); +} + +TEST(UserHeaderTest, CorruptDataTest1) +{ + auto data = pelDataFactory(TestPELType::userHeaderSection); + data.resize(data.size() - 1); + + data.at(0) = 0; // corrupt the section ID + + Stream stream(data); + UserHeader uh(stream); + + EXPECT_EQ(uh.valid(), false); +} + +TEST(UserHeaderTest, CorruptDataTest2) +{ + auto data = pelDataFactory(TestPELType::userHeaderSection); + + data.at(4) = 0x22; // corrupt the version + + Stream stream(data); + UserHeader uh(stream); + + EXPECT_EQ(uh.valid(), false); +} + +// Construct the User Header from the message registry +TEST(UserHeaderTest, ConstructionTest) +{ + using namespace openpower::pels::message; + Entry regEntry; + + regEntry.name = "test"; + regEntry.subsystem = 5; + regEntry.severity = 0x40; + regEntry.actionFlags = 0xC000; + regEntry.eventType = 1; + regEntry.eventScope = 2; + + UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error); + + ASSERT_TRUE(uh.valid()); + EXPECT_EQ(uh.header().id, 0x5548); + EXPECT_EQ(uh.header().size, UserHeader::flattenedSize()); + EXPECT_EQ(uh.header().version, 0x01); + EXPECT_EQ(uh.header().subType, 0x00); + EXPECT_EQ(uh.header().componentID, + static_cast<uint16_t>(ComponentID::phosphorLogging)); + + ASSERT_EQ(uh.subsystem(), 5); + ASSERT_EQ(uh.severity(), 0x40); + ASSERT_EQ(uh.eventType(), 1); + ASSERT_EQ(uh.scope(), 2); + ASSERT_EQ(uh.problemDomain(), 0); + ASSERT_EQ(uh.problemVector(), 0); + ASSERT_EQ(uh.actionFlags(), 0xC000); +} + +// Test that the severity comes from the event log if not +// in the message registry +TEST(UserHeaderTest, UseEventLogSevTest) +{ + using namespace openpower::pels::message; + Entry regEntry; + + regEntry.name = "test"; + regEntry.subsystem = 5; + regEntry.actionFlags = 0xC000; + regEntry.eventType = 1; + regEntry.eventScope = 2; + // Leave off severity + + UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error); + ASSERT_EQ(uh.severity(), 0x40); +} + +// Test that the optional event type & scope fields work +TEST(UserHeaderTest, DefaultEventTypeScopeTest) +{ + using namespace openpower::pels::message; + Entry regEntry; + + regEntry.name = "test"; + regEntry.subsystem = 5; + regEntry.severity = 0x40; + regEntry.actionFlags = 0xC000; + + UserHeader uh(regEntry, phosphor::logging::Entry::Level::Error); + + ASSERT_EQ(uh.eventType(), 0); + ASSERT_EQ(uh.scope(), 0x03); +} diff --git a/test/remote_logging_test_config.cpp b/test/remote_logging_test_config.cpp index b34a43f..cba54ea 100644 --- a/test/remote_logging_test_config.cpp +++ b/test/remote_logging_test_config.cpp @@ -3,19 +3,6 @@ #include <fstream> #include <string> -#if __has_include(<filesystem>) -#include <filesystem> -#elif __has_include(<experimental/filesystem>) -#include <experimental/filesystem> -namespace std -{ -// splice experimental::filesystem into std -namespace filesystem = std::experimental::filesystem; -} // namespace std -#else -#error filesystem not available -#endif - namespace phosphor { namespace logging @@ -34,13 +21,13 @@ std::string getConfig(const char* filePath) TEST_F(TestRemoteLogging, testOnlyAddress) { config->address("1.1.1.1"); - EXPECT_EQ(fs::exists(configFilePath.c_str()), false); + EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~"); } TEST_F(TestRemoteLogging, testOnlyPort) { config->port(100); - EXPECT_EQ(fs::exists(configFilePath.c_str()), false); + EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~"); } TEST_F(TestRemoteLogging, testGoodConfig) @@ -56,7 +43,7 @@ TEST_F(TestRemoteLogging, testClearAddress) config->port(100); EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* @@1.1.1.1:100"); config->address(""); - EXPECT_EQ(fs::exists(configFilePath.c_str()), false); + EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~"); } TEST_F(TestRemoteLogging, testClearPort) @@ -65,7 +52,7 @@ TEST_F(TestRemoteLogging, testClearPort) config->port(100); EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* @@1.1.1.1:100"); config->port(0); - EXPECT_EQ(fs::exists(configFilePath.c_str()), false); + EXPECT_EQ(getConfig(configFilePath.c_str()), "*.* ~"); } } // namespace test |