Skip to content

Conversation

@nh13
Copy link
Owner

@nh13 nh13 commented Dec 22, 2025

Summary

  • Add lightweight unit test framework for DWGSIM
  • Create tests/test_framework.h with assertion macros (ASSERT, ASSERT_EQ, ASSERT_STR_EQ, etc.)
  • Create tests/test_main.c test runner with framework verification tests
  • Add make test-unit and make clean-tests Makefile targets

Closes #94

Test plan

  • Run make test-unit - all tests should pass
  • Run make test - integration tests should still pass
  • Run make clean-tests - test artifacts should be cleaned

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Added an integrated unit test framework with assertion helpers, test lifecycle macros, and summary reporting.
    • Added runnable unit and integration test targets and a test runner that reports failures via exit status.
  • Chores

    • CI workflow updated to run unit and integration tests as separate steps.
    • VCS ignore rules expanded to exclude generated test artifacts.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 22, 2025

Warning

Rate limit exceeded

@nh13 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 11 minutes and 46 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 5517650 and 4c897d2.

📒 Files selected for processing (5)
  • .github/workflows/testing.yml
  • .gitignore
  • Makefile
  • tests/test_framework.h
  • tests/test_main.c

Walkthrough

Adds a lightweight unit testing infrastructure: a header-only test framework, a test runner, Makefile targets for building/running/cleaning tests, updated CI steps to run unit and integration tests separately, and .gitignore entries for test artifacts. (47 words)

Changes

Cohort / File(s) Summary
VCS ignores
\.gitignore
Adds ignore patterns for test artifacts (tests/*.o, tests/run_tests) in addition to existing ignores.
Build & CI
Makefile, .github/workflows/testing.yml
Adds test-related phony targets (test, test-unit, test-integration, clean-tests), compiles and links test objects into a test executable, runs unit tests and integration tests separately in CI, upgrades actions/checkout to v4.
Test framework
tests/test_framework.h
New header-only minimal unit test framework: typedef void (*test_fn)(void); and macros TEST, RUN_TEST, TEST_SUMMARY, TEST_SUITE, plus assertion macros (ASSERT, ASSERT_MSG, ASSERT_EQ, ASSERT_NE, ASSERT_STR_EQ, ASSERT_FLOAT_EQ, ASSERT_NOT_NULL, ASSERT_NULL, ASSERT_LT, ASSERT_LE, ASSERT_GT, ASSERT_GE) with internal counters and contextual reporting.
Test runner
tests/test_main.c
New test runner with int main(void) that registers and runs placeholder tests using the framework, prints summaries, and returns non-zero on failures; includes placeholders for adding more test files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add unit testing infrastructure' directly and clearly summarizes the main change—adding a unit testing framework to DWGSIM.
Linked Issues check ✅ Passed All primary objectives from issue #94 are met: test_framework.h with assertion macros created [#94], test_main.c as test runner implemented [#94], make test-unit target added [#94], make clean-tests target added [#94], and CI updated to run unit tests [#94].
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #94 objectives. The addition of .gitignore entries and actions/checkout upgrade are necessary supporting changes for the testing infrastructure.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
tests/test_framework.h (1)

59-109: Optional: Consider more unique macro variable names.

The assertion macros use variable names like _exp, _act, _nexp which, while legal in local scope, could be more defensive. Consider using double underscores or a prefix like __test_exp to further reduce collision risk with user code.

This is a minor concern since these are macro-local variables, but it's a best practice for macro hygiene in widely-used headers.

Makefile (1)

75-93: Consider consolidating or clarifying test target naming.

Currently:

  • make test → runs integration tests (testdata/test.sh)
  • make test-unit → runs unit tests

Suggested improvements:

  1. Have make test run both integration and unit tests
  2. Provide separate make test-integration and make test-unit targets
  3. Document the distinction clearly

This would provide a better user experience where make test runs all tests, while still allowing selective execution of each test suite.

🔎 Proposed approach
+test: test-integration test-unit
+
+test-integration:
 	if [ -d tmp ]; then rm -r tmp; fi
 	/bin/bash testdata/test.sh

 # Unit test target
 TEST_OBJS = tests/test_main.o
 TEST_PROG = tests/run_tests

 tests/test_main.o: tests/test_main.c tests/test_framework.h
 	$(CC) -c $(CFLAGS) $(DFLAGS) -I. -Isrc tests/test_main.c -o $@

 $(TEST_PROG): $(TEST_OBJS)
 	$(CC) $(CFLAGS) -o $@ $(TEST_OBJS) -lm

 test-unit: $(TEST_PROG)
 	./$(TEST_PROG)

 clean-tests:
 	rm -f tests/*.o $(TEST_PROG)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 786293b and d5c1589.

📒 Files selected for processing (4)
  • .gitignore
  • Makefile
  • tests/test_framework.h
  • tests/test_main.c
🔇 Additional comments (9)
.gitignore (1)

3-4: LGTM!

The ignore patterns correctly cover test artifacts introduced by the new unit test infrastructure.

tests/test_framework.h (4)

1-26: LGTM!

The header documentation clearly explains usage patterns, and the include guards and standard library dependencies are appropriate.


34-49: LGTM!

The TEST and RUN_TEST macro design is clean and follows standard patterns. The failure tracking correctly detects whether a test passed by comparing failure counts before and after execution.


51-57: LGTM!

The summary calculation correctly computes passed tests as test_count - test_failures. This works because the assertion macros return immediately after the first failure, ensuring test_failures counts failed tests rather than failed assertions.


126-222: LGTM!

The floating-point comparison correctly uses epsilon-based comparison, pointer assertions are straightforward, and the comparison macros (ASSERT_LT, ASSERT_LE, etc.) appropriately cast to long long for consistent comparison. The TEST_SUITE macro provides clear test organization.

tests/test_main.c (2)

15-58: LGTM!

The framework verification tests provide good coverage of all assertion macros with passing test cases. This validates that the basic framework infrastructure works correctly.


60-80: LGTM!

The main function correctly orchestrates test execution and returns an appropriate exit code (1 for failures, 0 for success), enabling integration with build systems and CI pipelines.

Makefile (2)

36-36: LGTM!

The .PHONY declarations correctly mark the test targets as non-file targets.


79-88: LGTM!

The test compilation and linking rules correctly:

  • Include -Isrc to allow tests to access source headers
  • Link with -lm for the math library (required by fabs in ASSERT_FLOAT_EQ)
  • Specify proper dependencies on both the .c file and the header

The omission of -lz and -lpthread compared to the main programs is appropriate since the test framework doesn't need these libraries.

Comment on lines +28 to +32
/* Global test counters */
static int test_count = 0;
static int test_failures = 0;
static int test_assertions = 0;
static const char *current_test = NULL;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: Static globals in header limit scalability to single translation unit.

Defining static global variables in a header file means each .c file that includes this header gets its own independent copy of these counters. This design only works correctly if the header is included in exactly one .c file.

Impact:

  • If future test files include this header separately (as proper .c files), their test counts won't aggregate
  • The framework cannot track results across multiple test compilation units

Current status: This works now because only test_main.c includes the header, but it will break if the codebase scales to multiple test files compiled separately.

Recommended solutions:

  1. Move these definitions to test_main.c and declare them extern in the header
  2. Use a single-file pattern where all tests are included as .c files (as hinted in test_main.c comments), though this has other downsides

Consider documenting this limitation in the header comment to warn future contributors.

🤖 Prompt for AI Agents
In tests/test_framework.h around lines 28-32, the static global counters are
defined in the header which creates per-translation-unit copies; change the
header to declare these as extern (e.g. extern int test_count; extern int
test_failures; extern int test_assertions; extern const char *current_test;) and
move the actual definitions (without extern and without static) into test_main.c
(or another single implementation file) so there is one shared instance;
additionally update any include patterns or add a short header comment
documenting that the real definitions live in test_main.c.

Comment on lines +111 to +124
/* 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)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reconsider NULL handling in ASSERT_STR_EQ.

The current logic treats NULL == NULL as unequal, which may surprise users:

ASSERT_STR_EQ(NULL, NULL);  // This would FAIL

The condition if (_exp == NULL || _act == NULL || strcmp(...)) fails when either pointer is NULL, even if both are NULL.

Most test frameworks allow NULL == NULL to pass. Consider revising to:

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
In tests/test_framework.h around lines 111 to 124, the ASSERT_STR_EQ macro
treats NULL == NULL as a failure because it unconditionally fails when either
pointer is NULL; change the boolean condition to first detect a nullity mismatch
or, only when both are non-NULL, call strcmp. Specifically, replace the current
if-check with logic equivalent to: fail if exactly one of _exp/_act is NULL, or
if both non-NULL and strcmp reports inequality; keep the existing printing
behavior (using "(null)" when appropriate) and return on failure.

Comment on lines +10 to +13
/* Include test files here as they are added */
/* #include "test_position.c" */
/* #include "test_memory.c" */
/* #include "test_parsing.c" */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reconsider the pattern of including .c files.

The commented examples suggest including .c files directly:

/* #include "test_position.c" */

Issues with this approach:

  • Violates standard C compilation model (separate compilation units)
  • Can cause symbol conflicts and duplicate definitions
  • Makes build dependencies unclear
  • While this pattern sidesteps the static global issue in test_framework.h (by keeping everything in one translation unit), it introduces other problems

Better alternatives:

  1. Compile test files separately and link them, fixing the static globals issue properly
  2. Use header files (.h) for test declarations if tests must be included

Consider documenting the intended test file organization pattern in the header comment or README.

🤖 Prompt for AI Agents
In tests/test_main.c around lines 10 to 13, avoid including .c test files
directly (e.g., /* #include "test_position.c" */) because that forces single
translation-unit builds and causes duplicate symbols and unclear deps; instead,
compile each test_*.c separately and link them into the test binary (or move
shared declarations into headers and include .h only), fix the static/global
symbol management in test_framework.h so separate compilation works, and add a
brief header comment or README note explaining the intended build pattern and
how to add new tests.

Copy link
Owner Author

@nh13 nh13 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure nothing needs to be added to .github/workflows/testing.yml for CI to run the unit tests

@nh13
Copy link
Owner Author

nh13 commented Dec 22, 2025

Addressed the review feedback:

  1. CI workflow updated: Unit tests now run explicitly in the CI pipeline (separate step from integration tests)
  2. Test targets consolidated: make test now runs both unit tests and integration tests
  3. New target added: make test-integration for running integration tests alone
  4. Updated actions/checkout: Upgraded from v2 to v4

The test targets are now:

  • make test - runs both unit and integration tests
  • make test-unit - runs only unit tests
  • make test-integration - runs only integration tests

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Makefile (1)

96-97: Consider integrating test cleanup into main clean target.

The clean-tests target correctly removes test artifacts, but it's not invoked by the main clean target (line 57). This means make clean leaves test artifacts behind. Consider adding clean-tests as a dependency of clean or cleanlocal for consistency.

🔎 Proposed integration

Add clean-tests to the main clean target:

-clean:cleanlocal-recur
+clean:cleanlocal-recur clean-tests
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d5c1589 and 5517650.

📒 Files selected for processing (2)
  • .github/workflows/testing.yml
  • Makefile
🔇 Additional comments (8)
.github/workflows/testing.yml (1)

20-23: LGTM: Test separation improves CI feedback loop.

Splitting unit and integration tests into separate steps provides faster feedback and clearer failure diagnostics.

Makefile (7)

36-36: LGTM: Proper PHONY declarations.

All new test targets are correctly declared as PHONY, preventing conflicts with potential files of the same name.


75-76: LGTM: Unified test target.

The consolidated test target provides a convenient way to run both unit and integration tests, improving developer experience.


78-82: LGTM: Integration test target.

The test-integration target correctly executes existing integration tests and handles temporary directory cleanup.


84-85: LGTM: Clear test artifact definitions.

Test variable naming is clear and the structure is extensible for future test additions.


87-88: LGTM: Correct test compilation rule.

The compilation rule properly handles dependencies, includes, and compiler flags.


93-94: LGTM: Test execution target.

The test-unit target correctly builds and executes the test program.


90-91: The test program linking is correct. The test code currently only verifies the test framework itself with basic assertions and does not call any DWGSIM library functions that would require -lz or -lpthread. The -lm library is sufficient for the current test suite.

Likely an incorrect or invalid review comment.

steps:
- name: Checkout sources
uses: actions/checkout@v2
uses: actions/checkout@v4
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

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@v6 to align with current best practices.

🤖 Prompt for AI Agents
In .github/workflows/testing.yml around line 15 the workflow uses
actions/checkout@v4; update this to actions/checkout@v6 (or
actions/checkout@v6.0.1) to pick up the latest features and security fixes, then
run the workflow locally or in CI to verify there are no breaking changes and
adjust any checkout-related inputs if needed.

@nh13
Copy link
Owner Author

nh13 commented Dec 22, 2025

Addressed the additional review feedback:

  • clean target updated: clean-tests is now a dependency of clean, so make clean will also remove test artifacts.

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>
@nh13 nh13 force-pushed the feature/add-unit-test-framework branch from 2678931 to 4c897d2 Compare December 22, 2025 23:10
@nh13 nh13 merged commit 29c3b7d into main Dec 22, 2025
2 checks passed
@nh13 nh13 deleted the feature/add-unit-test-framework branch December 22, 2025 23:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add unit testing infrastructure

2 participants