/* cpputest-client - CppUnit libnutclient active test Module for NUT `cpputest` runner, to check client-server interactions as part of NIT (NUT Integration Testing) suite and similar scenarios. This is an "active" NUT client talking to an `upsd` on $NUT_PORT, as opposed to nutclienttest.cpp which unit-tests the class API etc. in isolated-binary fashion. Copyright (C) 2022 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "common.h" /* Current CPPUnit offends the honor of C++98 */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) #pragma GCC diagnostic push # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS # pragma GCC diagnostic ignored "-Wglobal-constructors" # endif # ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS # pragma GCC diagnostic ignored "-Wexit-time-destructors" # endif #endif #include #include #include #include #include namespace nut { class NutActiveClientTest : public CppUnit::TestFixture { /* Note: is "friend" of nut::TcpClient class * to test a few of its protected methods */ CPPUNIT_TEST_SUITE( NutActiveClientTest ); CPPUNIT_TEST( test_query_ver ); CPPUNIT_TEST( test_list_ups ); CPPUNIT_TEST( test_auth_user ); CPPUNIT_TEST( test_auth_primary ); CPPUNIT_TEST_SUITE_END(); private: /* Fed by caller via envvars: */ uint16_t NUT_PORT = 0; std::string NUT_USER = ""; std::string NUT_PASS = ""; std::string NUT_PRIMARY_DEVICE = ""; std::string NUT_SETVAR_DEVICE = ""; public: void setUp() override; void tearDown() override; void test_query_ver(); void test_list_ups(); void test_auth_user(); void test_auth_primary(); }; // Registers the fixture into the 'registry' CPPUNIT_TEST_SUITE_REGISTRATION( NutActiveClientTest ); } // namespace nut {} #ifndef _NUTCLIENTTEST_BUILD # define _NUTCLIENTTEST_BUILD 1 #endif #include "../clients/nutclient.h" #include "../clients/nutclientmem.h" namespace nut { extern "C" { strarr stringset_to_strarr(const std::set& strset); strarr stringvector_to_strarr(const std::vector& strset); } // extern "C" void NutActiveClientTest::setUp() { /* NUT_PORT etc. are provided by external test suite driver */ char * s; s = std::getenv("NUT_PORT"); if (s) { long l = atol(s); if (l < 1 || l > 65535) { throw std::runtime_error("NUT_PORT specified by caller is out of range"); } NUT_PORT = static_cast(l); } else { throw std::runtime_error("NUT_PORT not specified by caller, NIT should call this test"); } s = std::getenv("NUT_USER"); if (s) { NUT_USER = s; } // else stays empty s = std::getenv("NUT_PASS"); if (s) { NUT_PASS = s; } // else stays empty s = std::getenv("NUT_PRIMARY_DEVICE"); if (s) { NUT_PRIMARY_DEVICE = s; } // else stays empty s = std::getenv("NUT_SETVAR_DEVICE"); if (s) { NUT_SETVAR_DEVICE = s; } // else stays empty } void NutActiveClientTest::tearDown() { } void NutActiveClientTest::test_query_ver() { nut::TcpClient c("localhost", NUT_PORT); std::string s; std::cerr << "[D] C++ NUT Client lib test running against Data Server at: " << c.getHost() << ':' << c.getPort() << std::endl; CPPUNIT_ASSERT_MESSAGE( "TcpClient is not connected after constructor", c.isConnected()); /* Note: generic client code can not use protected methods * like low-level sendQuery(), list(), get() and some more, * but this NutActiveClientTest is a friend of TcpClient: */ s = c.sendQuery("VER"); std::cerr << "[D] Got Data Server VER: " << s << std::endl; try { s = c.sendQuery("PROTVER"); std::cerr << "[D] Got PROTVER: " << s << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Did not get PROTVER: " << ex.what() << std::endl; } try { s = c.sendQuery("NETVER"); std::cerr << "[D] Got NETVER (obsolete): " << s << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Did not get NETVER (obsolete): " << ex.what() << std::endl; } try { c.logout(); } catch(nut::NutException& ex) { std::cerr << "[D] Could not get LOGOUT: " << ex.what() << std::endl; } try { c.disconnect(); } catch(nut::NutException& ex) { /* NUT_UNUSED_VARIABLE(ex); */ std::cerr << "[D] Could not get disconnect(): " << ex.what() << std::endl; } } void NutActiveClientTest::test_list_ups() { nut::TcpClient c("localhost", NUT_PORT); std::set devs; bool noException = true; try { devs = c.getDeviceNames(); std::cerr << "[D] Got device list (" << devs.size() << "): ["; for (std::set::iterator it = devs.begin(); it != devs.end(); it++ ) { if (it != devs.begin()) { std::cerr << ", "; } std::cerr << '"' << *it << '"'; } std::cerr << "]" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not device list: " << ex.what() << std::endl; noException = false; } c.logout(); c.disconnect(); CPPUNIT_ASSERT_MESSAGE( "Failed to list UPS with TcpClient: threw NutException", noException); } void NutActiveClientTest::test_auth_user() { if (NUT_USER.empty()) { std::cerr << "[D] SKIPPING test_auth_user()" << std::endl; return; } nut::TcpClient c("localhost", NUT_PORT); bool noException = true; try { c.authenticate(NUT_USER, NUT_PASS); std::cerr << "[D] Authenticated without exceptions" << std::endl; /* Note: no high hopes here, credentials are checked by server * when running critical commands, not at auth request itself */ } catch(nut::NutException& ex) { std::cerr << "[D] Could not authenticate as a simple user: " << ex.what() << std::endl; noException = false; } if (!NUT_SETVAR_DEVICE.empty()) { try { TrackingResult tres; TrackingID tid; int i; std::string nutVar = "ups.status"; /* Has a risk of flip-flop with NIT dummy setup */ std::string s1 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; std::string sTest = s1 + "-test"; std::cerr << "[D] Got initial device '" << NUT_SETVAR_DEVICE << "' variable '" << nutVar << "' value: " << s1 << std::endl; CPPUNIT_ASSERT_MESSAGE( "Did not expect empty value here", !s1.empty()); tid = c.setDeviceVariable(NUT_SETVAR_DEVICE, nutVar, sTest); while ( (tres = c.getTrackingResult(tid)) == PENDING) { usleep(100); } if (tres != SUCCESS) { std::cerr << "[D] Failed to set device variable: " << "tracking result is " << tres << std::endl; noException = false; } /* Check what we got after set */ /* Note that above we told the server to tell the driver * to set a dstate entry; below we ask the server to ask * the driver and relay the answer to us. The dummy-ups * driver may also be in a sleeping state between cycles. * Data propagation may be not instantaneous, so we loop * for a while to see the expected value (or give up). */ std::string s2; for (i = 0; i < 100 ; i++) { s2 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; if (s2 == sTest) break; usleep(100000); } std::cerr << "[D] Read back: " << s2 << " after " << (100 * i) << "msec" << std::endl; /* Fix it back */ tid = c.setDeviceVariable(NUT_SETVAR_DEVICE, nutVar, s1); while ( (tres = c.getTrackingResult(tid)) == PENDING) { usleep(100); } if (tres != SUCCESS) { std::cerr << "[D] Failed to set device variable: " << "tracking result is " << tres << std::endl; noException = false; } std::string s3; for (i = 0; i < 100 ; i++) { s3 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; if (s3 == s1) break; usleep(100000); } std::cerr << "[D] Read back: " << s3 << " after " << (100 * i) << "msec" << std::endl; if (s3 != s1) { std::cerr << "[D] Final device variable value '" << s3 << "' differs from original '" << s1 << "'" << std::endl; noException = false; } if (s2 == s1) { std::cerr << "[D] Tweaked device variable value '" << s2 << "' does not differ from original '" << s1 << "'" << std::endl; noException = false; } if (noException) { std::cerr << "[D] Tweaked device variable value OK" << std::endl; } } catch(nut::NutException& ex) { std::cerr << "[D] Failed to set device variable: " << ex.what() << std::endl; noException = false; } } else { std::cerr << "[D] SKIPPING test_auth_user() active test " << "(got no NUT_SETVAR_DEVICE to poke)" << std::endl; } c.logout(); c.disconnect(); CPPUNIT_ASSERT_MESSAGE( "Failed to auth as user with TcpClient or tweak device variable", noException); } void NutActiveClientTest::test_auth_primary() { if (NUT_USER.empty() || NUT_PRIMARY_DEVICE.empty()) { std::cerr << "[D] SKIPPING test_auth_primary()" << std::endl; return; } nut::TcpClient c("localhost", NUT_PORT); bool noException = true; try { c.authenticate(NUT_USER, NUT_PASS); std::cerr << "[D] Authenticated without exceptions" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not authenticate as an upsmon primary user: " << ex.what() << std::endl; noException = false; } try { Device d = c.getDevice(NUT_PRIMARY_DEVICE); bool gotPrimary = false; bool gotMaster = false; try { c.deviceMaster(NUT_PRIMARY_DEVICE); gotMaster = true; std::cerr << "[D] Elevated as MASTER without exceptions" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not elevate as MASTER for " << "NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE << ": " << ex.what() << std::endl; } try { c.devicePrimary(NUT_PRIMARY_DEVICE); gotPrimary = true; std::cerr << "[D] Elevated as PRIMARY without exceptions" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not elevate as PRIMARY for " << "NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE << ": " << ex.what() << std::endl; } if (!gotMaster && !gotPrimary) noException = false; } catch(nut::NutException& ex) { std::cerr << "[D] NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE << " not found on Data Server: " << ex.what() << std::endl; noException = false; } c.logout(); c.disconnect(); CPPUNIT_ASSERT_MESSAGE( "Failed to auth as user with TcpClient: threw NutException", noException); } } // namespace nut {} #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) #pragma GCC diagnostic pop #endif