-
Notifications
You must be signed in to change notification settings - Fork 37
feat: add unit testing infrastructure #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,2 +1,4 @@ | ||
| src/*.o | ||
| dwgsim* | ||
| tests/*.o | ||
| tests/run_tests |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,224 @@ | ||
| /* | ||
| * Minimal Unit Test Framework for DWGSIM | ||
| * | ||
| * Usage: | ||
| * #include "test_framework.h" | ||
| * | ||
| * TEST(test_name) { | ||
| * ASSERT(condition); | ||
| * ASSERT_EQ(expected, actual); | ||
| * ASSERT_STR_EQ(expected, actual); | ||
| * } | ||
| * | ||
| * int main(void) { | ||
| * RUN_TEST(test_name); | ||
| * TEST_SUMMARY(); | ||
| * return test_failures; | ||
| * } | ||
| */ | ||
|
|
||
| #ifndef TEST_FRAMEWORK_H | ||
| #define TEST_FRAMEWORK_H | ||
|
|
||
| #include <stdio.h> | ||
| #include <string.h> | ||
| #include <stdlib.h> | ||
| #include <math.h> | ||
|
|
||
| /* Global test counters */ | ||
| static int test_count = 0; | ||
| static int test_failures = 0; | ||
| static int test_assertions = 0; | ||
| static const char *current_test = NULL; | ||
|
Comment on lines
+28
to
+32
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Critical: Static globals in header limit scalability to single translation unit. Defining static global variables in a header file means each Impact:
Current status: This works now because only Recommended solutions:
Consider documenting this limitation in the header comment to warn future contributors. 🤖 Prompt for AI Agents |
||
|
|
||
| /* Test function signature */ | ||
| typedef void (*test_fn)(void); | ||
|
|
||
| /* Define a test function */ | ||
| #define TEST(name) static void name(void) | ||
|
|
||
| /* Run a test */ | ||
| #define RUN_TEST(name) do { \ | ||
| current_test = #name; \ | ||
| test_count++; \ | ||
| int prev_failures = test_failures; \ | ||
| name(); \ | ||
| if (test_failures == prev_failures) { \ | ||
| printf(" [PASS] %s\n", #name); \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Print test summary */ | ||
| #define TEST_SUMMARY() do { \ | ||
| printf("\n----------------------------------------\n"); \ | ||
| printf("Tests: %d | Passed: %d | Failed: %d | Assertions: %d\n", \ | ||
| test_count, test_count - test_failures, test_failures, test_assertions); \ | ||
| printf("----------------------------------------\n"); \ | ||
| } while (0) | ||
|
|
||
| /* Basic assertion */ | ||
| #define ASSERT(cond) do { \ | ||
| test_assertions++; \ | ||
| if (!(cond)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Assertion failed: %s\n", #cond); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert with message */ | ||
| #define ASSERT_MSG(cond, msg) do { \ | ||
| test_assertions++; \ | ||
| if (!(cond)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" %s\n", msg); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert equality for integers */ | ||
| #define ASSERT_EQ(expected, actual) do { \ | ||
| test_assertions++; \ | ||
| long long _exp = (long long)(expected); \ | ||
| long long _act = (long long)(actual); \ | ||
| if (_exp != _act) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected: %lld, Actual: %lld\n", _exp, _act); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert inequality */ | ||
| #define ASSERT_NE(not_expected, actual) do { \ | ||
| test_assertions++; \ | ||
| long long _nexp = (long long)(not_expected); \ | ||
| long long _act = (long long)(actual); \ | ||
| if (_nexp == _act) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected NOT: %lld, but got: %lld\n", _nexp, _act); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert string equality */ | ||
| #define ASSERT_STR_EQ(expected, actual) do { \ | ||
| test_assertions++; \ | ||
| const char *_exp = (expected); \ | ||
| const char *_act = (actual); \ | ||
| if (_exp == NULL || _act == NULL || strcmp(_exp, _act) != 0) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected: \"%s\", Actual: \"%s\"\n", \ | ||
| _exp ? _exp : "(null)", _act ? _act : "(null)"); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
Comment on lines
+111
to
+124
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reconsider NULL handling in ASSERT_STR_EQ. The current logic treats ASSERT_STR_EQ(NULL, NULL); // This would FAILThe condition Most test frameworks allow if ((_exp == NULL) != (_act == NULL) || (_exp != NULL && strcmp(_exp, _act) != 0))This would pass when both are NULL, but fail when only one is NULL or when non-NULL strings differ. 🔎 Proposed fix- if (_exp == NULL || _act == NULL || strcmp(_exp, _act) != 0) {
+ if ((_exp == NULL) != (_act == NULL) || (_exp != NULL && strcmp(_exp, _act) != 0)) {
printf(" [FAIL] %s\n", current_test);
printf(" Expected: \"%s\", Actual: \"%s\"\n", \
_exp ? _exp : "(null)", _act ? _act : "(null)");🤖 Prompt for AI Agents |
||
|
|
||
| /* Assert floating point equality within epsilon */ | ||
| #define ASSERT_FLOAT_EQ(expected, actual, epsilon) do { \ | ||
| test_assertions++; \ | ||
| double _exp = (double)(expected); \ | ||
| double _act = (double)(actual); \ | ||
| double _eps = (double)(epsilon); \ | ||
| if (fabs(_exp - _act) > _eps) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected: %g, Actual: %g (epsilon: %g)\n", _exp, _act, _eps); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert pointer is not NULL */ | ||
| #define ASSERT_NOT_NULL(ptr) do { \ | ||
| test_assertions++; \ | ||
| if ((ptr) == NULL) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected non-NULL pointer\n"); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert pointer is NULL */ | ||
| #define ASSERT_NULL(ptr) do { \ | ||
| test_assertions++; \ | ||
| if ((ptr) != NULL) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected NULL pointer\n"); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert a < b */ | ||
| #define ASSERT_LT(a, b) do { \ | ||
| test_assertions++; \ | ||
| long long _a = (long long)(a); \ | ||
| long long _b = (long long)(b); \ | ||
| if (!(_a < _b)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected %lld < %lld\n", _a, _b); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert a <= b */ | ||
| #define ASSERT_LE(a, b) do { \ | ||
| test_assertions++; \ | ||
| long long _a = (long long)(a); \ | ||
| long long _b = (long long)(b); \ | ||
| if (!(_a <= _b)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected %lld <= %lld\n", _a, _b); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert a > b */ | ||
| #define ASSERT_GT(a, b) do { \ | ||
| test_assertions++; \ | ||
| long long _a = (long long)(a); \ | ||
| long long _b = (long long)(b); \ | ||
| if (!(_a > _b)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected %lld > %lld\n", _a, _b); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Assert a >= b */ | ||
| #define ASSERT_GE(a, b) do { \ | ||
| test_assertions++; \ | ||
| long long _a = (long long)(a); \ | ||
| long long _b = (long long)(b); \ | ||
| if (!(_a >= _b)) { \ | ||
| printf(" [FAIL] %s\n", current_test); \ | ||
| printf(" Expected %lld >= %lld\n", _a, _b); \ | ||
| printf(" at %s:%d\n", __FILE__, __LINE__); \ | ||
| test_failures++; \ | ||
| return; \ | ||
| } \ | ||
| } while (0) | ||
|
|
||
| /* Test suite name header */ | ||
| #define TEST_SUITE(name) printf("\n=== %s ===\n", name) | ||
|
|
||
| #endif /* TEST_FRAMEWORK_H */ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| /* | ||
| * DWGSIM Unit Test Runner | ||
| * | ||
| * This file runs all unit tests for DWGSIM. | ||
| * Individual test files are included below. | ||
| */ | ||
|
|
||
| #include "test_framework.h" | ||
|
|
||
| /* Include test files here as they are added */ | ||
| /* #include "test_position.c" */ | ||
| /* #include "test_memory.c" */ | ||
| /* #include "test_parsing.c" */ | ||
|
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reconsider the pattern of including .c files. The commented examples suggest including /* #include "test_position.c" */Issues with this approach:
Better alternatives:
Consider documenting the intended test file organization pattern in the header comment or README. 🤖 Prompt for AI Agents |
||
|
|
||
| /* | ||
| * Placeholder tests to verify the test framework works | ||
| */ | ||
| TEST(test_framework_assert) { | ||
| ASSERT(1 == 1); | ||
| ASSERT(0 == 0); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_eq) { | ||
| ASSERT_EQ(42, 42); | ||
| ASSERT_EQ(-1, -1); | ||
| ASSERT_EQ(0, 0); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_ne) { | ||
| ASSERT_NE(1, 2); | ||
| ASSERT_NE(-1, 1); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_str_eq) { | ||
| ASSERT_STR_EQ("hello", "hello"); | ||
| ASSERT_STR_EQ("", ""); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_float_eq) { | ||
| ASSERT_FLOAT_EQ(3.14, 3.14, 0.001); | ||
| ASSERT_FLOAT_EQ(0.1 + 0.2, 0.3, 0.0001); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_comparisons) { | ||
| ASSERT_LT(1, 2); | ||
| ASSERT_LE(1, 1); | ||
| ASSERT_LE(1, 2); | ||
| ASSERT_GT(2, 1); | ||
| ASSERT_GE(2, 2); | ||
| ASSERT_GE(3, 2); | ||
| } | ||
|
|
||
| TEST(test_framework_assert_null) { | ||
| int *ptr = NULL; | ||
| int val = 42; | ||
| ASSERT_NULL(ptr); | ||
| ASSERT_NOT_NULL(&val); | ||
| } | ||
|
|
||
| int main(void) { | ||
| printf("DWGSIM Unit Tests\n"); | ||
| printf("========================================\n"); | ||
|
|
||
| TEST_SUITE("Test Framework Verification"); | ||
| RUN_TEST(test_framework_assert); | ||
| RUN_TEST(test_framework_assert_eq); | ||
| RUN_TEST(test_framework_assert_ne); | ||
| RUN_TEST(test_framework_assert_str_eq); | ||
| RUN_TEST(test_framework_assert_float_eq); | ||
| RUN_TEST(test_framework_assert_comparisons); | ||
| RUN_TEST(test_framework_assert_null); | ||
|
|
||
| /* Add test suites here as they are created */ | ||
| /* TEST_SUITE("Position Calculation Tests"); */ | ||
| /* Include tests from test_position.c */ | ||
|
|
||
| TEST_SUMMARY(); | ||
|
|
||
| return test_failures > 0 ? 1 : 0; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider upgrading to v6 for the latest features and security updates.
While upgrading from v2 to v4 is an improvement, v6.0.1 is the latest version. Recommend updating to
actions/checkout@v6to align with current best practices.🤖 Prompt for AI Agents