mirror of
https://github.com/tbamud/tbamud.git
synced 2026-04-30 04:41:51 +02:00
Compare commits
2 Commits
copilot/fi
...
6566ad5164
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6566ad5164 | ||
|
|
fff58ccab3 |
15
.github/workflows/build.yml
vendored
15
.github/workflows/build.yml
vendored
@@ -8,8 +8,6 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
checks: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@@ -21,15 +19,4 @@ jobs:
|
|||||||
- name: configure
|
- name: configure
|
||||||
run: ./configure
|
run: ./configure
|
||||||
- name: build
|
- name: build
|
||||||
working-directory: src
|
run: cd src && touch .accepted && make
|
||||||
run: touch .accepted && make
|
|
||||||
- name: test
|
|
||||||
working-directory: tests
|
|
||||||
run: make test
|
|
||||||
- name: publish test results
|
|
||||||
uses: dorny/test-reporter@v1
|
|
||||||
if: always()
|
|
||||||
with:
|
|
||||||
name: Unity Tests
|
|
||||||
path: tests/test-results/*.xml
|
|
||||||
reporter: java-junit
|
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -7,7 +7,6 @@ config.status
|
|||||||
src/Makefile
|
src/Makefile
|
||||||
src/conf.h
|
src/conf.h
|
||||||
src/util/Makefile
|
src/util/Makefile
|
||||||
tests/Makefile
|
|
||||||
src/.accepted
|
src/.accepted
|
||||||
src/depend
|
src/depend
|
||||||
src/util/depend
|
src/util/depend
|
||||||
@@ -100,4 +99,3 @@ tests/test_utils
|
|||||||
tests/test_random
|
tests/test_random
|
||||||
tests/test_interpreter
|
tests/test_interpreter
|
||||||
tests/test_class
|
tests/test_class
|
||||||
tests/test-results/
|
|
||||||
|
|||||||
31
README.md
31
README.md
@@ -1,34 +1,3 @@
|
|||||||
Files for tbaMUD.
|
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.
|
|
||||||
|
|
||||||
|
|||||||
150
doc/testing.md
150
doc/testing.md
@@ -1,150 +0,0 @@
|
|||||||
# 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/<name>.out`.
|
|
||||||
3. Measures wall-clock elapsed time.
|
|
||||||
4. Converts the Unity output to JUnit XML via `unity_to_junit.py`, writing
|
|
||||||
`test-results/<name>.xml`.
|
|
||||||
5. Prints `[PASS] <name>` or `[FAIL] <name>` 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_<name>.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_<module>.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_<module>: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \
|
|
||||||
$(SRCDIR)/<module>.c test_<module>.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 <suite_name> <output.xml> [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.
|
|
||||||
@@ -1024,27 +1024,17 @@ ACMD(do_mdoor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((rm = get_room(target)) == NULL) {
|
if ((rm = get_room(target)) == NULL) {
|
||||||
mob_log(ch, "mdoor: invalid target (arg == %s)", target);
|
mob_log(ch, "mdoor: invalid target");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
||||||
char dirs_str[256];
|
mob_log(ch, "mdoor: invalid direction");
|
||||||
int di, doff = 0;
|
|
||||||
dirs_str[0] = '\0';
|
|
||||||
for (di = 0; *dirs[di] != '\n'; di++)
|
|
||||||
doff += snprintf(dirs_str + doff, sizeof(dirs_str) - doff, "%s%s", doff ? " " : "", dirs[di]);
|
|
||||||
mob_log(ch, "mdoor: invalid direction (arg == %s) not found in: [ %s ]", direction, dirs_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
||||||
char fields_str[256];
|
mob_log(ch, "odoor: invalid field");
|
||||||
int fi, foff = 0;
|
|
||||||
fields_str[0] = '\0';
|
|
||||||
for (fi = 0; *door_field[fi] != '\n'; fi++)
|
|
||||||
foff += snprintf(fields_str + foff, sizeof(fields_str) - foff, "%s%s", foff ? " " : "", door_field[fi]);
|
|
||||||
mob_log(ch, "mdoor: invalid field (arg == %s) not found in: [ %s ]", field, fields_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1091,10 +1081,8 @@ ACMD(do_mdoor)
|
|||||||
case 5: /* room */
|
case 5: /* room */
|
||||||
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
||||||
newexit->to_room = to_room;
|
newexit->to_room = to_room;
|
||||||
else {
|
else
|
||||||
newexit->to_room = NOWHERE;
|
mob_log(ch, "mdoor: invalid door target");
|
||||||
mob_log(ch, "mdoor: invalid door target (arg == %s)", value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -625,27 +625,17 @@ static OCMD(do_odoor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((rm = get_room(target)) == NULL) {
|
if ((rm = get_room(target)) == NULL) {
|
||||||
obj_log(obj, "odoor: invalid target (arg == %s)", target);
|
obj_log(obj, "odoor: invalid target");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
||||||
char dirs_str[256];
|
obj_log(obj, "odoor: invalid direction");
|
||||||
int di, doff = 0;
|
|
||||||
dirs_str[0] = '\0';
|
|
||||||
for (di = 0; *dirs[di] != '\n'; di++)
|
|
||||||
doff += snprintf(dirs_str + doff, sizeof(dirs_str) - doff, "%s%s", doff ? " " : "", dirs[di]);
|
|
||||||
obj_log(obj, "odoor: invalid direction (arg == %s) not found in: [ %s ]", direction, dirs_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
||||||
char fields_str[256];
|
obj_log(obj, "odoor: invalid field");
|
||||||
int fi, foff = 0;
|
|
||||||
fields_str[0] = '\0';
|
|
||||||
for (fi = 0; *door_field[fi] != '\n'; fi++)
|
|
||||||
foff += snprintf(fields_str + foff, sizeof(fields_str) - foff, "%s%s", foff ? " " : "", door_field[fi]);
|
|
||||||
obj_log(obj, "odoor: invalid field (arg == %s) not found in: [ %s ]", field, fields_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,10 +682,8 @@ static OCMD(do_odoor)
|
|||||||
case 5: /* room */
|
case 5: /* room */
|
||||||
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
||||||
newexit->to_room = to_room;
|
newexit->to_room = to_room;
|
||||||
else {
|
else
|
||||||
newexit->to_room = NOWHERE;
|
obj_log(obj, "odoor: invalid door target");
|
||||||
obj_log(obj, "odoor: invalid door target (arg == %s)", value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -224,27 +224,17 @@ WCMD(do_wdoor)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ((rm = get_room(target)) == NULL) {
|
if ((rm = get_room(target)) == NULL) {
|
||||||
wld_log(room, "wdoor: invalid target (arg == %s)", target);
|
wld_log(room, "wdoor: invalid target");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
|
||||||
char dirs_str[256];
|
wld_log(room, "wdoor: invalid direction");
|
||||||
int di, doff = 0;
|
|
||||||
dirs_str[0] = '\0';
|
|
||||||
for (di = 0; *dirs[di] != '\n'; di++)
|
|
||||||
doff += snprintf(dirs_str + doff, sizeof(dirs_str) - doff, "%s%s", doff ? " " : "", dirs[di]);
|
|
||||||
wld_log(room, "wdoor: invalid direction (arg == %s) not found in: [ %s ]", direction, dirs_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
if ((fd = search_block(field, door_field, FALSE)) == -1) {
|
||||||
char fields_str[256];
|
wld_log(room, "wdoor: invalid field");
|
||||||
int fi, foff = 0;
|
|
||||||
fields_str[0] = '\0';
|
|
||||||
for (fi = 0; *door_field[fi] != '\n'; fi++)
|
|
||||||
foff += snprintf(fields_str + foff, sizeof(fields_str) - foff, "%s%s", foff ? " " : "", door_field[fi]);
|
|
||||||
wld_log(room, "wdoor: invalid field (arg == %s) not found in: [ %s ]", field, fields_str);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,10 +281,8 @@ WCMD(do_wdoor)
|
|||||||
case 5: /* room */
|
case 5: /* room */
|
||||||
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
if ((to_room = real_room(atoi(value))) != NOWHERE)
|
||||||
newexit->to_room = to_room;
|
newexit->to_room = to_room;
|
||||||
else {
|
else
|
||||||
newexit->to_room = NOWHERE;
|
wld_log(room, "wdoor: invalid door target");
|
||||||
wld_log(room, "wdoor: invalid door target (arg == %s)", value);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
/* local (file scope) functions */
|
/* local (file scope) functions */
|
||||||
static int perform_dupe_check(struct descriptor_data *d);
|
static int perform_dupe_check(struct descriptor_data *d);
|
||||||
static struct alias_data *find_alias(struct alias_data *alias_list, char *str);
|
static struct alias_data *find_alias(struct alias_data *alias_list, char *str);
|
||||||
static int perform_complex_alias(struct txt_q *input_q, char *orig, struct alias_data *a);
|
static void perform_complex_alias(struct txt_q *input_q, char *orig, struct alias_data *a);
|
||||||
static int _parse_name(char *arg, char *name);
|
static int _parse_name(char *arg, char *name);
|
||||||
static bool perform_new_char_dupe_check(struct descriptor_data *d);
|
static bool perform_new_char_dupe_check(struct descriptor_data *d);
|
||||||
/* sort_commands utility */
|
/* sort_commands utility */
|
||||||
@@ -668,10 +668,9 @@ ACMD(do_alias)
|
|||||||
* commands. */
|
* commands. */
|
||||||
#define NUM_TOKENS 9
|
#define NUM_TOKENS 9
|
||||||
|
|
||||||
static int perform_complex_alias(struct txt_q *input_q, char *orig, struct alias_data *a)
|
static void perform_complex_alias(struct txt_q *input_q, char *orig, struct alias_data *a)
|
||||||
{
|
{
|
||||||
struct txt_q temp_queue;
|
struct txt_q temp_queue;
|
||||||
struct txt_block *qtmp;
|
|
||||||
char *tokens[NUM_TOKENS], *temp, *write_point;
|
char *tokens[NUM_TOKENS], *temp, *write_point;
|
||||||
char buf2[MAX_RAW_INPUT_LENGTH], buf[MAX_RAW_INPUT_LENGTH]; /* raw? */
|
char buf2[MAX_RAW_INPUT_LENGTH], buf[MAX_RAW_INPUT_LENGTH]; /* raw? */
|
||||||
int num_of_tokens = 0, num;
|
int num_of_tokens = 0, num;
|
||||||
@@ -698,28 +697,17 @@ static int perform_complex_alias(struct txt_q *input_q, char *orig, struct alias
|
|||||||
} else if (*temp == ALIAS_VAR_CHAR) {
|
} else if (*temp == ALIAS_VAR_CHAR) {
|
||||||
temp++;
|
temp++;
|
||||||
if ((num = *temp - '1') < num_of_tokens && num >= 0) {
|
if ((num = *temp - '1') < num_of_tokens && num >= 0) {
|
||||||
if ((write_point - buf) + strlen(tokens[num]) >= MAX_RAW_INPUT_LENGTH)
|
strcpy(write_point, tokens[num]); /* strcpy: OK */
|
||||||
goto overflow;
|
|
||||||
strcpy(write_point, tokens[num]);
|
|
||||||
write_point += strlen(tokens[num]);
|
write_point += strlen(tokens[num]);
|
||||||
} else if (*temp == ALIAS_GLOB_CHAR) {
|
} else if (*temp == ALIAS_GLOB_CHAR) {
|
||||||
skip_spaces(&orig);
|
skip_spaces(&orig);
|
||||||
if ((write_point - buf) + strlen(orig) >= MAX_RAW_INPUT_LENGTH)
|
strcpy(write_point, orig); /* strcpy: OK */
|
||||||
goto overflow;
|
|
||||||
strcpy(write_point, orig);
|
|
||||||
write_point += strlen(orig);
|
write_point += strlen(orig);
|
||||||
} else {
|
} else if ((*(write_point++) = *temp) == '$') /* redouble $ for act safety */
|
||||||
if (write_point - buf + 2 >= MAX_RAW_INPUT_LENGTH)
|
|
||||||
goto overflow;
|
|
||||||
if ((*(write_point++) = *temp) == '$') /* redouble $ for act safety */
|
|
||||||
*(write_point++) = '$';
|
*(write_point++) = '$';
|
||||||
}
|
} else
|
||||||
} else {
|
|
||||||
if (write_point - buf + 1 >= MAX_RAW_INPUT_LENGTH)
|
|
||||||
goto overflow;
|
|
||||||
*(write_point++) = *temp;
|
*(write_point++) = *temp;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
*write_point = '\0';
|
*write_point = '\0';
|
||||||
buf[MAX_INPUT_LENGTH - 1] = '\0';
|
buf[MAX_INPUT_LENGTH - 1] = '\0';
|
||||||
@@ -732,16 +720,6 @@ static int perform_complex_alias(struct txt_q *input_q, char *orig, struct alias
|
|||||||
temp_queue.tail->next = input_q->head;
|
temp_queue.tail->next = input_q->head;
|
||||||
input_q->head = temp_queue.head;
|
input_q->head = temp_queue.head;
|
||||||
}
|
}
|
||||||
return (0);
|
|
||||||
|
|
||||||
overflow:
|
|
||||||
while (temp_queue.head) {
|
|
||||||
qtmp = temp_queue.head;
|
|
||||||
temp_queue.head = qtmp->next;
|
|
||||||
free(qtmp->text);
|
|
||||||
free(qtmp);
|
|
||||||
}
|
|
||||||
return (-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Given a character and a string, perform alias replacement on it.
|
/* Given a character and a string, perform alias replacement on it.
|
||||||
@@ -777,8 +755,7 @@ int perform_alias(struct descriptor_data *d, char *orig, size_t maxlen)
|
|||||||
strlcpy(orig, a->replacement, maxlen);
|
strlcpy(orig, a->replacement, maxlen);
|
||||||
return (0);
|
return (0);
|
||||||
} else {
|
} else {
|
||||||
if (perform_complex_alias(&d->input, ptr, a) < 0)
|
perform_complex_alias(&d->input, ptr, a);
|
||||||
send_to_char(d->character, "Alias expansion too long.\r\n");
|
|
||||||
return (1);
|
return (1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,14 +34,11 @@ set(TEST_INCLUDES
|
|||||||
|
|
||||||
# Suppress warnings that fire in generated stubs / vendored code and in the
|
# Suppress warnings that fire in generated stubs / vendored code and in the
|
||||||
# mud sources when compiled outside their normal full-build context.
|
# mud sources when compiled outside their normal full-build context.
|
||||||
set(TEST_CFLAGS)
|
set(TEST_CFLAGS
|
||||||
if(CMAKE_C_COMPILER_ID MATCHES "^(GNU|Clang|AppleClang)$")
|
|
||||||
list(APPEND TEST_CFLAGS
|
|
||||||
-Wno-unused-parameter
|
-Wno-unused-parameter
|
||||||
-Wno-unused-function
|
-Wno-unused-function
|
||||||
-Wno-unused-variable
|
-Wno-unused-variable
|
||||||
)
|
)
|
||||||
endif()
|
|
||||||
|
|
||||||
# Helper macro: add_mud_test(name SRC1 [SRC2 …])
|
# Helper macro: add_mud_test(name SRC1 [SRC2 …])
|
||||||
# Creates an executable, registers it with CTest.
|
# Creates an executable, registers it with CTest.
|
||||||
@@ -84,13 +81,8 @@ add_mud_test(test_interpreter
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/test_interpreter.c
|
${CMAKE_CURRENT_SOURCE_DIR}/test_interpreter.c
|
||||||
)
|
)
|
||||||
|
|
||||||
# crypt() is referenced from interpreter.c (nanny password hashing).
|
# crypt() is referenced from interpreter.c (nanny password hashing)
|
||||||
# Reuse the crypt library detected by the top-level build when one is needed;
|
target_link_libraries(test_interpreter PRIVATE crypt)
|
||||||
# on platforms where crypt() is provided by libc, no extra link library is
|
|
||||||
# required.
|
|
||||||
if(CRYPT_LIBRARY)
|
|
||||||
target_link_libraries(test_interpreter PRIVATE ${CRYPT_LIBRARY})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# test_class — covers src/class.c
|
# test_class — covers src/class.c
|
||||||
|
|||||||
82
tests/Makefile
Normal file
82
tests/Makefile
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# Generated automatically from Makefile.in by configure.
|
||||||
|
# tests/Makefile.in
|
||||||
|
# Autoconf template — processed by configure to produce tests/Makefile.
|
||||||
|
#
|
||||||
|
# Build and run the tbaMUD unit-test suite.
|
||||||
|
# Usage (after running ./configure from the project root):
|
||||||
|
#
|
||||||
|
# cd tests && make # build all test binaries
|
||||||
|
# cd tests && make test # build and run all tests
|
||||||
|
|
||||||
|
CC = gcc
|
||||||
|
MYFLAGS = -Wall -Wno-char-subscripts -Wno-unused-but-set-variable
|
||||||
|
CFLAGS = -g -O2 $(MYFLAGS)
|
||||||
|
LIBS = -lcrypt
|
||||||
|
|
||||||
|
SRCDIR = ../src
|
||||||
|
UNITYDIR = vendor/unity
|
||||||
|
|
||||||
|
# Include paths:
|
||||||
|
# ../src — mud headers and the generated conf.h
|
||||||
|
# vendor/unity — Unity framework headers
|
||||||
|
INCFLAGS = -I$(SRCDIR) -I$(UNITYDIR)
|
||||||
|
|
||||||
|
# Suppress warnings that fire in generated stubs / vendored code
|
||||||
|
WARNFLAGS = -Wno-unused-parameter -Wno-unused-function -Wno-unused-variable
|
||||||
|
|
||||||
|
COMPILE = $(CC) $(CFLAGS) $(WARNFLAGS) $(INCFLAGS)
|
||||||
|
|
||||||
|
# Common object files compiled into every test binary
|
||||||
|
UNITY_SRC = $(UNITYDIR)/unity.c
|
||||||
|
STUBS_SRC = test_stubs.c
|
||||||
|
|
||||||
|
# tbaMUD source files used by the tests
|
||||||
|
UTILS_SRC = $(SRCDIR)/utils.c $(SRCDIR)/random.c
|
||||||
|
|
||||||
|
# All test binaries
|
||||||
|
TESTS = test_utils test_random test_interpreter test_class
|
||||||
|
|
||||||
|
.PHONY: all test clean
|
||||||
|
|
||||||
|
all: $(TESTS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# test_utils — covers src/utils.c
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
test_utils: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) test_utils.c
|
||||||
|
$(COMPILE) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# test_random — covers src/random.c and rand_number/dice in src/utils.c
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
test_random: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) test_random.c
|
||||||
|
$(COMPILE) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# test_interpreter — covers string helpers in src/interpreter.c
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
test_interpreter: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \
|
||||||
|
$(SRCDIR)/interpreter.c test_interpreter.c
|
||||||
|
$(COMPILE) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# test_class — covers src/class.c
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
test_class: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \
|
||||||
|
$(SRCDIR)/class.c test_class.c
|
||||||
|
$(COMPILE) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Run all tests
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
test: $(TESTS)
|
||||||
|
@echo "=========================================="
|
||||||
|
@echo "Running tbaMUD unit tests"
|
||||||
|
@echo "=========================================="
|
||||||
|
@./test_utils && echo "[PASS] test_utils" || echo "[FAIL] test_utils"
|
||||||
|
@./test_random && echo "[PASS] test_random" || echo "[FAIL] test_random"
|
||||||
|
@./test_interpreter && echo "[PASS] test_interpreter" || echo "[FAIL] test_interpreter"
|
||||||
|
@./test_class && echo "[PASS] test_class" || echo "[FAIL] test_class"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(TESTS)
|
||||||
@@ -66,31 +66,16 @@ test_class: $(UNITY_SRC) $(STUBS_SRC) $(UTILS_SRC) \
|
|||||||
$(COMPILE) -o $@ $^ $(LIBS)
|
$(COMPILE) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Run all tests and produce JUnit XML files in test-results/
|
# Run all tests
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
test: $(TESTS)
|
test: $(TESTS)
|
||||||
@echo "=========================================="
|
@echo "=========================================="
|
||||||
@echo "Running tbaMUD unit tests"
|
@echo "Running tbaMUD unit tests"
|
||||||
@echo "=========================================="
|
@echo "=========================================="
|
||||||
@mkdir -p test-results
|
@./test_utils && echo "[PASS] test_utils" || echo "[FAIL] test_utils"
|
||||||
@status=0; \
|
@./test_random && echo "[PASS] test_random" || echo "[FAIL] test_random"
|
||||||
for t in $(TESTS); do \
|
@./test_interpreter && echo "[PASS] test_interpreter" || echo "[FAIL] test_interpreter"
|
||||||
t_start=$$(date +%s%3N); \
|
@./test_class && echo "[PASS] test_class" || echo "[FAIL] test_class"
|
||||||
./$$t > test-results/$$t.out 2>&1; \
|
|
||||||
rc=$$?; \
|
|
||||||
t_end=$$(date +%s%3N); \
|
|
||||||
elapsed=$$(awk "BEGIN{printf \"%.3f\", ($$t_end - $$t_start)/1000}"); \
|
|
||||||
cat test-results/$$t.out; \
|
|
||||||
python3 unity_to_junit.py $$t test-results/$$t.xml "$$elapsed" < test-results/$$t.out; \
|
|
||||||
if [ $$rc -eq 0 ]; then \
|
|
||||||
echo "[PASS] $$t"; \
|
|
||||||
else \
|
|
||||||
echo "[FAIL] $$t"; \
|
|
||||||
status=1; \
|
|
||||||
fi; \
|
|
||||||
done; \
|
|
||||||
exit $$status
|
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f $(TESTS)
|
rm -f $(TESTS)
|
||||||
rm -rf test-results
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
* @file test_interpreter.c
|
* @file test_interpreter.c
|
||||||
* Unit tests for pure string-handling functions in src/interpreter.c:
|
* Unit tests for pure string-handling functions in src/interpreter.c:
|
||||||
* is_number, is_abbrev, delete_doubledollar, any_one_arg, one_word
|
* is_number, is_abbrev, delete_doubledollar, any_one_arg, one_word
|
||||||
* and for the alias expansion path perform_complex_alias() (via perform_alias()).
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "unity.h"
|
#include "unity.h"
|
||||||
@@ -11,7 +10,6 @@
|
|||||||
#include "sysdep.h"
|
#include "sysdep.h"
|
||||||
#include "structs.h"
|
#include "structs.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "comm.h"
|
|
||||||
#include "interpreter.h"
|
#include "interpreter.h"
|
||||||
|
|
||||||
extern FILE *logfile;
|
extern FILE *logfile;
|
||||||
@@ -19,28 +17,6 @@ extern FILE *logfile;
|
|||||||
void setUp(void) { logfile = stderr; }
|
void setUp(void) { logfile = stderr; }
|
||||||
void tearDown(void) { logfile = NULL; }
|
void tearDown(void) { logfile = NULL; }
|
||||||
|
|
||||||
/* =========================================================
|
|
||||||
* write_to_q — real implementation for alias tests.
|
|
||||||
* Overrides the __attribute__((weak)) stub in test_stubs.c so that
|
|
||||||
* the queue is actually populated and we can inspect its contents.
|
|
||||||
* ========================================================= */
|
|
||||||
|
|
||||||
void write_to_q(const char *txt, struct txt_q *queue, int aliased)
|
|
||||||
{
|
|
||||||
struct txt_block *newt;
|
|
||||||
CREATE(newt, struct txt_block, 1);
|
|
||||||
newt->text = strdup(txt);
|
|
||||||
newt->aliased = aliased;
|
|
||||||
if (!queue->head) {
|
|
||||||
newt->next = NULL;
|
|
||||||
queue->head = queue->tail = newt;
|
|
||||||
} else {
|
|
||||||
queue->tail->next = newt;
|
|
||||||
queue->tail = newt;
|
|
||||||
newt->next = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
* is_number
|
* is_number
|
||||||
* ========================================================= */
|
* ========================================================= */
|
||||||
@@ -231,219 +207,6 @@ void test_one_word_leading_spaces_skipped(void)
|
|||||||
TEST_ASSERT_EQUAL_STRING("hello", first);
|
TEST_ASSERT_EQUAL_STRING("hello", first);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* =========================================================
|
|
||||||
* Helpers for perform_complex_alias tests
|
|
||||||
* ========================================================= */
|
|
||||||
|
|
||||||
/* Release every txt_block in a queue and reset head/tail to NULL. */
|
|
||||||
static void free_txt_q(struct txt_q *q)
|
|
||||||
{
|
|
||||||
struct txt_block *b = q->head, *n;
|
|
||||||
while (b) { n = b->next; free(b->text); free(b); b = n; }
|
|
||||||
q->head = q->tail = NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Build a heap-allocated alias_data with type ALIAS_COMPLEX. */
|
|
||||||
static struct alias_data *make_test_alias(const char *name, const char *repl)
|
|
||||||
{
|
|
||||||
struct alias_data *a;
|
|
||||||
CREATE(a, struct alias_data, 1);
|
|
||||||
a->alias = strdup(name);
|
|
||||||
a->replacement = strdup(repl);
|
|
||||||
a->type = ALIAS_COMPLEX;
|
|
||||||
a->next = NULL;
|
|
||||||
return a;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Free a single alias_data allocated by make_test_alias(). */
|
|
||||||
static void destroy_test_alias(struct alias_data *a)
|
|
||||||
{
|
|
||||||
free(a->alias);
|
|
||||||
free(a->replacement);
|
|
||||||
free(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Zero-initialise all three objects and wire them together so that
|
|
||||||
* IS_NPC() returns false and GET_ALIASES() returns a. */
|
|
||||||
static void alias_env_init(struct descriptor_data *d,
|
|
||||||
struct char_data *ch,
|
|
||||||
struct player_special_data *psd,
|
|
||||||
struct alias_data *a)
|
|
||||||
{
|
|
||||||
memset(d, 0, sizeof(*d));
|
|
||||||
memset(ch, 0, sizeof(*ch));
|
|
||||||
memset(psd, 0, sizeof(*psd));
|
|
||||||
ch->player_specials = psd;
|
|
||||||
psd->aliases = a;
|
|
||||||
d->character = ch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================================================
|
|
||||||
* perform_complex_alias — tested via the public perform_alias()
|
|
||||||
* ========================================================= */
|
|
||||||
|
|
||||||
/* Literal replacement with no variable tokens. */
|
|
||||||
void test_pca_literal(void)
|
|
||||||
{
|
|
||||||
struct alias_data *a = make_test_alias("lit", "hello world");
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
char orig[] = "lit";
|
|
||||||
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("hello world", d.input.head->text);
|
|
||||||
TEST_ASSERT_NULL(d.input.head->next);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $* glob expands to the full argument string after the alias trigger. */
|
|
||||||
void test_pca_glob_expansion(void)
|
|
||||||
{
|
|
||||||
struct alias_data *a = make_test_alias("say", "say $*");
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
char orig[] = "say hello there";
|
|
||||||
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("say hello there", d.input.head->text);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $1 expands to the first whitespace-delimited token of the arguments. */
|
|
||||||
void test_pca_token1_expansion(void)
|
|
||||||
{
|
|
||||||
struct alias_data *a = make_test_alias("s1", "say $1");
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
char orig[] = "s1 hello world";
|
|
||||||
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("say hello", d.input.head->text);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Semicolon separator (;) produces two independent queue entries. */
|
|
||||||
void test_pca_separator(void)
|
|
||||||
{
|
|
||||||
struct alias_data *a = make_test_alias("seq", "go north;go east");
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
char orig[] = "seq";
|
|
||||||
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("go north", d.input.head->text);
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head->next);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("go east", d.input.head->next->text);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* $$ in the replacement is preserved as $$ in the output (act-safety doubling). */
|
|
||||||
void test_pca_dollar_dollar(void)
|
|
||||||
{
|
|
||||||
struct alias_data *a = make_test_alias("dol", "cost $$5");
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
char orig[] = "dol";
|
|
||||||
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
TEST_ASSERT_NOT_NULL(d.input.head);
|
|
||||||
TEST_ASSERT_EQUAL_STRING("cost $$5", d.input.head->text);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overflow via $*: 255 "$*" tokens × 50-char argument exceeds
|
|
||||||
* MAX_RAW_INPUT_LENGTH. The queue must be empty after the call. */
|
|
||||||
void test_pca_overflow_glob(void)
|
|
||||||
{
|
|
||||||
/* 255 × "$*" = 510 bytes + NUL */
|
|
||||||
char repl[511];
|
|
||||||
char orig[64];
|
|
||||||
struct alias_data *a;
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < 255; i++) { repl[i * 2] = '$'; repl[i * 2 + 1] = '*'; }
|
|
||||||
repl[510] = '\0';
|
|
||||||
|
|
||||||
/* "al " + 50 'x' chars */
|
|
||||||
memcpy(orig, "al ", 3);
|
|
||||||
memset(orig + 3, 'x', 50);
|
|
||||||
orig[53] = '\0';
|
|
||||||
|
|
||||||
a = make_test_alias("al", repl);
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
/* No partial results must leak into the queue on overflow. */
|
|
||||||
TEST_ASSERT_NULL(d.input.head);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Overflow via $1: 255 "$1" tokens × 50-char first token exceeds
|
|
||||||
* MAX_RAW_INPUT_LENGTH. The queue must be empty after the call. */
|
|
||||||
void test_pca_overflow_token(void)
|
|
||||||
{
|
|
||||||
/* 255 × "$1" = 510 bytes + NUL */
|
|
||||||
char repl[511];
|
|
||||||
char orig[64];
|
|
||||||
struct alias_data *a;
|
|
||||||
struct descriptor_data d;
|
|
||||||
struct char_data ch;
|
|
||||||
struct player_special_data psd;
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; i < 255; i++) { repl[i * 2] = '$'; repl[i * 2 + 1] = '1'; }
|
|
||||||
repl[510] = '\0';
|
|
||||||
|
|
||||||
/* "al " + 50 'y' chars */
|
|
||||||
memcpy(orig, "al ", 3);
|
|
||||||
memset(orig + 3, 'y', 50);
|
|
||||||
orig[53] = '\0';
|
|
||||||
|
|
||||||
a = make_test_alias("al", repl);
|
|
||||||
alias_env_init(&d, &ch, &psd, a);
|
|
||||||
perform_alias(&d, orig, sizeof(orig));
|
|
||||||
|
|
||||||
/* No partial results must leak into the queue on overflow. */
|
|
||||||
TEST_ASSERT_NULL(d.input.head);
|
|
||||||
|
|
||||||
free_txt_q(&d.input);
|
|
||||||
destroy_test_alias(a);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
* main
|
* main
|
||||||
* ========================================================= */
|
* ========================================================= */
|
||||||
@@ -489,14 +252,5 @@ int main(void)
|
|||||||
RUN_TEST(test_one_word_empty_quoted);
|
RUN_TEST(test_one_word_empty_quoted);
|
||||||
RUN_TEST(test_one_word_leading_spaces_skipped);
|
RUN_TEST(test_one_word_leading_spaces_skipped);
|
||||||
|
|
||||||
/* perform_complex_alias */
|
|
||||||
RUN_TEST(test_pca_literal);
|
|
||||||
RUN_TEST(test_pca_glob_expansion);
|
|
||||||
RUN_TEST(test_pca_token1_expansion);
|
|
||||||
RUN_TEST(test_pca_separator);
|
|
||||||
RUN_TEST(test_pca_dollar_dollar);
|
|
||||||
RUN_TEST(test_pca_overflow_glob);
|
|
||||||
RUN_TEST(test_pca_overflow_token);
|
|
||||||
|
|
||||||
return UNITY_END();
|
return UNITY_END();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ void tearDown(void) { logfile = NULL; }
|
|||||||
* Precomputed for seed=1:
|
* Precomputed for seed=1:
|
||||||
* call 1 → 16807
|
* call 1 → 16807
|
||||||
* call 2 → 282475249
|
* call 2 → 282475249
|
||||||
* call 3 → 1622650073
|
* call 3 → 1622136673
|
||||||
* ========================================================= */
|
* ========================================================= */
|
||||||
|
|
||||||
void test_circle_random_deterministic_first(void)
|
void test_circle_random_deterministic_first(void)
|
||||||
|
|||||||
@@ -1,80 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Convert Unity test-runner output to JUnit XML.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
./test_binary | python3 unity_to_junit.py <suite_name> <output.xml> [elapsed_seconds]
|
|
||||||
|
|
||||||
Unity emits one result line per test:
|
|
||||||
path/to/file.c:LINE:TEST_NAME:PASS
|
|
||||||
path/to/file.c:LINE:TEST_NAME:FAIL:message
|
|
||||||
path/to/file.c:LINE:TEST_NAME:IGNORE:message
|
|
||||||
followed by a summary line:
|
|
||||||
N Tests N Failures N Ignored
|
|
||||||
"""
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import xml.etree.ElementTree as ET
|
|
||||||
|
|
||||||
|
|
||||||
def parse_unity(lines):
|
|
||||||
tests = []
|
|
||||||
total = failures = ignored = 0
|
|
||||||
for line in lines:
|
|
||||||
line = line.rstrip("\n")
|
|
||||||
m = re.match(
|
|
||||||
r"^.+:\d+:([^:]+):(PASS|FAIL|IGNORE)(?::(.*))?$", line
|
|
||||||
)
|
|
||||||
if m:
|
|
||||||
name, result, message = m.group(1), m.group(2), m.group(3) or ""
|
|
||||||
tests.append((name, result, message))
|
|
||||||
continue
|
|
||||||
m2 = re.match(r"^(\d+) Tests (\d+) Failures (\d+) Ignored", line)
|
|
||||||
if m2:
|
|
||||||
total, failures, ignored = int(m2.group(1)), int(m2.group(2)), int(m2.group(3))
|
|
||||||
if not total:
|
|
||||||
total = len(tests)
|
|
||||||
failures = sum(1 for _, r, _ in tests if r == "FAIL")
|
|
||||||
ignored = sum(1 for _, r, _ in tests if r == "IGNORE")
|
|
||||||
return tests, total, failures, ignored
|
|
||||||
|
|
||||||
|
|
||||||
def build_xml(suite_name, tests, total, failures, ignored, elapsed):
|
|
||||||
# Distribute total time evenly across tests for per-testcase timing.
|
|
||||||
per_test = round(elapsed / total, 6) if total else 0.0
|
|
||||||
suite = ET.Element(
|
|
||||||
"testsuite",
|
|
||||||
name=suite_name,
|
|
||||||
tests=str(total),
|
|
||||||
failures=str(failures),
|
|
||||||
errors="0",
|
|
||||||
skipped=str(ignored),
|
|
||||||
time=f"{elapsed:.6f}",
|
|
||||||
)
|
|
||||||
for name, result, message in tests:
|
|
||||||
case = ET.SubElement(
|
|
||||||
suite, "testcase",
|
|
||||||
name=name, classname=suite_name, time=f"{per_test:.6f}",
|
|
||||||
)
|
|
||||||
if result == "FAIL":
|
|
||||||
f = ET.SubElement(case, "failure", message=message)
|
|
||||||
f.text = message
|
|
||||||
elif result == "IGNORE":
|
|
||||||
ET.SubElement(case, "skipped", message=message)
|
|
||||||
return ET.ElementTree(suite)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
if len(sys.argv) < 3 or len(sys.argv) > 4:
|
|
||||||
print(f"usage: {sys.argv[0]} <suite_name> <output.xml> [elapsed_seconds]", file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
suite_name, output_file = sys.argv[1], sys.argv[2]
|
|
||||||
elapsed = float(sys.argv[3]) if len(sys.argv) == 4 else 0.0
|
|
||||||
tests, total, failures, ignored = parse_unity(sys.stdin.readlines())
|
|
||||||
tree = build_xml(suite_name, tests, total, failures, ignored, elapsed)
|
|
||||||
ET.indent(tree, space=" ")
|
|
||||||
tree.write(output_file, encoding="unicode", xml_declaration=True)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
Reference in New Issue
Block a user