diff --git a/README.md b/README.md index 5485bea..042b9d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,34 @@ Files for tbaMUD. +## Unit Tests + +tbaMUD ships with a C unit-test suite located in the `tests/` directory. +The suite uses the [Unity](https://github.com/ThrowTheSwitch/Unity) test +framework (vendored under `tests/vendor/unity/`). + +### Quick start + +``` +./configure +cd tests && make test +``` + +`make test` builds each test binary, runs it, and writes JUnit XML results to +`tests/test-results/`. A summary is printed to the terminal: + +``` +[PASS] test_utils +[PASS] test_random +[PASS] test_interpreter +[PASS] test_class +``` + +### CI + +The GitHub Actions workflow (`.github/workflows/build.yml`) runs `make test` +on every push and pull request against `master` and publishes a formatted +report via the `dorny/test-reporter` action. + +See [doc/testing.md](doc/testing.md) for full details on adding new tests and +understanding the test infrastructure. diff --git a/doc/testing.md b/doc/testing.md new file mode 100644 index 0000000..de5452f --- /dev/null +++ b/doc/testing.md @@ -0,0 +1,150 @@ +# tbaMUD Unit Testing + +_Updated 2026-04_ + +## Overview + +tbaMUD has a C unit-test suite built on the +[Unity](https://github.com/ThrowTheSwitch/Unity) framework. Tests live in the +`tests/` directory alongside the vendored Unity source. + +``` +tests/ + Makefile.in – Autoconf template; processed by configure + test_stubs.c – Weak-symbol stubs that satisfy mud headers + unity_to_junit.py – Converts Unity output to JUnit XML + test_class.c – Tests for src/class.c + test_interpreter.c – Tests for src/interpreter.c + test_random.c – Tests for src/random.c + test_utils.c – Tests for src/utils.c + vendor/unity/ – Vendored Unity test framework +``` + +## Prerequisites + +| Requirement | Notes | +|---|---| +| C compiler (gcc or clang) | Same compiler used to build the mud | +| GNU make | Any POSIX-compatible make works | +| Python 3 | Required only for JUnit XML conversion (`unity_to_junit.py`) | +| autoconf / configure | Already needed to build the mud | + +## Running the tests + +Run `./configure` from the repository root first (only needed once): + +```sh +./configure +``` + +Then build and run all tests from the `tests/` directory: + +```sh +cd tests +make test +``` + +`make test` performs the following steps for each test binary: + +1. Compiles the test binary (if not already up to date). +2. Runs the binary and captures stdout/stderr to `test-results/.out`. +3. Measures wall-clock elapsed time. +4. Converts the Unity output to JUnit XML via `unity_to_junit.py`, writing + `test-results/.xml`. +5. Prints `[PASS] ` or `[FAIL] ` and exits non-zero if any + binary failed. + +To build the test binaries without running them: + +```sh +cd tests +make +``` + +To remove all test binaries and result files: + +```sh +cd tests +make clean +``` + +## Test suites + +| Binary | Source under test | Test file | +|---|---|---| +| `test_utils` | `src/utils.c`, `src/random.c` | `test_utils.c` | +| `test_random` | `src/random.c`, `rand_number`/`dice` in `src/utils.c` | `test_random.c` | +| `test_interpreter` | `src/interpreter.c` | `test_interpreter.c` | +| `test_class` | `src/class.c` | `test_class.c` | + +## Writing a new test + +### Adding a test case to an existing suite + +1. Open the relevant `test_.c` file. +2. Write a function with the signature `void test_my_feature(void)`. +3. Use Unity assertion macros such as `TEST_ASSERT_EQUAL_INT`, + `TEST_ASSERT_NULL`, `TEST_ASSERT_TRUE`, etc. +4. Register the function in the `main()` block: + ```c + RUN_TEST(test_my_feature); + ``` + +Example: + +```c +void test_str_cmp_equal_strings(void) +{ + TEST_ASSERT_EQUAL_INT(0, str_cmp("hello", "hello")); +} +``` + +### Creating a new test suite + +1. Create `tests/test_.c`. Copy the boilerplate from an existing + suite: include `unity.h`, define `setUp`/`tearDown` (may be empty), write + test functions, and provide a `main()` that calls `UNITY_BEGIN()`, + `RUN_TEST(...)` for each function, and `return UNITY_END();`. + +2. Add the binary to `tests/Makefile.in`: + - Add the name to the `TESTS` variable. + - Add a build rule: + ```make + test_: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \ + $(SRCDIR)/.c test_.c + $(COMPILE) -o $@ $^ $(LIBS) + ``` + +3. Re-run `./configure` from the repository root to regenerate + `tests/Makefile` from the updated `tests/Makefile.in`. + +### Stubs + +Many mud source files reference global variables and functions that are only +meaningful at runtime (e.g. `descriptor_list`, `log()`). `test_stubs.c` +provides zero-initialised definitions and `__attribute__((weak))` stub +implementations for these symbols so that test binaries link without pulling +in the full mud. + +If a new test requires a function not yet stubbed, add a weak stub to +`test_stubs.c`: + +```c +__attribute__((weak)) void my_function(void) { /* no-op */ } +``` + +## JUnit XML output and CI + +`unity_to_junit.py` reads Unity's line-oriented output on stdin and writes a +JUnit-compatible XML file. It accepts an optional third argument with the +elapsed wall-clock time in seconds (provided by the `make test` target): + +``` +usage: unity_to_junit.py [elapsed_seconds] +``` + +The GitHub Actions workflow (`.github/workflows/build.yml`) runs `make test` +on every push and pull request against `master`. After the tests finish the +`dorny/test-reporter` action reads `tests/test-results/*.xml` and publishes a +formatted report as a GitHub Check with pass/fail counts and per-suite +execution times.