Skip to content

Commit 29c3b7d

Browse files
nh13claude
andauthored
feat: add unit testing infrastructure (#95)
Add a lightweight unit test framework for DWGSIM: - tests/test_framework.h: Minimal test macros (ASSERT, ASSERT_EQ, etc.) - tests/test_main.c: Test runner with framework verification tests - Makefile updates: - `make test` runs both unit and integration tests - `make test-unit` runs only unit tests - `make test-integration` runs only integration tests - `make clean-tests` cleans test artifacts - `clean` target now includes `clean-tests` - .gitignore: Ignore test build artifacts - CI workflow: Run unit tests explicitly in pipeline, upgrade actions/checkout to v4 Closes #94 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 786293b commit 29c3b7d

5 files changed

Lines changed: 334 additions & 6 deletions

File tree

.github/workflows/testing.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ jobs:
1212
runs-on: ubuntu-24.04
1313
steps:
1414
- name: Checkout sources
15-
uses: actions/checkout@v2
15+
uses: actions/checkout@v4
1616
with:
1717
submodules: 'recursive'
1818
- name: Build
1919
run: make
20-
- name: Test
21-
run: make test
20+
- name: Unit Tests
21+
run: make test-unit
22+
- name: Integration Tests
23+
run: make test-integration
2224

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
src/*.o
22
dwgsim*
3+
tests/*.o
4+
tests/run_tests

Makefile

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ all-recur lib-recur clean-recur cleanlocal-recur install-recur:
3333

3434
all:$(PROG)
3535

36-
.PHONY:all lib clean cleanlocal
36+
.PHONY:all lib clean cleanlocal test test-unit test-integration clean-tests
3737
.PHONY:all-recur lib-recur clean-recur cleanlocal-recur install-recur
3838

3939
dwgsim:lib-recur $(DWGSIM_AOBJS)
@@ -54,7 +54,7 @@ cleanlocal:
5454
fi; \
5555
done;
5656

57-
clean:cleanlocal-recur
57+
clean:cleanlocal-recur clean-tests
5858

5959
dist:clean
6060
if [ -f dwgsim-${PACKAGE_VERSION}.tar.gz ]; then \
@@ -72,6 +72,26 @@ dist:clean
7272
gzip -9 dwgsim-${PACKAGE_VERSION}.tar; \
7373
rm -rv dwgsim-${PACKAGE_VERSION};
7474

75-
test:
75+
# Run all tests (unit + integration)
76+
test: test-unit test-integration
77+
78+
# Integration tests
79+
test-integration:
7680
if [ -d tmp ]; then rm -r tmp; fi
7781
/bin/bash testdata/test.sh
82+
83+
# Unit test target
84+
TEST_OBJS = tests/test_main.o
85+
TEST_PROG = tests/run_tests
86+
87+
tests/test_main.o: tests/test_main.c tests/test_framework.h
88+
$(CC) -c $(CFLAGS) $(DFLAGS) -I. -Isrc tests/test_main.c -o $@
89+
90+
$(TEST_PROG): $(TEST_OBJS)
91+
$(CC) $(CFLAGS) -o $@ $(TEST_OBJS) -lm
92+
93+
test-unit: $(TEST_PROG)
94+
./$(TEST_PROG)
95+
96+
clean-tests:
97+
rm -f tests/*.o $(TEST_PROG)

tests/test_framework.h

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*
2+
* Minimal Unit Test Framework for DWGSIM
3+
*
4+
* Usage:
5+
* #include "test_framework.h"
6+
*
7+
* TEST(test_name) {
8+
* ASSERT(condition);
9+
* ASSERT_EQ(expected, actual);
10+
* ASSERT_STR_EQ(expected, actual);
11+
* }
12+
*
13+
* int main(void) {
14+
* RUN_TEST(test_name);
15+
* TEST_SUMMARY();
16+
* return test_failures;
17+
* }
18+
*/
19+
20+
#ifndef TEST_FRAMEWORK_H
21+
#define TEST_FRAMEWORK_H
22+
23+
#include <stdio.h>
24+
#include <string.h>
25+
#include <stdlib.h>
26+
#include <math.h>
27+
28+
/* Global test counters */
29+
static int test_count = 0;
30+
static int test_failures = 0;
31+
static int test_assertions = 0;
32+
static const char *current_test = NULL;
33+
34+
/* Test function signature */
35+
typedef void (*test_fn)(void);
36+
37+
/* Define a test function */
38+
#define TEST(name) static void name(void)
39+
40+
/* Run a test */
41+
#define RUN_TEST(name) do { \
42+
current_test = #name; \
43+
test_count++; \
44+
int prev_failures = test_failures; \
45+
name(); \
46+
if (test_failures == prev_failures) { \
47+
printf(" [PASS] %s\n", #name); \
48+
} \
49+
} while (0)
50+
51+
/* Print test summary */
52+
#define TEST_SUMMARY() do { \
53+
printf("\n----------------------------------------\n"); \
54+
printf("Tests: %d | Passed: %d | Failed: %d | Assertions: %d\n", \
55+
test_count, test_count - test_failures, test_failures, test_assertions); \
56+
printf("----------------------------------------\n"); \
57+
} while (0)
58+
59+
/* Basic assertion */
60+
#define ASSERT(cond) do { \
61+
test_assertions++; \
62+
if (!(cond)) { \
63+
printf(" [FAIL] %s\n", current_test); \
64+
printf(" Assertion failed: %s\n", #cond); \
65+
printf(" at %s:%d\n", __FILE__, __LINE__); \
66+
test_failures++; \
67+
return; \
68+
} \
69+
} while (0)
70+
71+
/* Assert with message */
72+
#define ASSERT_MSG(cond, msg) do { \
73+
test_assertions++; \
74+
if (!(cond)) { \
75+
printf(" [FAIL] %s\n", current_test); \
76+
printf(" %s\n", msg); \
77+
printf(" at %s:%d\n", __FILE__, __LINE__); \
78+
test_failures++; \
79+
return; \
80+
} \
81+
} while (0)
82+
83+
/* Assert equality for integers */
84+
#define ASSERT_EQ(expected, actual) do { \
85+
test_assertions++; \
86+
long long _exp = (long long)(expected); \
87+
long long _act = (long long)(actual); \
88+
if (_exp != _act) { \
89+
printf(" [FAIL] %s\n", current_test); \
90+
printf(" Expected: %lld, Actual: %lld\n", _exp, _act); \
91+
printf(" at %s:%d\n", __FILE__, __LINE__); \
92+
test_failures++; \
93+
return; \
94+
} \
95+
} while (0)
96+
97+
/* Assert inequality */
98+
#define ASSERT_NE(not_expected, actual) do { \
99+
test_assertions++; \
100+
long long _nexp = (long long)(not_expected); \
101+
long long _act = (long long)(actual); \
102+
if (_nexp == _act) { \
103+
printf(" [FAIL] %s\n", current_test); \
104+
printf(" Expected NOT: %lld, but got: %lld\n", _nexp, _act); \
105+
printf(" at %s:%d\n", __FILE__, __LINE__); \
106+
test_failures++; \
107+
return; \
108+
} \
109+
} while (0)
110+
111+
/* Assert string equality */
112+
#define ASSERT_STR_EQ(expected, actual) do { \
113+
test_assertions++; \
114+
const char *_exp = (expected); \
115+
const char *_act = (actual); \
116+
if (_exp == NULL || _act == NULL || strcmp(_exp, _act) != 0) { \
117+
printf(" [FAIL] %s\n", current_test); \
118+
printf(" Expected: \"%s\", Actual: \"%s\"\n", \
119+
_exp ? _exp : "(null)", _act ? _act : "(null)"); \
120+
printf(" at %s:%d\n", __FILE__, __LINE__); \
121+
test_failures++; \
122+
return; \
123+
} \
124+
} while (0)
125+
126+
/* Assert floating point equality within epsilon */
127+
#define ASSERT_FLOAT_EQ(expected, actual, epsilon) do { \
128+
test_assertions++; \
129+
double _exp = (double)(expected); \
130+
double _act = (double)(actual); \
131+
double _eps = (double)(epsilon); \
132+
if (fabs(_exp - _act) > _eps) { \
133+
printf(" [FAIL] %s\n", current_test); \
134+
printf(" Expected: %g, Actual: %g (epsilon: %g)\n", _exp, _act, _eps); \
135+
printf(" at %s:%d\n", __FILE__, __LINE__); \
136+
test_failures++; \
137+
return; \
138+
} \
139+
} while (0)
140+
141+
/* Assert pointer is not NULL */
142+
#define ASSERT_NOT_NULL(ptr) do { \
143+
test_assertions++; \
144+
if ((ptr) == NULL) { \
145+
printf(" [FAIL] %s\n", current_test); \
146+
printf(" Expected non-NULL pointer\n"); \
147+
printf(" at %s:%d\n", __FILE__, __LINE__); \
148+
test_failures++; \
149+
return; \
150+
} \
151+
} while (0)
152+
153+
/* Assert pointer is NULL */
154+
#define ASSERT_NULL(ptr) do { \
155+
test_assertions++; \
156+
if ((ptr) != NULL) { \
157+
printf(" [FAIL] %s\n", current_test); \
158+
printf(" Expected NULL pointer\n"); \
159+
printf(" at %s:%d\n", __FILE__, __LINE__); \
160+
test_failures++; \
161+
return; \
162+
} \
163+
} while (0)
164+
165+
/* Assert a < b */
166+
#define ASSERT_LT(a, b) do { \
167+
test_assertions++; \
168+
long long _a = (long long)(a); \
169+
long long _b = (long long)(b); \
170+
if (!(_a < _b)) { \
171+
printf(" [FAIL] %s\n", current_test); \
172+
printf(" Expected %lld < %lld\n", _a, _b); \
173+
printf(" at %s:%d\n", __FILE__, __LINE__); \
174+
test_failures++; \
175+
return; \
176+
} \
177+
} while (0)
178+
179+
/* Assert a <= b */
180+
#define ASSERT_LE(a, b) do { \
181+
test_assertions++; \
182+
long long _a = (long long)(a); \
183+
long long _b = (long long)(b); \
184+
if (!(_a <= _b)) { \
185+
printf(" [FAIL] %s\n", current_test); \
186+
printf(" Expected %lld <= %lld\n", _a, _b); \
187+
printf(" at %s:%d\n", __FILE__, __LINE__); \
188+
test_failures++; \
189+
return; \
190+
} \
191+
} while (0)
192+
193+
/* Assert a > b */
194+
#define ASSERT_GT(a, b) do { \
195+
test_assertions++; \
196+
long long _a = (long long)(a); \
197+
long long _b = (long long)(b); \
198+
if (!(_a > _b)) { \
199+
printf(" [FAIL] %s\n", current_test); \
200+
printf(" Expected %lld > %lld\n", _a, _b); \
201+
printf(" at %s:%d\n", __FILE__, __LINE__); \
202+
test_failures++; \
203+
return; \
204+
} \
205+
} while (0)
206+
207+
/* Assert a >= b */
208+
#define ASSERT_GE(a, b) do { \
209+
test_assertions++; \
210+
long long _a = (long long)(a); \
211+
long long _b = (long long)(b); \
212+
if (!(_a >= _b)) { \
213+
printf(" [FAIL] %s\n", current_test); \
214+
printf(" Expected %lld >= %lld\n", _a, _b); \
215+
printf(" at %s:%d\n", __FILE__, __LINE__); \
216+
test_failures++; \
217+
return; \
218+
} \
219+
} while (0)
220+
221+
/* Test suite name header */
222+
#define TEST_SUITE(name) printf("\n=== %s ===\n", name)
223+
224+
#endif /* TEST_FRAMEWORK_H */

tests/test_main.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* DWGSIM Unit Test Runner
3+
*
4+
* This file runs all unit tests for DWGSIM.
5+
* Individual test files are included below.
6+
*/
7+
8+
#include "test_framework.h"
9+
10+
/* Include test files here as they are added */
11+
/* #include "test_position.c" */
12+
/* #include "test_memory.c" */
13+
/* #include "test_parsing.c" */
14+
15+
/*
16+
* Placeholder tests to verify the test framework works
17+
*/
18+
TEST(test_framework_assert) {
19+
ASSERT(1 == 1);
20+
ASSERT(0 == 0);
21+
}
22+
23+
TEST(test_framework_assert_eq) {
24+
ASSERT_EQ(42, 42);
25+
ASSERT_EQ(-1, -1);
26+
ASSERT_EQ(0, 0);
27+
}
28+
29+
TEST(test_framework_assert_ne) {
30+
ASSERT_NE(1, 2);
31+
ASSERT_NE(-1, 1);
32+
}
33+
34+
TEST(test_framework_assert_str_eq) {
35+
ASSERT_STR_EQ("hello", "hello");
36+
ASSERT_STR_EQ("", "");
37+
}
38+
39+
TEST(test_framework_assert_float_eq) {
40+
ASSERT_FLOAT_EQ(3.14, 3.14, 0.001);
41+
ASSERT_FLOAT_EQ(0.1 + 0.2, 0.3, 0.0001);
42+
}
43+
44+
TEST(test_framework_assert_comparisons) {
45+
ASSERT_LT(1, 2);
46+
ASSERT_LE(1, 1);
47+
ASSERT_LE(1, 2);
48+
ASSERT_GT(2, 1);
49+
ASSERT_GE(2, 2);
50+
ASSERT_GE(3, 2);
51+
}
52+
53+
TEST(test_framework_assert_null) {
54+
int *ptr = NULL;
55+
int val = 42;
56+
ASSERT_NULL(ptr);
57+
ASSERT_NOT_NULL(&val);
58+
}
59+
60+
int main(void) {
61+
printf("DWGSIM Unit Tests\n");
62+
printf("========================================\n");
63+
64+
TEST_SUITE("Test Framework Verification");
65+
RUN_TEST(test_framework_assert);
66+
RUN_TEST(test_framework_assert_eq);
67+
RUN_TEST(test_framework_assert_ne);
68+
RUN_TEST(test_framework_assert_str_eq);
69+
RUN_TEST(test_framework_assert_float_eq);
70+
RUN_TEST(test_framework_assert_comparisons);
71+
RUN_TEST(test_framework_assert_null);
72+
73+
/* Add test suites here as they are created */
74+
/* TEST_SUITE("Position Calculation Tests"); */
75+
/* Include tests from test_position.c */
76+
77+
TEST_SUMMARY();
78+
79+
return test_failures > 0 ? 1 : 0;
80+
}

0 commit comments

Comments
 (0)