| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "libnetdata/libnetdata.h" |
| 4 | #include "eval-internal.h" |
| 5 | |
| 6 | // Test case structure for hardcode variable tests |
| 7 | typedef struct { |
| 8 | const char *name; // Name/description of the test case |
| 9 | const char *expression; // Initial expression to parse |
| 10 | const char *variable; // Variable name to hardcode (NULL for testing NULL variable) |
| 11 | NETDATA_DOUBLE hardcode_value; // Value to hardcode (NAN for testing NaN) |
| 12 | const char *expected_source; // Expected expression source after hardcoding |
| 13 | NETDATA_DOUBLE expected_result; // Expected result after evaluation |
| 14 | int expected_error; // Expected error code (EVAL_ERROR_OK if no error) |
| 15 | } HardcodeTestCase; |
| 16 | |
| 17 | int eval_hardcode_unittest(void) { |
| 18 | printf("\n=== Running Tests for expression_hardcode_variable() ===\n"); |
| 19 | |
| 20 | // Define all test cases as data |
| 21 | HardcodeTestCase test_cases[] = { |
| 22 | // Basic variable replacement |
| 23 | { |
| 24 | "Basic variable", |
| 25 | "$test_var + 10", |
| 26 | "test_var", |
| 27 | 42.0, |
| 28 | "42 + 10", |
| 29 | 52.0, |
| 30 | EVAL_ERROR_OK |
| 31 | }, |
| 32 | // Variable with braces |
| 33 | { |
| 34 | "Variable with braces", |
| 35 | "${test_var} * 2", |
| 36 | "test_var", |
| 37 | 42.0, |
| 38 | "42 * 2", |
| 39 | 84.0, |
| 40 | EVAL_ERROR_OK |
| 41 | }, |
| 42 | // Multiple occurrences of same variable |
| 43 | { |
| 44 | "Multiple occurrences", |
| 45 | "$test_var + ${test_var} + $test_var + ${test_var} + $test_var + ${test_var}", |
| 46 | "test_var", |
| 47 | 42.0, |
| 48 | "42 + 42 + 42 + 42 + 42 + 42", |
| 49 | 252.0, |
| 50 | EVAL_ERROR_OK |
| 51 | }, |
| 52 | // Complex nested expression |
| 53 | { |
| 54 | "Complex expression", |
| 55 | "($test_var > 30) ? (${test_var} * 2) : ($test_var / 2)", |
| 56 | "test_var", |
| 57 | 42.0, |
| 58 | "(42 > 30) ? (42 * 2) : (42 / 2)", |
| 59 | 84.0, |
| 60 | EVAL_ERROR_OK |
| 61 | }, |
| 62 | // Variable not in expression (should remain unchanged) |
| 63 | { |
| 64 | "Variable not in expression", |
| 65 | "33 + 33", |
| 66 | "test_var", |
| 67 | 42.0, |
| 68 | "33 + 33", |
| 69 | 66.0, |
| 70 | EVAL_ERROR_OK |
| 71 | }, |
| 72 | // Hardcoding negative value |
| 73 | { |
| 74 | "Negative value", |
| 75 | "$test_var * 10", |
| 76 | "test_var", |
| 77 | -5.0, |
| 78 | "-5 * 10", |
| 79 | -50.0, |
| 80 | EVAL_ERROR_OK |
| 81 | }, |
| 82 | // Hardcoding decimal value |
| 83 | { |
| 84 | "Decimal value", |
| 85 | "$test_var / 10", |
| 86 | "test_var", |
| 87 | 123.456, |
| 88 | "123.456 / 10", |
| 89 | 12.3456, |
| 90 | EVAL_ERROR_OK |
| 91 | }, |
| 92 | // Function parameter |
| 93 | { |
| 94 | "Function parameter", |
| 95 | "abs($test_var)", |
| 96 | "test_var", |
| 97 | -42.0, |
| 98 | "abs(-42)", |
| 99 | 42.0, |
| 100 | EVAL_ERROR_OK |
| 101 | }, |
| 102 | // NaN value |
| 103 | { |
| 104 | "NaN value (ignored)", |
| 105 | "$test_var + 10", |
| 106 | "test_var", |
| 107 | NAN, |
| 108 | "nan + 10", // source should remain unchanged |
| 109 | 0.0, // Unresolved variable error |
| 110 | EVAL_ERROR_VALUE_IS_NAN |
| 111 | }, |
| 112 | // INFINITY value |
| 113 | { |
| 114 | "NaN value (ignored)", |
| 115 | "$test_var + 10", |
| 116 | "test_var", |
| 117 | INFINITY, |
| 118 | "inf + 10", // source should remain unchanged |
| 119 | 0.0, // Unresolved variable error |
| 120 | EVAL_ERROR_VALUE_IS_INFINITE |
| 121 | }, |
| 122 | // NULL expression (no crash test) |
| 123 | { |
| 124 | "NULL expression", |
| 125 | NULL, // This isn't actually used - we'll handle it specially |
| 126 | "test_var", |
| 127 | 42.0, |
| 128 | NULL, // Expected source doesn't matter |
| 129 | 0.0, // Result doesn't matter |
| 130 | EVAL_ERROR_OK |
| 131 | }, |
| 132 | // NULL variable (no crash test) |
| 133 | { |
| 134 | "NULL variable", |
| 135 | "$test_var + 10", |
| 136 | NULL, // NULL variable name |
| 137 | 42.0, |
| 138 | "$test_var + 10", // source should remain unchanged |
| 139 | 0.0, // Unresolved variable error |
| 140 | EVAL_ERROR_UNKNOWN_VARIABLE |
| 141 | } |
| 142 | }; |
| 143 | |
| 144 | int passed = 0; |
| 145 | int failed = 0; |
| 146 | |
| 147 | // Single loop to process all test cases |
| 148 | for (size_t i = 0; i < sizeof(test_cases) / sizeof(test_cases[0]); i++) { |
| 149 | HardcodeTestCase *tc = &test_cases[i]; |
| 150 | |
| 151 | printf("Test %zu: %s\n", i + 1, tc->name); |
| 152 | |
| 153 | // Handle special case of NULL expression test (just don't crash) |
| 154 | if (!tc->expression) { |
| 155 | printf(" Testing NULL expression (shouldn't crash)...\n"); |
| 156 | STRING *var = tc->variable ? string_strdupz(tc->variable) : NULL; |
| 157 | |
| 158 | // This call shouldn't crash |
| 159 | expression_hardcode_variable(NULL, var, tc->hardcode_value); |
| 160 | |
| 161 | printf(" PASSED: No crash with NULL expression\n"); |
| 162 | passed++; |
| 163 | if (var) string_freez(var); |
| 164 | continue; |
| 165 | } |
| 166 | |
| 167 | // Parse the expression |
| 168 | const char *failed_at = NULL; |
| 169 | int error = 0; |
| 170 | EVAL_EXPRESSION *exp = expression_parse(tc->expression, &failed_at, &error); |
| 171 | |
| 172 | if (!exp) { |
| 173 | printf(" FAILED: Could not parse expression, error: %d (%s)\n", |
| 174 | error, expression_strerror(error)); |
| 175 | failed++; |
| 176 | continue; |
| 177 | } |
| 178 | |
| 179 | // Save the original source |
| 180 | const char *original_source = expression_source(exp); |
| 181 | printf(" Original source: %s\n", original_source); |
| 182 | |
| 183 | // Hardcode the variable |
| 184 | STRING *var = tc->variable ? string_strdupz(tc->variable) : NULL; |
| 185 | expression_hardcode_variable(exp, var, tc->hardcode_value); |
| 186 | if (var) string_freez(var); |
| 187 | |
| 188 | // Get the modified source |
| 189 | const char *modified_source = expression_source(exp); |
| 190 | printf(" Modified source: %s\n", modified_source); |
| 191 | |
| 192 | // Check if source was modified as expected |
| 193 | bool source_correct = true; |
| 194 | if (tc->expected_source && |
| 195 | strcmp(modified_source, tc->expected_source) != 0) { |
| 196 | printf(" FAILED: Source doesn't match expected.\n"); |
| 197 | printf(" Expected: %s\n", tc->expected_source); |
| 198 | printf(" Actual: %s\n", modified_source); |
| 199 | source_correct = false; |
| 200 | } |
| 201 | |
| 202 | // Evaluate the expression |
| 203 | expression_evaluate(exp); |
| 204 | |
| 205 | // Check error code |
| 206 | bool error_correct = (exp->error == tc->expected_error); |
| 207 | if (!error_correct) { |
| 208 | printf(" FAILED: Error code doesn't match expected.\n"); |
| 209 | printf(" Expected error: %d (%s)\n", |
| 210 | tc->expected_error, expression_strerror(tc->expected_error)); |
| 211 | printf(" Actual error: %d (%s)\n", |
| 212 | exp->error, expression_strerror(exp->error)); |
| 213 | } |
| 214 | |
| 215 | // Check result if we expected no error |
| 216 | bool result_correct = true; |
| 217 | if (tc->expected_error == EVAL_ERROR_OK) { |
| 218 | NETDATA_DOUBLE result = expression_result(exp); |
| 219 | printf(" Result: %f\n", result); |
| 220 | |
| 221 | if (fabs(result - tc->expected_result) > 0.000001) { |
| 222 | printf(" FAILED: Result doesn't match expected.\n"); |
| 223 | printf(" Expected: %f\n", tc->expected_result); |
| 224 | printf(" Actual: %f\n", result); |
| 225 | result_correct = false; |
| 226 | } |
| 227 | } |
| 228 | |
| 229 | // Determine if test passed overall |
| 230 | if (source_correct && error_correct && result_correct) { |
| 231 | printf(" PASSED\n"); |
| 232 | passed++; |
| 233 | } else { |
| 234 | failed++; |
| 235 | } |
| 236 | |
| 237 | // Clean up |
| 238 | expression_free(exp); |
| 239 | } |
| 240 | |
| 241 | // Report results |
| 242 | printf("\nHardcode variable test results: %d passed, %d failed\n", passed, failed); |
| 243 | return failed; |
| 244 | } |