8TEST_CASE(
"Mathematical and Geometric Helpers") {
9 SUBCASE(
"global.h utilities") {
10 SUBCASE(
"deg2rad and rad2deg") {
11 DOCTEST_CHECK(
deg2rad(180.f) == doctest::Approx(
PI_F));
12 DOCTEST_CHECK(
rad2deg(
PI_F) == doctest::Approx(180.f));
16 DOCTEST_CHECK(
clamp(5, 0, 10) == 5);
17 DOCTEST_CHECK(
clamp(-5, 0, 10) == 0);
18 DOCTEST_CHECK(
clamp(15, 0, 10) == 10);
19 DOCTEST_CHECK(
clamp(5.5f, 0.f, 10.f) == doctest::Approx(5.5f));
22 SUBCASE(
"Safe trigonometry") {
23 DOCTEST_CHECK(
acos_safe(1.000001f) == doctest::Approx(0.f));
24 DOCTEST_CHECK(
acos_safe(-1.000001f) == doctest::Approx(
PI_F));
25 DOCTEST_CHECK(
asin_safe(1.000001f) == doctest::Approx(
PI_F / 2.f));
26 DOCTEST_CHECK(
asin_safe(-1.000001f) == doctest::Approx(-
PI_F / 2.f));
29 SUBCASE(
"Integer power") {
30 DOCTEST_CHECK(
powi(2, 10) == 1024);
31 DOCTEST_CHECK(
powi(3.f, 3) == doctest::Approx(27.f));
34 SUBCASE(
"calculateTriangleArea") {
35 vec3 v0(0, 0, 0), v1(1, 0, 0), v2(0, 1, 0);
39 SUBCASE(
"calculateTriangleArea with degenerate triangle") {
40 vec3 v0(0, 0, 0), v1(1, 1, 1), v2(2, 2, 2);
44 SUBCASE(
"blend colors") {
45 RGBcolor c1(1, 0, 0), c2(0, 1, 0);
46 RGBcolor blended =
blend(c1, c2, 0.5f);
47 DOCTEST_CHECK(blended.r == doctest::Approx(0.5f));
48 DOCTEST_CHECK(blended.g == doctest::Approx(0.5f));
49 DOCTEST_CHECK(blended.b == doctest::Approx(0.f));
51 RGBAcolor ca1(1, 0, 0, 0), ca2(0, 1, 0, 1);
52 RGBAcolor blended_a =
blend(ca1, ca2, 0.5f);
53 DOCTEST_CHECK(blended_a.r == doctest::Approx(0.5f));
54 DOCTEST_CHECK(blended_a.g == doctest::Approx(0.5f));
55 DOCTEST_CHECK(blended_a.b == doctest::Approx(0.f));
56 DOCTEST_CHECK(blended_a.a == doctest::Approx(0.5f));
59 SUBCASE(
"global.h geometric functions") {
60 SUBCASE(
"atan2_2pi") {
61 DOCTEST_CHECK(
atan2_2pi(0, 1) == doctest::Approx(0));
62 DOCTEST_CHECK(
atan2_2pi(1, 0) == doctest::Approx(
PI_F / 2.0));
64 DOCTEST_CHECK(
atan2_2pi(-1, 0) == doctest::Approx(3.0 *
PI_F / 2.0));
68 SUBCASE(
"atan2_2pi edge cases") {
70 DOCTEST_CHECK(res == doctest::Approx(0.f));
73 SUBCASE(
"rotatePoint") {
76 DOCTEST_CHECK(rotated.x == doctest::Approx(0.f));
77 DOCTEST_CHECK(rotated.y == doctest::Approx(0.f));
78 DOCTEST_CHECK(rotated.z == doctest::Approx(-1.f));
81 SUBCASE(
"rotatePointAboutLine") {
83 vec3 line_base(0, 0, 0);
84 vec3 line_dir(0, 0, 1);
86 DOCTEST_CHECK(rotated.x == doctest::Approx(0.f));
87 DOCTEST_CHECK(rotated.y == doctest::Approx(1.f));
88 DOCTEST_CHECK(rotated.z == doctest::Approx(0.f));
91 SUBCASE(
"lineIntersection") {
92 vec2 p1(0, 0), q1(2, 2);
93 vec2 p2(0, 2), q2(2, 0);
96 vec2 p3(0, 0), q3(1, 1);
97 vec2 p4(2, 2), q4(3, 3);
100 SUBCASE(
"lineIntersection with collinear and overlapping segments") {
102 vec2 p1(0, 0), q1(1, 1);
103 vec2 p2(2, 2), q2(3, 3);
107 vec2 p3(0, 0), q3(2, 2);
108 vec2 p4(1, 1), q4(3, 3);
112 vec2 p5(0, 0), q5(3, 3);
113 vec2 p6(1, 1), q6(2, 2);
117 vec2 p7(0, 0), q7(1, 1);
118 vec2 p8(1, 1), q8(2, 2);
121 SUBCASE(
"lineIntersection parallel") {
122 vec2 p1(0, 0), q1(1, 0);
123 vec2 p2(0, 1), q2(1, 1);
126 SUBCASE(
"pointInPolygon") {
127 std::vector<vec2> square = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
132 SUBCASE(
"pointInPolygon with concave polygon") {
134 std::vector<vec2> concave_poly = {{0, 0}, {5, 0}, {5, 5}, {3, 3}, {2, 5}, {0, 5}};
140 SUBCASE(
"Spline Interpolation") {
146 vec3 ref =
make_vec3(0.9360f, 0.8280f, 0.2592f);
147 DOCTEST_CHECK(xi.x == doctest::Approx(ref.x).epsilon(errtol));
148 DOCTEST_CHECK(xi.y == doctest::Approx(ref.y).epsilon(errtol));
149 DOCTEST_CHECK(xi.z == doctest::Approx(ref.z).epsilon(errtol));
151 SUBCASE(
"spline_interp3 edge cases") {
158 DOCTEST_CHECK(res_start.x == doctest::Approx(p0.x));
159 DOCTEST_CHECK(res_start.y == doctest::Approx(p0.y));
160 DOCTEST_CHECK(res_start.z == doctest::Approx(p0.z));
163 DOCTEST_CHECK(res_end.x == doctest::Approx(p1.x));
164 DOCTEST_CHECK(res_end.y == doctest::Approx(p1.y));
165 DOCTEST_CHECK(res_end.z == doctest::Approx(p1.z));
168 vec3 res_low, res_high;
170 capture_cerr cerr_buffer;
174 DOCTEST_CHECK(res_low.x == doctest::Approx(p0.x));
175 DOCTEST_CHECK(res_high.x == doctest::Approx(p1.x));
177 SUBCASE(
"Angle conversion helpers") {
178 CHECK(
deg2rad(180.f) == doctest::Approx(
PI_F).epsilon(errtol));
179 CHECK(
rad2deg(
PI_F) == doctest::Approx(180.f).epsilon(errtol));
181 CHECK(
atan2_2pi(0.f, 1.f) == doctest::Approx(0.f).epsilon(errtol));
182 CHECK(
atan2_2pi(1.f, 0.f) == doctest::Approx(0.5f *
PI_F * 2.f / 2.f).epsilon(errtol));
183 CHECK(
atan2_2pi(0.f, -1.f) == doctest::Approx(
PI_F).epsilon(errtol));
184 CHECK(
atan2_2pi(-1.f, 0.f) == doctest::Approx(1.5f *
PI_F).epsilon(errtol));
187 SUBCASE(
"clamp template for common types") {
188 CHECK(
clamp(5, 1, 4) == 4);
189 CHECK(
clamp(-1, 0, 10) == 0);
190 CHECK(
clamp(3.5f, 0.f, 10.f) == doctest::Approx(3.5f).epsilon(errtol));
191 CHECK(
clamp(12.0, -5.0, 11.0) == doctest::Approx(11.0).epsilon(errtol));
195TEST_CASE(
"Matrix and Transformation Helpers") {
196 float M[16], T[16],
R[16];
198 SUBCASE(
"makeIdentityMatrix") {
200 DOCTEST_CHECK(T[0] == 1.f);
201 DOCTEST_CHECK(T[1] == 0.f);
202 DOCTEST_CHECK(T[2] == 0.f);
203 DOCTEST_CHECK(T[3] == 0.f);
204 DOCTEST_CHECK(T[4] == 0.f);
205 DOCTEST_CHECK(T[5] == 1.f);
206 DOCTEST_CHECK(T[6] == 0.f);
207 DOCTEST_CHECK(T[7] == 0.f);
208 DOCTEST_CHECK(T[8] == 0.f);
209 DOCTEST_CHECK(T[9] == 0.f);
210 DOCTEST_CHECK(T[10] == 1.f);
211 DOCTEST_CHECK(T[11] == 0.f);
212 DOCTEST_CHECK(T[12] == 0.f);
213 DOCTEST_CHECK(T[13] == 0.f);
214 DOCTEST_CHECK(T[14] == 0.f);
215 DOCTEST_CHECK(T[15] == 1.f);
218 SUBCASE(
"makeRotationMatrix") {
221 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
222 DOCTEST_CHECK(T[6] == doctest::Approx(-1.f));
223 DOCTEST_CHECK(T[9] == doctest::Approx(1.f));
224 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
228 DOCTEST_CHECK(T[0] == doctest::Approx(0.f));
229 DOCTEST_CHECK(T[2] == doctest::Approx(1.f));
230 DOCTEST_CHECK(T[8] == doctest::Approx(-1.f));
231 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
235 DOCTEST_CHECK(T[0] == doctest::Approx(0.f));
236 DOCTEST_CHECK(T[1] == doctest::Approx(-1.f));
237 DOCTEST_CHECK(T[4] == doctest::Approx(1.f));
238 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
243 DOCTEST_CHECK(T[5] == doctest::Approx(0.f));
244 DOCTEST_CHECK(T[6] == doctest::Approx(-1.f));
245 DOCTEST_CHECK(T[9] == doctest::Approx(1.f));
246 DOCTEST_CHECK(T[10] == doctest::Approx(0.f));
249 SUBCASE(
"makeRotationMatrix invalid axis") {
254 SUBCASE(
"makeTranslationMatrix") {
256 DOCTEST_CHECK(T[3] == 1.f);
257 DOCTEST_CHECK(T[7] == 2.f);
258 DOCTEST_CHECK(T[11] == 3.f);
261 SUBCASE(
"makeScaleMatrix") {
264 DOCTEST_CHECK(T[0] == 2.f);
265 DOCTEST_CHECK(T[5] == 3.f);
266 DOCTEST_CHECK(T[10] == 4.f);
273 DOCTEST_CHECK(res.x == doctest::Approx(3.f));
274 DOCTEST_CHECK(res.y == doctest::Approx(3.f));
275 DOCTEST_CHECK(res.z == doctest::Approx(3.f));
282 DOCTEST_CHECK(
R[0] == doctest::Approx(1.f));
283 DOCTEST_CHECK(
R[5] == doctest::Approx(1.f));
284 DOCTEST_CHECK(
R[10] == doctest::Approx(1.f));
285 DOCTEST_CHECK(
R[15] == doctest::Approx(1.f));
293 DOCTEST_CHECK(res.x == doctest::Approx(0.f));
294 DOCTEST_CHECK(res.y == doctest::Approx(1.f));
295 DOCTEST_CHECK(res.z == doctest::Approx(0.f));
297 float v_arr[3] = {1, 0, 0};
300 DOCTEST_CHECK(res_arr[0] == doctest::Approx(0.f));
301 DOCTEST_CHECK(res_arr[1] == doctest::Approx(1.f));
302 DOCTEST_CHECK(res_arr[2] == doctest::Approx(0.f));
305 SUBCASE(
"makeIdentityMatrix / matmult / vecmult") {
308 for (
int r = 0; r < 4; ++r) {
309 for (
int c = 0; c < 4; ++c) {
310 CHECK(I[4 * r + c] == doctest::Approx(r == c ? 1.f : 0.f).epsilon(errtol));
318 for (
int i = 0; i < 16; ++i) {
319 CHECK(
R[i] == doctest::Approx(T[i]).epsilon(errtol));
322 float v[3] = {4.f, 5.f, 6.f}, out[3] = {};
324 CHECK(out[0] == doctest::Approx(4.f).epsilon(errtol));
325 CHECK(out[1] == doctest::Approx(5.f).epsilon(errtol));
326 CHECK(out[2] == doctest::Approx(6.f).epsilon(errtol));
329 SUBCASE(
"rotatePoint & rotatePointAboutLine") {
333 CHECK(r.x == doctest::Approx(0.f).epsilon(errtol));
334 CHECK(r.y == doctest::Approx(1.f).epsilon(errtol));
338 CHECK(q.x == doctest::Approx(0.f).epsilon(errtol));
339 CHECK(q.y == doctest::Approx(0.f).epsilon(errtol));
343TEST_CASE(
"String, File Path, and Parsing Utilities") {
344 SUBCASE(
"String Manipulation") {
345 DOCTEST_CHECK(
deblank(
" hello world ") ==
"helloworld");
348 SUBCASE(
"String Delimiting") {
350 DOCTEST_CHECK(result.size() == 3);
351 if (result.size() == 3) {
352 DOCTEST_CHECK(result[0] ==
"a");
353 DOCTEST_CHECK(result[1] ==
"b");
354 DOCTEST_CHECK(result[2] ==
"c");
357 SUBCASE(
"separate_string_by_delimiter edge cases") {
358 std::vector<std::string> result;
360 DOCTEST_CHECK(result.size() == 1);
361 DOCTEST_CHECK(result[0] ==
"a,b,c");
364 DOCTEST_CHECK(result.size() == 3);
365 DOCTEST_CHECK(result[0] ==
"a");
366 DOCTEST_CHECK(result[1] ==
"b");
367 DOCTEST_CHECK(result[2] ==
"c");
370 DOCTEST_CHECK(result.size() == 1);
373 capture_cerr cerr_buffer;
378 DOCTEST_CHECK(result.size() == 2);
379 if (result.size() == 2) {
380 DOCTEST_CHECK(result[0] ==
"");
381 DOCTEST_CHECK(result[1] ==
"");
384 SUBCASE(
"String to Vector Conversions") {
385 DOCTEST_CHECK(
string2vec2(
"1.5 2.5") == vec2(1.5f, 2.5f));
386 DOCTEST_CHECK(
string2vec3(
"1.5 2.5 3.5") == vec3(1.5f, 2.5f, 3.5f));
387 DOCTEST_CHECK(
string2vec4(
"1.5 2.5 3.5 4.5") == vec4(1.5f, 2.5f, 3.5f, 4.5f));
389 DOCTEST_CHECK(
string2int3(
"1 2 3") == int3(1, 2, 3));
390 DOCTEST_CHECK(
string2int4(
"1 2 3 4") == int4(1, 2, 3, 4));
392 SUBCASE(
"string to vector conversions with invalid input") {
400 DOCTEST_CHECK_THROWS(result_vec2 =
string2vec2(
"1.5"));
401 DOCTEST_CHECK_THROWS(result_vec3 =
string2vec3(
"1.5 2.5"));
402 DOCTEST_CHECK_THROWS(result_vec4 =
string2vec4(
"1.5 2.5 3.5"));
403 DOCTEST_CHECK_THROWS(result_int2 =
string2int2(
"1"));
404 DOCTEST_CHECK_THROWS(result_int3 =
string2int3(
"1 2"));
405 DOCTEST_CHECK_THROWS(result_int4 =
string2int4(
"1 2 3"));
406 DOCTEST_CHECK_THROWS(result_vec2 =
string2vec2(
"1.5 abc"));
409 SUBCASE(
"String to Color Conversion") {
411 DOCTEST_CHECK(color.r == doctest::Approx(0.1f));
412 DOCTEST_CHECK(color.g == doctest::Approx(0.2f));
413 DOCTEST_CHECK(color.b == doctest::Approx(0.3f));
414 DOCTEST_CHECK(color.a == doctest::Approx(0.4f));
417 DOCTEST_CHECK(color.r == doctest::Approx(0.5f));
418 DOCTEST_CHECK(color.g == doctest::Approx(0.6f));
419 DOCTEST_CHECK(color.b == doctest::Approx(0.7f));
420 DOCTEST_CHECK(color.a == doctest::Approx(1.0f));
422 SUBCASE(
"string2RGBcolor with invalid input") {
429 SUBCASE(
"File Path Parsing") {
430 std::string filepath =
"/path/to/file/filename.ext";
432 DOCTEST_CHECK(
getFileName(filepath) ==
"filename.ext");
433 DOCTEST_CHECK(
getFileStem(filepath) ==
"filename");
435 DOCTEST_CHECK(
getFilePath(filepath,
true) ==
"/path/to/file/");
436 DOCTEST_CHECK(
getFilePath(filepath,
false) ==
"/path/to/file");
439 std::string filepath_noext =
"/path/to/file/filename";
441 DOCTEST_CHECK(
getFileName(filepath_noext) ==
"filename");
442 DOCTEST_CHECK(
getFileStem(filepath_noext) ==
"filename");
444 std::string filepath_nodir =
"filename.ext";
446 DOCTEST_CHECK(
getFileName(filepath_nodir) ==
"filename.ext");
447 DOCTEST_CHECK(
getFileStem(filepath_nodir) ==
"filename");
448 DOCTEST_CHECK(
getFilePath(filepath_nodir,
true).empty());
450 SUBCASE(
"File path parsing edge cases") {
452 DOCTEST_CHECK(
getFileName(
".bashrc") ==
".bashrc");
453 DOCTEST_CHECK(
getFileStem(
".bashrc") ==
".bashrc");
455 DOCTEST_CHECK(
getFilePath(
"/path/to/file/",
true) ==
"/path/to/file/");
456 DOCTEST_CHECK(
getFilePath(
"/path/to/file/",
false) ==
"/path/to/file");
462 SUBCASE(
"Directory path detection") {
487 if (std::filesystem::exists(
"core/tests")) {
490 if (std::filesystem::exists(
"core/include/global.h")) {
494 SUBCASE(
"validateOutputPath adds trailing slash to directories") {
499 std::filesystem::path temp_dir = std::filesystem::temp_directory_path() /
"helios_test_validatepath";
500 std::filesystem::create_directories(temp_dir);
503 std::string path1 = temp_dir.string();
505 if (!path1.empty() && (path1.back() ==
'/' || path1.back() ==
'\\')) {
509 DOCTEST_CHECK(path1.back() ==
'/');
512 std::string path2 = temp_dir.string() +
"/";
514 DOCTEST_CHECK(path2.back() ==
'/');
518 std::string path3 = temp_dir.string() +
"_nonexistent";
519 if (!path3.empty() && (path3.back() ==
'/' || path3.back() ==
'\\')) {
524 DOCTEST_CHECK(result3 ==
true);
525 DOCTEST_CHECK(path3.back() ==
'/');
528 std::string path4 = temp_dir.string() +
"/test.txt";
530 DOCTEST_CHECK(path4.back() !=
'/');
533 std::filesystem::remove_all(temp_dir);
534 std::filesystem::remove_all(temp_dir.string() +
"_nonexistent");
536 SUBCASE(
"Primitive Type Parsing") {
539 DOCTEST_CHECK(f == doctest::Approx(1.23f));
544 DOCTEST_CHECK(d == doctest::Approx(1.23));
549 DOCTEST_CHECK(i == 123);
555 DOCTEST_CHECK(u == 123u);
558 SUBCASE(
"Compound Type Parsing") {
561 DOCTEST_CHECK(i2 == int2(1, 2));
566 DOCTEST_CHECK(i3 == int3(1, 2, 3));
571 DOCTEST_CHECK(v2.x == doctest::Approx(1.1f));
572 DOCTEST_CHECK(v2.y == doctest::Approx(2.2f));
577 DOCTEST_CHECK(v3.x == doctest::Approx(1.1f));
582 DOCTEST_CHECK(rgb.r == doctest::Approx(0.1f));
585 SUBCASE(
"parse functions with whitespace") {
588 DOCTEST_CHECK(i == 123);
591 DOCTEST_CHECK(f == doctest::Approx(1.23f));
593 SUBCASE(
"parse_* invalid input") {
610 DOCTEST_CHECK(!
parse_vec3(
"1.1 2.2 abc", v3));
617TEST_CASE(
"Vector Statistics and Manipulation") {
618 SUBCASE(
"Vector Statistics") {
619 std::vector<float> v = {1.f, 2.f, 3.f, 4.f, 5.f};
620 DOCTEST_CHECK(
sum(v) == doctest::Approx(15.f));
621 DOCTEST_CHECK(
mean(v) == doctest::Approx(3.f));
622 DOCTEST_CHECK(
min(v) == doctest::Approx(1.f));
623 DOCTEST_CHECK(
max(v) == doctest::Approx(5.f));
624 DOCTEST_CHECK(
stdev(v) == doctest::Approx(sqrtf(2.f)));
625 DOCTEST_CHECK(
median(v) == doctest::Approx(3.f));
626 std::vector<float> v2 = {1.f, 2.f, 3.f, 4.f};
627 DOCTEST_CHECK(
median(v2) == doctest::Approx(2.5f));
629 SUBCASE(
"sum / mean / min / max / stdev / median") {
630 std::vector<float> vf{1.f, 2.f, 3.f, 4.f, 5.f};
631 CHECK(
sum(vf) == doctest::Approx(15.f).epsilon(errtol));
632 CHECK(
mean(vf) == doctest::Approx(3.f).epsilon(errtol));
633 CHECK(
min(vf) == doctest::Approx(1.f).epsilon(errtol));
634 CHECK(
max(vf) == doctest::Approx(5.f).epsilon(errtol));
635 CHECK(
median(vf) == doctest::Approx(3.f).epsilon(errtol));
636 CHECK(
stdev(vf) == doctest::Approx(std::sqrt(2.f)).epsilon(1e-4));
638 std::vector<int> vi{9, 4, -3, 10, 2};
639 CHECK(
min(vi) == -3);
640 CHECK(
max(vi) == 10);
646 SUBCASE(
"Vector statistics with single element") {
647 std::vector<float> v = {5.f};
648 DOCTEST_CHECK(
stdev(v) == doctest::Approx(0.f));
650 SUBCASE(
"Vector Manipulation") {
651 SUBCASE(
"resize_vector") {
652 std::vector<std::vector<int>> vec2d;
654 DOCTEST_CHECK(vec2d.size() == 3);
655 DOCTEST_CHECK(vec2d[0].size() == 2);
657 std::vector<std::vector<std::vector<int>>> vec3d;
659 DOCTEST_CHECK(vec3d.size() == 4);
660 DOCTEST_CHECK(vec3d[0].size() == 3);
661 DOCTEST_CHECK(vec3d[0][0].size() == 2);
663 std::vector<std::vector<std::vector<std::vector<int>>>> vec4d;
665 DOCTEST_CHECK(vec4d.size() == 5);
666 DOCTEST_CHECK(vec4d[0].size() == 4);
667 DOCTEST_CHECK(vec4d[0][0].size() == 3);
668 DOCTEST_CHECK(vec4d[0][0][0].size() == 2);
672 std::vector<std::vector<int>> vec2d = {{1, 2}, {3, 4}};
673 std::vector<int> flat =
flatten(vec2d);
674 DOCTEST_CHECK(flat.size() == 4);
675 DOCTEST_CHECK(flat[3] == 4);
677 std::vector<std::vector<std::vector<int>>> vec3d = {{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}};
679 DOCTEST_CHECK(flat.size() == 8);
680 DOCTEST_CHECK(flat[7] == 8);
682 std::vector<std::vector<std::vector<std::vector<int>>>> vec4d = {{{{1, 2}, {3, 4}}, {{5, 6}, {7, 8}}}, {{{9, 10}, {11, 12}}, {{13, 14}, {15, 16}}}};
684 DOCTEST_CHECK(flat.size() == 16);
685 DOCTEST_CHECK(flat[15] == 16);
688 SUBCASE(
"vector operators") {
689 std::vector<float> v1 = {1, 2, 3};
690 std::vector<float> v2 = {4, 5, 6};
691 std::vector<float> v_sum = v1 + v2;
692 DOCTEST_CHECK(v_sum[0] == 5.f);
693 DOCTEST_CHECK(v_sum[2] == 9.f);
696 DOCTEST_CHECK(v1[0] == 5.f);
697 DOCTEST_CHECK(v1[2] == 9.f);
699 std::vector<float> v3 = {1};
700 DOCTEST_CHECK_THROWS(v1 + v3);
703 SUBCASE(
"vector-scalar operators") {
704 std::vector<float> v = {1.f, 2.f, 3.f};
708 std::vector<float> v_add = v + s;
709 DOCTEST_CHECK(v_add.size() == 3);
710 DOCTEST_CHECK(v_add[0] == doctest::Approx(3.f));
711 DOCTEST_CHECK(v_add[1] == doctest::Approx(4.f));
712 DOCTEST_CHECK(v_add[2] == doctest::Approx(5.f));
715 std::vector<float> v_add2 = s + v;
716 DOCTEST_CHECK(v_add2[0] == doctest::Approx(3.f));
717 DOCTEST_CHECK(v_add2[1] == doctest::Approx(4.f));
718 DOCTEST_CHECK(v_add2[2] == doctest::Approx(5.f));
721 std::vector<float> v_sub = v - s;
722 DOCTEST_CHECK(v_sub[0] == doctest::Approx(-1.f));
723 DOCTEST_CHECK(v_sub[1] == doctest::Approx(0.f));
724 DOCTEST_CHECK(v_sub[2] == doctest::Approx(1.f));
727 std::vector<float> v_sub2 = s - v;
728 DOCTEST_CHECK(v_sub2[0] == doctest::Approx(1.f));
729 DOCTEST_CHECK(v_sub2[1] == doctest::Approx(0.f));
730 DOCTEST_CHECK(v_sub2[2] == doctest::Approx(-1.f));
733 std::vector<float> v_mul = v * s;
734 DOCTEST_CHECK(v_mul[0] == doctest::Approx(2.f));
735 DOCTEST_CHECK(v_mul[1] == doctest::Approx(4.f));
736 DOCTEST_CHECK(v_mul[2] == doctest::Approx(6.f));
739 std::vector<float> v_mul2 = s * v;
740 DOCTEST_CHECK(v_mul2[0] == doctest::Approx(2.f));
741 DOCTEST_CHECK(v_mul2[1] == doctest::Approx(4.f));
742 DOCTEST_CHECK(v_mul2[2] == doctest::Approx(6.f));
745 std::vector<float> v_div = v / s;
746 DOCTEST_CHECK(v_div[0] == doctest::Approx(0.5f));
747 DOCTEST_CHECK(v_div[1] == doctest::Approx(1.f));
748 DOCTEST_CHECK(v_div[2] == doctest::Approx(1.5f));
751 std::vector<float> v_div2 = 6.f / v;
752 DOCTEST_CHECK(v_div2[0] == doctest::Approx(6.f));
753 DOCTEST_CHECK(v_div2[1] == doctest::Approx(3.f));
754 DOCTEST_CHECK(v_div2[2] == doctest::Approx(2.f));
757 std::vector<float> empty;
758 std::vector<float> empty_result = empty + 1.f;
759 DOCTEST_CHECK(empty_result.empty());
764TEST_CASE(
"Miscellaneous Utilities") {
765 SUBCASE(
"1D Interpolation") {
766 std::vector<vec2> points = {{0, 0}, {1, 1}, {2, 0}};
767 float res =
interp1(points, 0.5f);
768 DOCTEST_CHECK(res == doctest::Approx(0.5f));
770 DOCTEST_CHECK(res == doctest::Approx(0.5f));
772 DOCTEST_CHECK(res == doctest::Approx(0.f));
774 DOCTEST_CHECK(res == doctest::Approx(0.f));
776 SUBCASE(
"interp1 edge cases") {
778 std::vector<vec2> empty_points;
780 DOCTEST_CHECK_THROWS(result =
interp1(empty_points, 0.5f));
782 std::vector<vec2> single_point = {{1, 5}};
783 result =
interp1(single_point, 0.5f);
784 DOCTEST_CHECK(result == doctest::Approx(5.f));
785 result =
interp1(single_point, 2.f);
786 DOCTEST_CHECK(result == doctest::Approx(5.f));
792 double elapsed = t.toc(
"mute");
793 DOCTEST_CHECK(elapsed >= 0);
795 SUBCASE(
"Custom Error") {
796 capture_cerr cerr_buffer;
799 SUBCASE(
"Random Number Generation") {
801 DOCTEST_CHECK(r >= 0.f);
802 DOCTEST_CHECK(r <= 1.f);
804 int ri =
randu(1, 10);
805 DOCTEST_CHECK(ri >= 1);
806 DOCTEST_CHECK(ri <= 10);
808 SUBCASE(
"randu(int, int)") {
810 DOCTEST_CHECK(r == 5);
811 for (
int i = 0; i < 100; ++i) {
813 DOCTEST_CHECK(r >= 1);
814 DOCTEST_CHECK(r <= 100);
817 SUBCASE(
"acos_safe & asin_safe clamp inputs") {
818 CHECK(
acos_safe(1.5f) == doctest::Approx(0.f).epsilon(errtol));
819 CHECK(
asin_safe(-2.f) == doctest::Approx(-0.5f *
PI_F).epsilon(errtol));
822 SUBCASE(
"randu range checks") {
823 for (
int i = 0; i < 100; ++i) {
828 int ri =
randu(5, 10);
831 CHECK(
randu(3, 3) == 3);
836static float quadratic(
float x, std::vector<float> &,
const void *) {
839static float linear(
float x, std::vector<float> &,
const void *) {
842static float flat(
float, std::vector<float> &,
const void *) {
845static float cubic(
float x, std::vector<float> &,
const void *) {
846 return (x - 1.0f) * (x + 2.0f) * (x - 4.0f);
848static float near_singular(
float x, std::vector<float> &,
const void *) {
849 return (x - 1e-3f) * (x - 1e-3f);
852 SUBCASE(
"fzero finds positive quadratic root") {
853 std::vector<float> v;
854 float root =
helios::fzero(quadratic, v,
nullptr, 1.0f, 1e-5f, 50,
nullptr);
855 DOCTEST_CHECK(root == doctest::Approx(2.0f).epsilon(errtol));
858 SUBCASE(
"fzero finds root far from initial guess") {
859 std::vector<float> v;
860 float root =
helios::fzero(linear, v,
nullptr, -10.0f, 1e-6f, 50,
nullptr);
861 DOCTEST_CHECK(root == doctest::Approx(3.5f).epsilon(errtol));
864 SUBCASE(
"fzero handles function without zero") {
865 std::vector<float> v;
866 WarningAggregator warnings;
867 float root =
helios::fzero(flat, v,
nullptr, 0.0f, 1e-6f, 10, &warnings);
868 DOCTEST_CHECK(std::isfinite(root));
870 bool has_stagnation = warnings.getCount(
"fzero_stagnation") > 0;
871 bool has_convergence = warnings.getCount(
"fzero_convergence_failure") > 0;
872 DOCTEST_CHECK((has_stagnation || has_convergence));
875 SUBCASE(
"fzero returns exact root at initial guess") {
876 std::vector<float> v;
877 float root =
helios::fzero(quadratic, v,
nullptr, 2.0f, 1e-6f, 5,
nullptr);
878 DOCTEST_CHECK(root == doctest::Approx(2.0f).epsilon(errtol));
881 SUBCASE(
"fzero finds a cubic root") {
882 std::vector<float> v;
883 float root =
helios::fzero(cubic, v,
nullptr, 3.5f, 1e-5f, 80,
nullptr);
884 DOCTEST_CHECK(root == doctest::Approx(4.0f).epsilon(errtol));
887 SUBCASE(
"fzero copes with near-singular derivative") {
888 std::vector<float> v;
889 float root =
helios::fzero(near_singular, v,
nullptr, 0.01f, 1e-4f, 50,
nullptr);
890 DOCTEST_CHECK(std::fabs(near_singular(root, v,
nullptr)) < 1e-4f);
894TEST_CASE(
"linspace - Linearly Spaced Values") {
895 SUBCASE(
"linspace float basic functionality") {
896 std::vector<float> result =
linspace(0.f, 10.f, 11);
897 DOCTEST_CHECK(result.size() == 11);
898 DOCTEST_CHECK(result[0] == doctest::Approx(0.f));
899 DOCTEST_CHECK(result[5] == doctest::Approx(5.f));
900 DOCTEST_CHECK(result[10] == doctest::Approx(10.f));
903 for (
size_t i = 1; i < result.size(); ++i) {
904 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(1.f));
908 SUBCASE(
"linspace float with negative range") {
909 std::vector<float> result =
linspace(-5.f, 5.f, 6);
910 DOCTEST_CHECK(result.size() == 6);
911 DOCTEST_CHECK(result[0] == doctest::Approx(-5.f));
912 DOCTEST_CHECK(result[2] == doctest::Approx(-1.f));
913 DOCTEST_CHECK(result[5] == doctest::Approx(5.f));
916 for (
size_t i = 1; i < result.size(); ++i) {
917 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(2.f));
921 SUBCASE(
"linspace float single point") {
922 std::vector<float> result =
linspace(5.f, 10.f, 1);
923 DOCTEST_CHECK(result.size() == 1);
924 DOCTEST_CHECK(result[0] == doctest::Approx(5.f));
927 SUBCASE(
"linspace float two points") {
928 std::vector<float> result =
linspace(1.f, 3.f, 2);
929 DOCTEST_CHECK(result.size() == 2);
930 DOCTEST_CHECK(result[0] == doctest::Approx(1.f));
931 DOCTEST_CHECK(result[1] == doctest::Approx(3.f));
934 SUBCASE(
"linspace float reversed range") {
935 std::vector<float> result =
linspace(10.f, 0.f, 6);
936 DOCTEST_CHECK(result.size() == 6);
937 DOCTEST_CHECK(result[0] == doctest::Approx(10.f));
938 DOCTEST_CHECK(result[2] == doctest::Approx(6.f));
939 DOCTEST_CHECK(result[5] == doctest::Approx(0.f));
942 for (
size_t i = 1; i < result.size(); ++i) {
943 DOCTEST_CHECK(result[i] - result[i - 1] == doctest::Approx(-2.f));
947 SUBCASE(
"linspace float error handling") {
948 std::vector<float> result;
950 DOCTEST_CHECK_THROWS(result =
linspace(0.f, 1.f, 0));
951 DOCTEST_CHECK_THROWS(result =
linspace(0.f, 1.f, -1));
954 SUBCASE(
"linspace vec2 basic functionality") {
955 vec2 start(0.f, 1.f);
957 std::vector<vec2> result =
linspace(start, end, 5);
959 DOCTEST_CHECK(result.size() == 5);
960 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
961 DOCTEST_CHECK(result[0].y == doctest::Approx(1.f));
962 DOCTEST_CHECK(result[2].x == doctest::Approx(2.f));
963 DOCTEST_CHECK(result[2].y == doctest::Approx(3.f));
964 DOCTEST_CHECK(result[4].x == doctest::Approx(4.f));
965 DOCTEST_CHECK(result[4].y == doctest::Approx(5.f));
968 SUBCASE(
"linspace vec2 single point") {
969 vec2 start(1.f, 2.f);
971 std::vector<vec2> result =
linspace(start, end, 1);
973 DOCTEST_CHECK(result.size() == 1);
974 DOCTEST_CHECK(result[0].x == doctest::Approx(start.x));
975 DOCTEST_CHECK(result[0].y == doctest::Approx(start.y));
978 SUBCASE(
"linspace vec2 error handling") {
979 std::vector<vec2> result;
980 vec2 start(0.f, 0.f);
983 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
984 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -5));
987 SUBCASE(
"linspace vec3 basic functionality") {
988 vec3 start(0.f, 0.f, 0.f);
989 vec3 end(3.f, 6.f, 9.f);
990 std::vector<vec3> result =
linspace(start, end, 4);
992 DOCTEST_CHECK(result.size() == 4);
993 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
994 DOCTEST_CHECK(result[0].y == doctest::Approx(0.f));
995 DOCTEST_CHECK(result[0].z == doctest::Approx(0.f));
996 DOCTEST_CHECK(result[1].x == doctest::Approx(1.f));
997 DOCTEST_CHECK(result[1].y == doctest::Approx(2.f));
998 DOCTEST_CHECK(result[1].z == doctest::Approx(3.f));
999 DOCTEST_CHECK(result[3].x == doctest::Approx(3.f));
1000 DOCTEST_CHECK(result[3].y == doctest::Approx(6.f));
1001 DOCTEST_CHECK(result[3].z == doctest::Approx(9.f));
1004 SUBCASE(
"linspace vec3 with mixed positive/negative components") {
1005 vec3 start(-1.f, 2.f, -3.f);
1006 vec3 end(1.f, -2.f, 3.f);
1007 std::vector<vec3> result =
linspace(start, end, 3);
1009 DOCTEST_CHECK(result.size() == 3);
1010 DOCTEST_CHECK(result[0].x == doctest::Approx(-1.f));
1011 DOCTEST_CHECK(result[0].y == doctest::Approx(2.f));
1012 DOCTEST_CHECK(result[0].z == doctest::Approx(-3.f));
1013 DOCTEST_CHECK(result[1].x == doctest::Approx(0.f));
1014 DOCTEST_CHECK(result[1].y == doctest::Approx(0.f));
1015 DOCTEST_CHECK(result[1].z == doctest::Approx(0.f));
1016 DOCTEST_CHECK(result[2].x == doctest::Approx(1.f));
1017 DOCTEST_CHECK(result[2].y == doctest::Approx(-2.f));
1018 DOCTEST_CHECK(result[2].z == doctest::Approx(3.f));
1021 SUBCASE(
"linspace vec3 error handling") {
1022 std::vector<vec3> result;
1023 vec3 start(0.f, 0.f, 0.f);
1024 vec3 end(1.f, 1.f, 1.f);
1026 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
1027 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -10));
1030 SUBCASE(
"linspace vec4 basic functionality") {
1031 vec4 start(0.f, 1.f, 2.f, 3.f);
1032 vec4 end(4.f, 9.f, 14.f, 19.f);
1033 std::vector<vec4> result =
linspace(start, end, 5);
1035 DOCTEST_CHECK(result.size() == 5);
1036 DOCTEST_CHECK(result[0].x == doctest::Approx(0.f));
1037 DOCTEST_CHECK(result[0].y == doctest::Approx(1.f));
1038 DOCTEST_CHECK(result[0].z == doctest::Approx(2.f));
1039 DOCTEST_CHECK(result[0].w == doctest::Approx(3.f));
1040 DOCTEST_CHECK(result[2].x == doctest::Approx(2.f));
1041 DOCTEST_CHECK(result[2].y == doctest::Approx(5.f));
1042 DOCTEST_CHECK(result[2].z == doctest::Approx(8.f));
1043 DOCTEST_CHECK(result[2].w == doctest::Approx(11.f));
1044 DOCTEST_CHECK(result[4].x == doctest::Approx(4.f));
1045 DOCTEST_CHECK(result[4].y == doctest::Approx(9.f));
1046 DOCTEST_CHECK(result[4].z == doctest::Approx(14.f));
1047 DOCTEST_CHECK(result[4].w == doctest::Approx(19.f));
1050 SUBCASE(
"linspace vec4 two points") {
1051 vec4 start(1.f, 2.f, 3.f, 4.f);
1052 vec4 end(5.f, 6.f, 7.f, 8.f);
1053 std::vector<vec4> result =
linspace(start, end, 2);
1055 DOCTEST_CHECK(result.size() == 2);
1056 DOCTEST_CHECK(result[0] == start);
1057 DOCTEST_CHECK(result[1] == end);
1060 SUBCASE(
"linspace vec4 error handling") {
1061 std::vector<vec4> result;
1062 vec4 start(0.f, 0.f, 0.f, 0.f);
1063 vec4 end(1.f, 1.f, 1.f, 1.f);
1065 DOCTEST_CHECK_THROWS(result =
linspace(start, end, 0));
1066 DOCTEST_CHECK_THROWS(result =
linspace(start, end, -1));
1069 SUBCASE(
"linspace precision and endpoint accuracy") {
1071 std::vector<float> result =
linspace(0.1f, 0.9f, 9);
1072 DOCTEST_CHECK(result[0] == doctest::Approx(0.1f));
1073 DOCTEST_CHECK(result[8] == doctest::Approx(0.9f));
1076 result =
linspace(1000000.f, 2000000.f, 11);
1077 DOCTEST_CHECK(result[0] == doctest::Approx(1000000.f));
1078 DOCTEST_CHECK(result[10] == doctest::Approx(2000000.f));
1081 result =
linspace(1e-6f, 2e-6f, 3);
1082 DOCTEST_CHECK(result[0] == doctest::Approx(1e-6f));
1083 DOCTEST_CHECK(result[2] == doctest::Approx(2e-6f));
1086 SUBCASE(
"linspace zero-length intervals") {
1088 std::vector<float> result =
linspace(5.f, 5.f, 5);
1089 DOCTEST_CHECK(result.size() == 5);
1090 for (
const auto &val: result) {
1091 DOCTEST_CHECK(val == doctest::Approx(5.f));
1095 vec3 point(1.f, 2.f, 3.f);
1096 std::vector<vec3> vec_result =
linspace(point, point, 3);
1097 DOCTEST_CHECK(vec_result.size() == 3);
1098 for (
const auto &v: vec_result) {
1099 DOCTEST_CHECK(v.x == doctest::Approx(point.x));
1100 DOCTEST_CHECK(v.y == doctest::Approx(point.y));
1101 DOCTEST_CHECK(v.z == doctest::Approx(point.z));
1106TEST_CASE(
"Asset Resolution Functions") {
1107 SUBCASE(
"resolveAssetPath basic functionality") {
1109 bool exception_thrown =
false;
1111 [[maybe_unused]]
auto path =
resolveAssetPath(
"nonexistent_test_file.txt");
1112 }
catch (
const std::runtime_error &) {
1113 exception_thrown =
true;
1115 DOCTEST_CHECK(exception_thrown);
1118 SUBCASE(
"resolvePluginAsset") {
1120 bool exception_thrown =
false;
1122 [[maybe_unused]]
auto path =
resolvePluginAsset(
"visualizer",
"nonexistent_font.ttf");
1123 }
catch (
const std::runtime_error &) {
1124 exception_thrown =
true;
1126 DOCTEST_CHECK(exception_thrown);
1130 SUBCASE(
"resolveSpectraPath") {
1132 bool exception_thrown =
false;
1135 }
catch (
const std::runtime_error &) {
1136 exception_thrown =
true;
1138 DOCTEST_CHECK(exception_thrown);
1141 SUBCASE(
"validateAssetPath with valid path") {
1143 std::filesystem::path temp_path = std::filesystem::temp_directory_path() /
"helios_test_asset.txt";
1144 std::ofstream temp_file(temp_path);
1145 temp_file <<
"test content";
1152 std::filesystem::remove(temp_path);
1155 SUBCASE(
"validateAssetPath with invalid path") {
1157 std::filesystem::path non_existent =
"/this/path/does/not/exist.txt";
1161 SUBCASE(
"resolveAssetPath with empty string") {
1164 DOCTEST_CHECK(!path.empty());
1165 DOCTEST_CHECK(path.is_absolute());
1168 SUBCASE(
"error message content") {
1172 DOCTEST_FAIL(
"Expected exception was not thrown");
1173 }
catch (
const std::runtime_error &e) {
1174 std::string error_msg = e.what();
1175 DOCTEST_CHECK(error_msg.find(
"Could not locate asset file") != std::string::npos);
1176 DOCTEST_CHECK(error_msg.find(
"nonexistent_file.txt") != std::string::npos);
1180 SUBCASE(
"asset resolution consistency") {
1182 std::string error1, error2;
1185 }
catch (
const std::runtime_error &e) {
1190 }
catch (
const std::runtime_error &e) {
1193 DOCTEST_CHECK(error1 == error2);
1196 SUBCASE(
"different plugin error messages") {
1198 std::string vis_error, rad_error;
1201 }
catch (
const std::runtime_error &e) {
1202 vis_error = e.what();
1206 }
catch (
const std::runtime_error &e) {
1207 rad_error = e.what();
1210 DOCTEST_CHECK(vis_error != rad_error);
1211 DOCTEST_CHECK(vis_error.find(
"visualizer") != std::string::npos);
1212 DOCTEST_CHECK(rad_error.find(
"radiation") != std::string::npos);
1216TEST_CASE(
"Project-based File Resolution") {
1217 SUBCASE(
"findProjectRoot basic functionality") {
1219 std::filesystem::path cwd = std::filesystem::current_path();
1223 DOCTEST_CHECK(!project_root.empty());
1226 auto cmake_file = project_root /
"CMakeLists.txt";
1227 DOCTEST_CHECK(std::filesystem::exists(cmake_file));
1230 SUBCASE(
"findProjectRoot with non-existent path") {
1232 std::filesystem::path fake_path =
"/this/path/does/not/exist";
1236 DOCTEST_CHECK(project_root.empty());
1239 SUBCASE(
"findProjectRoot from root directory") {
1241 std::filesystem::path root_path =
"/";
1245 DOCTEST_CHECK(project_root.empty());
1248 SUBCASE(
"resolveProjectFile with existing file in cwd") {
1250 std::string test_filename =
"test_project_resolve.tmp";
1251 std::ofstream test_file(test_filename);
1252 test_file <<
"test content";
1258 DOCTEST_CHECK(!resolved_path.empty());
1259 DOCTEST_CHECK(std::filesystem::exists(resolved_path));
1262 std::filesystem::remove(test_filename);
1267 std::filesystem::remove(test_filename);
1270 SUBCASE(
"resolveProjectFile with non-existent file") {
1272 std::string fake_filename =
"this_file_does_not_exist_anywhere.tmp";
1274 std::string error_message;
1277 }
catch (
const std::runtime_error &e) {
1278 error_message = e.what();
1282 DOCTEST_CHECK(!error_message.empty());
1283 DOCTEST_CHECK(error_message.find(
"Could not locate file") != std::string::npos);
1284 DOCTEST_CHECK(error_message.find(fake_filename) != std::string::npos);
1287 SUBCASE(
"resolveProjectFile with empty filename") {
1289 std::string error_message;
1292 }
catch (
const std::runtime_error &e) {
1293 error_message = e.what();
1297 DOCTEST_CHECK(!error_message.empty());
1300 SUBCASE(
"resolveProjectFile project directory fallback") {
1304 if (!project_root.empty()) {
1305 std::string test_filename =
"test_project_fallback.tmp";
1306 auto test_file_path = project_root / test_filename;
1309 std::ofstream test_file(test_file_path);
1310 test_file <<
"fallback test content";
1316 DOCTEST_CHECK(!resolved_path.empty());
1317 DOCTEST_CHECK(std::filesystem::exists(resolved_path));
1318 DOCTEST_CHECK(resolved_path == test_file_path);
1321 std::filesystem::remove(test_file_path);
1326 std::filesystem::remove(test_file_path);
1331TEST_CASE(
"WarningAggregator") {
1333 SUBCASE(
"Basic accumulation") {
1334 WarningAggregator agg;
1335 agg.addWarning(
"test_category",
"test message 1");
1336 agg.addWarning(
"test_category",
"test message 2");
1337 agg.addWarning(
"test_category",
"test message 3");
1339 DOCTEST_CHECK(agg.getCount(
"test_category") == 3);
1340 DOCTEST_CHECK(agg.getCount(
"nonexistent_category") == 0);
1343 SUBCASE(
"Multiple categories") {
1344 WarningAggregator agg;
1345 agg.addWarning(
"category_a",
"message A1");
1346 agg.addWarning(
"category_a",
"message A2");
1347 agg.addWarning(
"category_b",
"message B1");
1348 agg.addWarning(
"category_b",
"message B2");
1349 agg.addWarning(
"category_b",
"message B3");
1351 DOCTEST_CHECK(agg.getCount(
"category_a") == 2);
1352 DOCTEST_CHECK(agg.getCount(
"category_b") == 3);
1355 SUBCASE(
"Enable and disable") {
1356 WarningAggregator agg;
1359 DOCTEST_CHECK(agg.isEnabled());
1361 agg.addWarning(
"test",
"message 1");
1362 DOCTEST_CHECK(agg.getCount(
"test") == 1);
1365 agg.setEnabled(
false);
1366 DOCTEST_CHECK(!agg.isEnabled());
1367 agg.addWarning(
"test",
"message 2");
1368 agg.addWarning(
"test",
"message 3");
1371 DOCTEST_CHECK(agg.getCount(
"test") == 1);
1374 agg.setEnabled(
true);
1375 agg.addWarning(
"test",
"message 4");
1376 DOCTEST_CHECK(agg.getCount(
"test") == 2);
1379 SUBCASE(
"Clear warnings") {
1380 WarningAggregator agg;
1381 agg.addWarning(
"test",
"message 1");
1382 agg.addWarning(
"test",
"message 2");
1383 DOCTEST_CHECK(agg.getCount(
"test") == 2);
1386 DOCTEST_CHECK(agg.getCount(
"test") == 0);
1389 SUBCASE(
"Report to stream") {
1390 WarningAggregator agg;
1391 agg.addWarning(
"convergence_failure",
"fzero did not converge after 100 iterations.");
1392 agg.addWarning(
"convergence_failure",
"fzero did not converge after 100 iterations.");
1393 agg.addWarning(
"convergence_failure",
"fzero did not converge after 100 iterations.");
1396 std::ostringstream oss;
1399 std::string output = oss.str();
1402 DOCTEST_CHECK(output.find(
"WARNING:") != std::string::npos);
1403 DOCTEST_CHECK(output.find(
"3 instances") != std::string::npos);
1404 DOCTEST_CHECK(output.find(
"convergence_failure") != std::string::npos);
1405 DOCTEST_CHECK(output.find(
"showing first 3") != std::string::npos);
1408 DOCTEST_CHECK(agg.getCount(
"convergence_failure") == 0);
1411 SUBCASE(
"Report with many warnings") {
1412 WarningAggregator agg;
1415 for (
int i = 0; i < 10; i++) {
1416 agg.addWarning(
"test_many",
"Warning message " + std::to_string(i));
1419 std::ostringstream oss;
1422 std::string output = oss.str();
1424 DOCTEST_CHECK(output.find(
"10 instances") != std::string::npos);
1425 DOCTEST_CHECK(output.find(
"showing first 3") != std::string::npos);
1428 DOCTEST_CHECK(output.find(
"Warning message 0") != std::string::npos);
1429 DOCTEST_CHECK(output.find(
"Warning message 1") != std::string::npos);
1430 DOCTEST_CHECK(output.find(
"Warning message 2") != std::string::npos);
1433 DOCTEST_CHECK(output.find(
"Warning message 9") == std::string::npos);
1436 SUBCASE(
"Empty report") {
1437 WarningAggregator agg;
1439 std::ostringstream oss;
1443 DOCTEST_CHECK(oss.str().empty());
1446 SUBCASE(
"Thread safety with OpenMP") {
1448 WarningAggregator agg;
1450 const int num_threads = 4;
1451 const int warnings_per_thread = 250;
1453#pragma omp parallel for num_threads(num_threads)
1454 for (
int i = 0; i < num_threads * warnings_per_thread; i++) {
1455 agg.addWarning(
"parallel_test",
"message from thread");
1459 DOCTEST_CHECK(agg.getCount(
"parallel_test") == num_threads * warnings_per_thread);
1463 SUBCASE(
"Maximum examples limit") {
1464 WarningAggregator agg;
1467 for (
int i = 0; i < 150; i++) {
1468 agg.addWarning(
"many_warnings",
"Warning " + std::to_string(i));
1472 DOCTEST_CHECK(agg.getCount(
"many_warnings") == 150);
1474 std::ostringstream oss;
1477 std::string output = oss.str();
1480 DOCTEST_CHECK(output.find(
"150 instances") != std::string::npos);
1482 DOCTEST_CHECK(output.find(
"More than 100 warnings") != std::string::npos);
1485 SUBCASE(
"Report with single vs multiple instances") {
1486 WarningAggregator agg;
1489 agg.addWarning(
"single",
"Only one warning");
1491 std::ostringstream oss1;
1493 std::string output1 = oss1.str();
1496 DOCTEST_CHECK(output1.find(
"1 instance") != std::string::npos);
1498 auto instances_pos = output1.find(
" instances");
1499 auto one_instance_pos = output1.find(
"1 instance");
1500 bool is_singular = (instances_pos == std::string::npos) || (one_instance_pos < instances_pos);
1501 DOCTEST_CHECK(is_singular);
1504 agg.addWarning(
"multiple",
"Warning 1");
1505 agg.addWarning(
"multiple",
"Warning 2");
1507 std::ostringstream oss2;
1509 std::string output2 = oss2.str();
1512 DOCTEST_CHECK(output2.find(
"2 instances") != std::string::npos);