summaryrefslogtreecommitdiffstats
path: root/test/edge_cases.c
diff options
context:
space:
mode:
authorEthan Werner <mail@ethanwerner.com>2026-07-01 09:42:39 +0400
committerEthan Werner <mail@ethanwerner.com>2026-07-01 09:42:39 +0400
commit07e18043145f3d91c8ea7ec3ad41b3c905585277 (patch)
tree2bdeae46973f4bdfce263679a4008ac768011f28 /test/edge_cases.c
initial commit: ts time series analysis library v0.1.0
- 354 financial and mathematical time-series indicators - Uniform C11 API with consistent function signatures - 349 passing tests with -Werror - Static and shared library builds - pkg-config integration - Python bindings - Fuzzing and sanitizer support - Security hardening flags
Diffstat (limited to 'test/edge_cases.c')
-rw-r--r--test/edge_cases.c324
1 files changed, 324 insertions, 0 deletions
diff --git a/test/edge_cases.c b/test/edge_cases.c
new file mode 100644
index 0000000..ce47f72
--- /dev/null
+++ b/test/edge_cases.c
@@ -0,0 +1,324 @@
+#include <math.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include "ts.h"
+
+bool ts_test_edge_cases( void )
+{
+ bool result = true;
+
+ /* === NULL input === */
+
+ /* average with NULL input should return -1 */
+ {
+ uint_t k = 3;
+ int ret = ts_average(NULL, 0, NULL, 0, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with NULL input should return -1 */
+ {
+ uint_t k = 3;
+ int ret = ts_rsi(NULL, 0, NULL, 0, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === n = 0 (empty input) === */
+
+ /* average with n=0 should return early, no crash */
+ {
+ uint_t k = 3;
+ int ret = ts_average(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with n=0 should not crash */
+ {
+ uint_t k = 3;
+ int ret = ts_rsi(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with n=0 should return early */
+ {
+ uint_t k = 3;
+ int ret = ts_deviation_standard(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with n=0 should return early */
+ {
+ uint_t k = 3;
+ int ret = ts_variance(NULL, 0, NULL, 0, 0, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === n = 1 (single element) === */
+
+ /* average with n=1, k=1 should produce the element itself */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {5};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* deviation_standard with n=1, k=1 should produce 0 */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with n=1, k=1 should produce 0 */
+ {
+ fp_t const x[] = {5};
+ fp_t const e[] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 1;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 1, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === k = 0 (zero period) === */
+
+ /* average with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_average(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* rsi with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_rsi(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_deviation_standard(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with k=0 should return early */
+ {
+ fp_t x[] = {1, 2, 3, 4, 5};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 0;
+ int ret = ts_variance(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === k > n (period exceeds data) === */
+
+ /* average with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_average(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* deviation_standard with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_deviation_standard(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* variance with k > n should return early */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_variance(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === All-zero input === */
+
+ /* average with all-zero input should produce zeros */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with all-zero input should not crash (all gains/losses are 0) */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0, 0, 0};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* deviation_standard with all-zero input should produce 0 */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with all-zero input should produce 0 */
+ {
+ fp_t const x[] = {0, 0, 0, 0, 0};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === All-same input (zero variance) === */
+
+ /* average with all-same input should produce that value */
+ {
+ fp_t const x[] = {7, 7, 7, 7, 7};
+ fp_t const e[] = {7, 7, 7};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with all-same input should produce 100 (no losses) */
+ {
+ fp_t const x[] = {5, 5, 5, 5, 5, 5, 5};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* deviation_standard with all-same input should produce 0 */
+ {
+ fp_t const x[] = {3, 3, 3, 3, 3};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_deviation_standard, ts_deviation_standard_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_deviation_standard_p);
+ }
+
+ /* variance with all-same input should produce 0 */
+ {
+ fp_t const x[] = {3, 3, 3, 3, 3};
+ fp_t const e[] = {0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_variance, ts_variance_offset, inp, exp, 5, (ts_constant_t *)&k, &ts_variance_p);
+ }
+
+ /* === n == k boundary === */
+
+ /* average with n==k should succeed (produces 1 output) */
+ {
+ fp_t const x[] = {1, 2, 3};
+ fp_t const e[] = {2};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 3;
+ result &= ts_test(ts_average, ts_average_offset, inp, exp, 3, (ts_constant_t *)&k, &ts_average_p);
+ }
+
+ /* rsi with n==k should fail (requires n > k) */
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 3;
+ int ret = ts_rsi(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === NaN input === */
+
+ /* average with NaN input should return -1 (invalid input) */
+ {
+ fp_t x[] = {NAN, 1.0, 2.0, 3.0, 4.0};
+ fp_t y[5] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 3;
+ int ret = ts_average(out, 1, inp, 1, 5, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ /* === Monotonic input (RSI edge cases) === */
+
+ /* rsi with strictly ascending input should produce 100 */
+ {
+ fp_t const x[] = {1, 2, 3, 4, 5, 6, 7};
+ fp_t const e[] = {100, 100, 100, 100, 100};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* rsi with strictly descending input should produce 0 */
+ {
+ fp_t const x[] = {7, 6, 5, 4, 3, 2, 1};
+ fp_t const e[] = {0, 0, 0, 0, 0};
+ fp_t const * const inp[1] = {x};
+ fp_t const * const exp[1] = {e};
+ uint_t k = 2;
+ result &= ts_test(ts_rsi, ts_rsi_offset, inp, exp, 7, (ts_constant_t *)&k, &ts_rsi_p);
+ }
+
+ /* === rsi with k > n === */
+
+ {
+ fp_t x[] = {1, 2, 3};
+ fp_t y[3] = {0};
+ fp_t const * const inp[1] = {x};
+ fp_t * const out[1] = {y};
+ uint_t k = 10;
+ int ret = ts_rsi(out, 1, inp, 1, 3, (ts_constant_t *)&k);
+ result &= (ret == -1);
+ }
+
+ return result;
+}