From f0ed80f6fc3aff12636f5317f2daf6acf5cb70e4 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Wed, 27 Aug 2025 13:19:42 +0200 Subject: [PATCH 1/2] Added time based sys variables Ticket: CFE-4000 Signed-off-by: Victor Moene --- libenv/time_classes.c | 61 ++++++++++++++++++++++------------ libenv/time_classes.h | 2 +- tests/unit/eval_context_test.c | 13 +++++--- 3 files changed, 48 insertions(+), 28 deletions(-) diff --git a/libenv/time_classes.c b/libenv/time_classes.c index 7f127e6cf0..db57b10973 100644 --- a/libenv/time_classes.c +++ b/libenv/time_classes.c @@ -24,6 +24,7 @@ #include #include +#include // xvasprintf static void RemoveTimeClass(EvalContext *ctx, const char* tags) { @@ -44,9 +45,26 @@ static void RemoveTimeClass(EvalContext *ctx, const char* tags) RlistDestroy(tags_rlist); } -StringSet *GetTimeClasses(time_t time) { +static void InsertTimeClassF(StringMap *classes, const char *prefix, const char *type, const char *fmt, ...) +{ + char *key; + xasprintf(&key, "time_based_%s%s", prefix, type); + + va_list args; + va_start(args, fmt); + + char *value; + NDEBUG_UNUSED int ret = xvasprintf(&value, fmt, args); + assert(ret >= 0); + + va_end(args); + StringMapInsert(classes, key, value); +} + +StringMap *GetTimeClasses(time_t time) { // The first element is the local timezone const char* tz_prefix[2] = { "", "GMT_" }; + const char* tz_var_prefix[2] = { "", "gmt_" }; const char* tz_function[2] = { "localtime_r", "gmtime_r" }; struct tm tz_parsed_time[2]; const struct tm* tz_tm[2] = { @@ -54,7 +72,7 @@ StringSet *GetTimeClasses(time_t time) { gmtime_r(&time, &(tz_parsed_time[1])) }; - StringSet *classes = StringSetNew(); + StringMap *classes = StringMapNew(); for (int tz = 0; tz < 2; tz++) { @@ -67,16 +85,14 @@ StringSet *GetTimeClasses(time_t time) { } /* Lifecycle */ - - StringSetAddF(classes, "%sLcycle_%d", tz_prefix[tz], ((tz_parsed_time[tz].tm_year + 1900) % 3)); + InsertTimeClassF(classes, tz_var_prefix[tz], "lcycle", "%sLcycle_%d", tz_prefix[tz], ((tz_parsed_time[tz].tm_year + 1900) % 3)); /* Year */ - - StringSetAddF(classes, "%sYr%04d", tz_prefix[tz], tz_parsed_time[tz].tm_year + 1900); + InsertTimeClassF(classes, tz_var_prefix[tz], "yr", "%sYr%04d", tz_prefix[tz], tz_parsed_time[tz].tm_year + 1900); /* Month */ - StringSetAddF(classes, "%s%s", tz_prefix[tz], MONTH_TEXT[tz_parsed_time[tz].tm_mon]); + InsertTimeClassF(classes, tz_var_prefix[tz], "month", "%s%s", tz_prefix[tz], MONTH_TEXT[tz_parsed_time[tz].tm_mon]); /* Day of week */ @@ -85,36 +101,36 @@ StringSet *GetTimeClasses(time_t time) { ... Sunday is 0 in tm_wday, 6 in DAY_TEXT */ day_text_index = (tz_parsed_time[tz].tm_wday + 6) % 7; - StringSetAddF(classes, "%s%s", tz_prefix[tz], DAY_TEXT[day_text_index]); + InsertTimeClassF(classes, tz_var_prefix[tz], "dow", "%s%s", tz_prefix[tz], DAY_TEXT[day_text_index]); /* Day */ - StringSetAddF(classes, "%sDay%d", tz_prefix[tz], tz_parsed_time[tz].tm_mday); + InsertTimeClassF(classes, tz_var_prefix[tz], "dom", "%sDay%d", tz_prefix[tz], tz_parsed_time[tz].tm_mday); /* Shift */ - StringSetAddF(classes, "%s%s", tz_prefix[tz], SHIFT_TEXT[tz_parsed_time[tz].tm_hour / 6]); + InsertTimeClassF(classes, tz_var_prefix[tz], "pod", "%s%s", tz_prefix[tz], SHIFT_TEXT[tz_parsed_time[tz].tm_hour / 6]); /* Hour */ - StringSetAddF(classes, "%sHr%02d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); - StringSetAddF(classes, "%sHr%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); + InsertTimeClassF(classes, tz_var_prefix[tz], "hr", "%sHr%02d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); + InsertTimeClassF(classes, tz_var_prefix[tz], "hr_2", "%sHr%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour); /* Quarter */ quarter = tz_parsed_time[tz].tm_min / 15 + 1; - StringSetAddF(classes, "%sQ%d", tz_prefix[tz], quarter); - StringSetAddF(classes, "%sHr%02d_Q%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour, quarter); + InsertTimeClassF(classes, tz_var_prefix[tz], "qoh", "%sQ%d", tz_prefix[tz], quarter); + InsertTimeClassF(classes, tz_var_prefix[tz], "hr_qoh", "%sHr%02d_Q%d", tz_prefix[tz], tz_parsed_time[tz].tm_hour, quarter); /* Minute */ - StringSetAddF(classes, "%sMin%02d", tz_prefix[tz], tz_parsed_time[tz].tm_min); + InsertTimeClassF(classes, tz_var_prefix[tz], "min", "%sMin%02d", tz_prefix[tz], tz_parsed_time[tz].tm_min); interval_start = (tz_parsed_time[tz].tm_min / 5) * 5; interval_end = (interval_start + 5) % 60; - StringSetAddF(classes, "%sMin%02d_%02d", tz_prefix[tz], interval_start, interval_end); + InsertTimeClassF(classes, tz_var_prefix[tz], "min_span_5", "%sMin%02d_%02d", tz_prefix[tz], interval_start, interval_end); } return classes; @@ -122,18 +138,19 @@ StringSet *GetTimeClasses(time_t time) { static void AddTimeClass(EvalContext *ctx, time_t time, const char* tags) { - StringSet *time_classes = GetTimeClasses(time); + StringMap *time_classes = GetTimeClasses(time); if (time_classes == NULL) { return; } - StringSetIterator iter = StringSetIteratorInit(time_classes); - const char *time_class = NULL; - while ((time_class = StringSetIteratorNext(&iter)) != NULL) { - EvalContextClassPutHard(ctx, time_class, tags); + StringMapIterator iter = StringMapIteratorInit(time_classes); + MapKeyValue *item; + while ((item = StringMapIteratorNext(&iter)) != NULL) { + EvalContextClassPutHard(ctx, item->value, tags); + EvalContextVariablePutSpecial(ctx, SPECIAL_SCOPE_SYS, item->key, item->value, CF_DATA_TYPE_STRING, "noreport"); } - StringSetDestroy(time_classes); + StringMapDestroy(time_classes); } void UpdateTimeClasses(EvalContext *ctx, time_t t) diff --git a/libenv/time_classes.h b/libenv/time_classes.h index 149188bc42..f1ce9313ad 100644 --- a/libenv/time_classes.h +++ b/libenv/time_classes.h @@ -39,7 +39,7 @@ * 'GMT_Yr2024', 'Hr15', 'Hr15_Q3', 'Lcycle_2', 'May', 'Min30_35', * 'Min33', 'Q3', 'Wednesday', 'Yr2024' } */ -StringSet *GetTimeClasses(time_t time); +StringMap *GetTimeClasses(time_t time); void UpdateTimeClasses(EvalContext *ctx, time_t t); diff --git a/tests/unit/eval_context_test.c b/tests/unit/eval_context_test.c index 7eee8318d8..712bc3c67f 100644 --- a/tests/unit/eval_context_test.c +++ b/tests/unit/eval_context_test.c @@ -133,11 +133,13 @@ void test_eval_with_token_from_list(void) * 'GMT_Yr2024' */ const time_t timestamp = 1716989621; - StringSet *time_classes = GetTimeClasses(timestamp); - StringSetIterator iter = StringSetIteratorInit(time_classes); - const char *time_class = NULL; - while((time_class = StringSetIteratorNext(&iter)) != NULL) { - printf("Time class: %s\n", time_class); + StringSet *time_classes = StringSetNew(); + + StringMap *time_classes_map = GetTimeClasses(timestamp); + StringMapIterator iter = StringMapIteratorInit(time_classes_map); + MapKeyValue *item; + while((item = StringMapIteratorNext(&iter)) != NULL) { + StringSetAdd(time_classes, SafeStringDuplicate(item->value)); } assert_true(EvalWithTokenFromList("GMT_Wednesday", time_classes)); @@ -146,6 +148,7 @@ void test_eval_with_token_from_list(void) assert_true(EvalWithTokenFromList("GMT_Monday|GMT_Wednesday", time_classes)); assert_true(EvalWithTokenFromList("!GMT_Monday", time_classes)); + StringMapDestroy(time_classes_map); StringSetDestroy(time_classes); } From 86e009e92d11522d327967b05bd491a87a935888 Mon Sep 17 00:00:00 2001 From: Victor Moene Date: Wed, 27 Aug 2025 17:29:32 +0200 Subject: [PATCH 2/2] Added tests for sys.time_based* variables Signed-off-by: Victor Moene --- .../01_vars/01_basic/time_based_vars.cf | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tests/acceptance/01_vars/01_basic/time_based_vars.cf diff --git a/tests/acceptance/01_vars/01_basic/time_based_vars.cf b/tests/acceptance/01_vars/01_basic/time_based_vars.cf new file mode 100644 index 0000000000..91dfca1837 --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/time_based_vars.cf @@ -0,0 +1,37 @@ +body common control +{ + bundlesequence => { "test" }; +} + + +bundle agent test +{ + vars: + "time_based_var_names" slist => variablesmatching(".*sys.time_based.*"); + "time_based_vars" slist => maplist("$($(this))", "@(time_based_var_names)"); + + classes: + "year_ok" expression => reglist("@(time_based_vars)", "^Yr\d{4}$"); + "month_ok" expression => reglist("@(time_based_vars)", "^(January|February|March|April|May|June|July|August|September|October|November|December)$"); + "day_ok" expression => reglist("@(time_based_vars)", "^Day([1-9]|[1-2][0-9]|3[0-1])$"); + "weekday_ok" expression => reglist("@(time_based_vars)", "^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday)$"); + "shift_ok" expression => reglist("@(time_based_vars)", "^(Night|Morning|Afternoon|Evening)$"); + "hour_2_ok" expression => reglist("@(time_based_vars)", "^Hr(0[1-9]|1[0-9]|2[0-4])$"); + "hour_ok" expression => reglist("@(time_based_vars)", "^Hr([1-9]|1[0-9]|2[0-4])$"); + "quarter_ok" expression => reglist("@(time_based_vars)", "^Q[1-4]$"); + "minute_ok" expression => reglist("@(time_based_vars)", "^Min([0-5][0-9]|60)$"); + "minute_span_5_ok" expression => reglist("@(time_based_vars)", "^Min([0-5][0-9]|60)_(05|[0-5][05]|60)$"); + "hour_2_quarter_ok" expression => reglist("@(time_based_vars)", "^Hr(0[1-9]|1[0-9]|2[0-4])_Q[1-4]$"); + + # Testing for one GMT + "hour_2_gmt_ok" expression => reglist("@(time_based_vars)", "^GMT_Hr(0[1-9]|1[0-9]|2[0-4])$"); + + "ok" expression => and("year_ok", "month_ok", "day_ok", "weekday_ok", "shift_ok", "hour_2_ok", "hour_ok", + "quarter_ok", "minute_ok", "minute_span_5_ok", "hour_2_quarter_ok", "hour_2_gmt_ok"); + + reports: + ok:: + "$(this.promise_filename) Pass"; + !ok:: + "$(this.promise_filename) FAIL"; +}