diff --git a/NEWS.adoc b/NEWS.adoc index bad715de33..43965430d3 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -479,8 +479,8 @@ relocated into new `shutdown.default` INSTCMD definitions. [#2670] * custom `distcheck-something` targets did not inherit `DISTCHECK_FLAGS` properly. [#2541] * added `status_get()` in NUT driver state API, to check if a status - token string had been set recently, and to avoid duplicate settings. - [PR #2565] + token string had been set recently, and to avoid duplicate settings; + fixed `status_set()` for multi-token arguments. [PR #2565, issue #2708] * local socket/pipe protocol introduced a `LOGOUT` command for cleaner disconnection handling. [#2572] * codebase adapted to the liking of `clang-18` and newer revisions of diff --git a/appveyor.yml b/appveyor.yml index b6d1a32c56..f37b854ea2 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -110,7 +110,7 @@ after_test: set MSYSTEM=MINGW64 REM Oh the joys of shell scripting with strings passed through CMD: REM Note: currently Python installation path with MSYS is buggy [#1584] - C:\msys64\usr\bin\bash -lc 'date -u; set -e ; if ! rm -rf ".inst" ; then echo "WARNING: Failed to clean away .inst" ; fi ; PATH="/mingw64/lib/ccache/bin:/mingw64/bin:$PATH" make -s install-win-bundle DESTDIR="`pwd`/.inst/NUT-for-Windows-x86_64-SNAPSHOT-%APPVEYOR_BUILD_VERSION%" ; ln -fs "NUT-for-Windows-x86_64-SNAPSHOT-%APPVEYOR_BUILD_VERSION%" ./.inst/NUT-for-Windows-x86_64-SNAPSHOT ; ( cd .inst/NUT-for-Windows-x86_64-SNAPSHOT ; find . -ls ; ) ; date -u' + C:\msys64\usr\bin\bash -lc 'date -u; set -e ; if ! rm -rf ".inst" ; then echo "WARNING: Failed to clean away .inst" ; fi ; PATH="/mingw64/lib/ccache/bin:/mingw64/bin:$PATH" make -s install-win-bundle DESTDIR="`pwd`/.inst/NUT-for-Windows-x86_64-SNAPSHOT-%APPVEYOR_BUILD_VERSION%" ; rm -rf ./.inst/NUT-for-Windows-x86_64-SNAPSHOT || true ; ln -fs "NUT-for-Windows-x86_64-SNAPSHOT-%APPVEYOR_BUILD_VERSION%" ./.inst/NUT-for-Windows-x86_64-SNAPSHOT ; ( cd .inst/NUT-for-Windows-x86_64-SNAPSHOT ; find . -ls ; ) ; date -u' cd .inst 7z a ../NUT-for-Windows-x86_64-SNAPSHOT-%APPVEYOR_BUILD_VERSION%.7z NUT* - cmd: | diff --git a/docs/new-drivers.txt b/docs/new-drivers.txt index 3ca3ca9c76..be88bb3503 100644 --- a/docs/new-drivers.txt +++ b/docs/new-drivers.txt @@ -247,6 +247,10 @@ Possible values for status_set: BOOST -- UPS is boosting incoming voltage FSD -- Forced Shutdown (restricted use, see the note below) +Internally, an `ALARM` value would be added (typically as first in the list) +if the `ups.alarm` is currently not empty. For more details, see below in +`alarm_set()` description. + Anything else will not be recognized by the usual clients expecting a particular NUT standard release. New tokens may appear over time, but driver developers should coordinate with the nut-upsdev list before creating diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c index 8869840c6f..f9aeab7982 100644 --- a/drivers/bestfortress.c +++ b/drivers/bestfortress.c @@ -35,7 +35,7 @@ #endif #define DRIVER_NAME "Best Fortress UPS driver" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -401,13 +401,13 @@ void upsdrv_updateinfo(void) status_init(); if (low_batt) - status_set("LB "); + status_set("LB"); else if (trimming) status_set("TRIM"); else if (boosting) status_set("BOOST"); else - status_set(is_online ? (is_off ? "OFF " : "OL ") : "OB "); + status_set(is_online ? (is_off ? "OFF" : "OL") : "OB"); /* setinfo(INFO_STATUS, "%s%s", * (util < lownorm) ? "BOOST ", "", diff --git a/drivers/dstate.c b/drivers/dstate.c index f830ea8e6a..91baf88656 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -1644,11 +1644,16 @@ int status_get(const char *buf) s = strstr(status_buf, buf); buflen = strlen(buf); - /* not found */ - if (!s) +repeat: + /* not found or hit end of line */ + if (!s || !*s) return 0; - offset = status_buf - s; + offset = s - status_buf; +#if 0 + upsdebugx(3, "%s: '%s' in '%s': offset=%" PRIuSIZE" buflen=%" PRIuSIZE" s[buflen]='0x%2X'\n", + __func__, buf, status_buf, offset, buflen, s[buflen]); +#endif if (offset == 0 || status_buf[offset - 1] == ' ') { /* We have hit the start of token */ if (s[buflen] == '\0' || s[buflen] == ' ') { @@ -1658,12 +1663,46 @@ int status_get(const char *buf) } /* buf was a substring of some other token */ - return 0; + s = strstr(s + 1, buf); + goto repeat; } /* add a status element */ void status_set(const char *buf) { +#if 0 + upsdebugx(3, "%s: '%s'\n", __func__, buf); +#endif + if (strstr(buf, " ")) { + /* Recurse adding each sub-status one by one (avoid duplicates) + * We frown upon adding "A FEW TOKENS" at once, but in e.g. + * snmp-ups subdrivers with a mapping table this is not easily + * avoidable... + */ + char *tmp = xstrdup(buf), *p = tmp, *s = tmp; + while (*p) { + if (*p == ' ') { + *p = '\0'; + if (s != p) { + /* Only recurse to set non-trivial tokens */ + status_set(s); + } + p++; + s = p; /* Start of new word... or a consecutive space to ignore on next cycle */ + } else { + p++; + } + } + + if (s != p) { + /* Last valid token did end with (*p=='\0') */ + status_set(s); + } + + free(tmp); + return; + } + if (ignorelb && !strcasecmp(buf, "LB")) { upsdebugx(2, "%s: ignoring LB flag from device", __func__); return; diff --git a/drivers/liebert-gxe.c b/drivers/liebert-gxe.c index 36886c80f8..dbed3f149a 100644 --- a/drivers/liebert-gxe.c +++ b/drivers/liebert-gxe.c @@ -24,7 +24,7 @@ #include "ydn23.h" #define DRIVER_NAME "Liebert GXE Series UPS driver" -#define DRIVER_VERSION "0.02" +#define DRIVER_VERSION "0.03" #define PROBE_RETRIES 3 #define DEFAULT_STALE_RETRIES 3 @@ -173,9 +173,10 @@ static void upsdrv_updateinfo_onoff(void) status_set("OB"); else if (pwrval == 0x01) status_set("OL"); - else if (pwrval == 0x02) - status_set("OL BYPASS"); - else + else if (pwrval == 0x02) { + status_set("OL"); + status_set("BYPASS"); + } else upslogx(LOG_WARNING, "unknown ups state: %x %x", (unsigned int)pwrval, (unsigned int)rectval); diff --git a/drivers/main.c b/drivers/main.c index 0217e5438a..78339d7f92 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -47,9 +47,11 @@ const char *progname = NULL, *upsname = NULL, *device_name = NULL; /* may be set by the driver to wake up while in dstate_poll_fds */ TYPE_FD extrafd = ERROR_FD; -#ifdef WIN32 +#ifndef DRIVERS_MAIN_WITHOUT_MAIN +# ifdef WIN32 static HANDLE mutex = INVALID_HANDLE_VALUE; -#endif +# endif /* WIN32 */ +#endif /* DRIVERS_MAIN_WITHOUT_MAIN */ /* Set by INSTCMD to killpower or by running `drivername -k` to * help differentiate calls into upsdrv_shutdown() and further diff --git a/tests/.gitignore b/tests/.gitignore index ab9969c713..59edbae1af 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -4,6 +4,9 @@ /cppnit /cppnit.log /cppnit.trs +/driver_methods_utest +/driver_methods_utest.log +/driver_methods_utest.trs /gpiotest /gpiotest.log /gpiotest.trs diff --git a/tests/Makefile.am b/tests/Makefile.am index 38b4f71c99..5d30b5c8de 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -116,6 +116,11 @@ endif !WITH_GPIO CLEANFILES += generic_gpio_libgpiod.c generic_gpio_common.c EXTRA_DIST += generic_gpio_utest.h generic_gpio_test.txt +TESTS += driver_methods_utest +driver_methods_utest_SOURCES = driver_methods_utest.c +driver_methods_utest_LDADD = $(top_builddir)/drivers/libdummy_mockdrv.la +driver_methods_utest_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/tests -DDRIVERS_MAIN_WITHOUT_MAIN=1 + # Make sure out-of-dir dependencies exist (especially when dev-building parts): $(top_builddir)/drivers/libdummy_mockdrv.la \ $(top_builddir)/common/libnutconf.la \ diff --git a/tests/driver_methods_utest.c b/tests/driver_methods_utest.c new file mode 100644 index 0000000000..77da6c2221 --- /dev/null +++ b/tests/driver_methods_utest.c @@ -0,0 +1,134 @@ +/* driver_methods_utest.c - NUT driver code test tool + * + * Copyright (C) + * 2025 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 "config.h" +#include "main.h" +#include "dstate.h" +#include "attribute.h" +#include "nut_stdint.h" + +/* driver version */ +#define DRIVER_NAME "Mock driver for unit tests" +#define DRIVER_VERSION "0.01" + +/* driver description structure */ +upsdrv_info_t upsdrv_info = { + DRIVER_NAME, + DRIVER_VERSION, + "Jim Klimov ", + DRV_EXPERIMENTAL, + { NULL } +}; + +static int cases_passed = 0; +static int cases_failed = 0; + +static char * pass_fail[2] = {"pass", "fail"}; + +void upsdrv_cleanup(void) {} +void upsdrv_shutdown(void) {} + +static void report_pass(void) { + printf("%s", pass_fail[0]); + cases_passed++; +} + +static void report_fail(void) { + printf("%s", pass_fail[1]); + cases_failed++; +} + +static int report_0_means_pass(int i) { + if (i == 0) { + report_pass(); + } else { + report_fail(); + } + return i; +} +int main(int argc, char **argv) { + const char *valueStr = NULL; + + NUT_UNUSED_VARIABLE(argc); + NUT_UNUSED_VARIABLE(argv); + + cases_passed = 0; + cases_failed = 0; + + /* test case #1 */ + status_init(); + nut_debug_level = 6; + status_set(" OL "); + status_set("OL BOOST"); + status_set("OB "); + status_set(" BOOST"); + status_commit(); + valueStr = dstate_getinfo("ups.status"); + nut_debug_level = 0; + report_0_means_pass(strcmp(valueStr, "OL BOOST OB")); + printf(" test for ups.status: '%s'; any duplicates?\n", NUT_STRARG(valueStr)); + + /* test case #2, build on top of #1 */ + alarm_init(); + alarm_set("Test alarm 1"); + alarm_set("Test alarm 2"); + alarm_set("Test alarm 1"); + alarm_commit(); + /* Note: normally we re-init and re-set the values */ + status_commit(); + valueStr = dstate_getinfo("ups.status"); + report_0_means_pass(strcmp(valueStr, "ALARM OL BOOST OB")); + printf(" test for ups.status: '%s'; got alarm?\n", NUT_STRARG(valueStr)); + + /* test case #3, build on top of #2 */ + valueStr = dstate_getinfo("ups.alarm"); + /* NOTE: no dedup here! */ + report_0_means_pass(strcmp(valueStr, "Test alarm 1 Test alarm 2 Test alarm 1")); + printf(" test for ups.alarm: '%s'; got 3 alarms?\n", NUT_STRARG(valueStr)); + + /* test case #4, build on top of #1 and #2 */ + /* Note: normally we re-init and re-set the values */ + status_set("BOO"); + status_set("BOO"); + status_set("OST"); + status_set("OST"); + status_set("OOS"); + status_set("OOS"); + status_commit(); + valueStr = dstate_getinfo("ups.status"); + nut_debug_level = 0; + report_0_means_pass(strcmp(valueStr, "ALARM OL BOOST OB BOO OST OOS")); + printf(" test for ups.status: '%s'; any duplicates?\n", NUT_STRARG(valueStr)); + + /* finish */ + printf("test_rules completed. Total cases %d, passed %d, failed %d\n", + cases_passed+cases_failed, cases_passed, cases_failed); + + dstate_free(); + upsdrv_cleanup(); + + /* Return 0 (exit-code OK, boolean false) if no tests failed and some ran */ + if ( (cases_failed == 0) && (cases_passed > 0) ) + return 0; + + return 1; +}