13 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
2e0668d4f8 docs: add unit test documentation to README.md and doc/testing.md
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/dd8af74a-9ecb-485b-851a-96b38b3cfc79

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 22:31:37 +00:00
copilot-swe-agent[bot]
13c6f6291c fix: add time attributes to JUnit XML to eliminate NaNms in test reporter
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/8d948e86-1d59-496f-a317-7fd9294fcad8

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 21:53:27 +00:00
copilot-swe-agent[bot]
73bda79fe6 CI: produce JUnit XML from Unity tests for GitHub test reporting
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/0bc0c851-36e7-4d13-a393-517477e66c73

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 21:41:53 +00:00
Thomas Arp
d810782392 test run 2026-04-22 23:32:35 +02:00
copilot-swe-agent[bot]
063bd86a69 Use working-directory for build and test steps
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/02b27398-2736-4f5c-bbd9-b212340323f8

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 21:29:01 +00:00
copilot-swe-agent[bot]
2ef139a7d4 Add test step to CI build workflow
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/88e8bf39-a94e-440e-835a-0bb9c62fe95d

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 21:22:17 +00:00
copilot-swe-agent[bot]
04a99b3ebf Add tests/Makefile to .gitignore and untrack generated file
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/ff469e4b-880b-4326-85fd-1abc76bd80bc

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-22 20:55:59 +00:00
Thomas Arp
b77f4ad3ca Propagate failure to calling shell
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 22:48:56 +02:00
Thomas Arp
9343e38860 Comment should match code
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 22:47:51 +02:00
Thomas Arp
48030f3c2f Only set flags on compilers that recognize them
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 22:47:17 +02:00
Thomas Arp
83d109e7ae Do not include crypt when not available
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 22:45:41 +02:00
copilot-swe-agent[bot]
6566ad5164 Add test binaries to .gitignore and untrack them
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/d5b86db2-e5ab-4729-b8b1-2ca7cf01c6b9

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-21 22:20:58 +00:00
copilot-swe-agent[bot]
fff58ccab3 Add Unity-based unit test infrastructure (Phase 1: 117 tests passing)
Agent-Logs-Url: https://github.com/tbamud/tbamud/sessions/d5b86db2-e5ab-4729-b8b1-2ca7cf01c6b9

Co-authored-by: welcor <357770+welcor@users.noreply.github.com>
2026-04-21 22:19:11 +00:00
7 changed files with 25 additions and 330 deletions

View File

@@ -3,7 +3,7 @@ name: C/C++ CI
on:
push:
branches: [ "master" ]
pull_request_target:
pull_request:
branches: [ "master" ]
permissions:

View File

@@ -1024,27 +1024,17 @@ ACMD(do_mdoor)
}
if ((rm = get_room(target)) == NULL) {
mob_log(ch, "mdoor: invalid target (arg == %s)", target);
mob_log(ch, "mdoor: invalid target");
return;
}
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
char dirs_str[256];
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);
mob_log(ch, "mdoor: invalid direction");
return;
}
if ((fd = search_block(field, door_field, FALSE)) == -1) {
char fields_str[256];
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);
mob_log(ch, "odoor: invalid field");
return;
}
@@ -1091,10 +1081,8 @@ ACMD(do_mdoor)
case 5: /* room */
if ((to_room = real_room(atoi(value))) != NOWHERE)
newexit->to_room = to_room;
else {
newexit->to_room = NOWHERE;
mob_log(ch, "mdoor: invalid door target (arg == %s)", value);
}
else
mob_log(ch, "mdoor: invalid door target");
break;
}
}

View File

@@ -625,27 +625,17 @@ static OCMD(do_odoor)
}
if ((rm = get_room(target)) == NULL) {
obj_log(obj, "odoor: invalid target (arg == %s)", target);
obj_log(obj, "odoor: invalid target");
return;
}
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
char dirs_str[256];
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);
obj_log(obj, "odoor: invalid direction");
return;
}
if ((fd = search_block(field, door_field, FALSE)) == -1) {
char fields_str[256];
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);
obj_log(obj, "odoor: invalid field");
return;
}
@@ -692,10 +682,8 @@ static OCMD(do_odoor)
case 5: /* room */
if ((to_room = real_room(atoi(value))) != NOWHERE)
newexit->to_room = to_room;
else {
newexit->to_room = NOWHERE;
obj_log(obj, "odoor: invalid door target (arg == %s)", value);
}
else
obj_log(obj, "odoor: invalid door target");
break;
}
}

View File

@@ -1722,5 +1722,5 @@ void var_subst(void *go, struct script_data *sc, trig_data *trig,
left -= len;
} /* else if *p .. */
} /* while *p .. */
*buf = '\0';
buf[sizeof(buf) - 1] = '\0';
}

View File

@@ -224,27 +224,17 @@ WCMD(do_wdoor)
}
if ((rm = get_room(target)) == NULL) {
wld_log(room, "wdoor: invalid target (arg == %s)", target);
wld_log(room, "wdoor: invalid target");
return;
}
if ((dir = search_block(direction, dirs, FALSE)) == -1) {
char dirs_str[256];
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);
wld_log(room, "wdoor: invalid direction");
return;
}
if ((fd = search_block(field, door_field, FALSE)) == -1) {
char fields_str[256];
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);
wld_log(room, "wdoor: invalid field");
return;
}
@@ -291,10 +281,8 @@ WCMD(do_wdoor)
case 5: /* room */
if ((to_room = real_room(atoi(value))) != NOWHERE)
newexit->to_room = to_room;
else {
newexit->to_room = NOWHERE;
wld_log(room, "wdoor: invalid door target (arg == %s)", value);
}
else
wld_log(room, "wdoor: invalid door target");
break;
}
}

View File

@@ -41,7 +41,7 @@
/* local (file scope) functions */
static int perform_dupe_check(struct descriptor_data *d);
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 bool perform_new_char_dupe_check(struct descriptor_data *d);
/* sort_commands utility */
@@ -668,10 +668,9 @@ ACMD(do_alias)
* commands. */
#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_block *qtmp;
char *tokens[NUM_TOKENS], *temp, *write_point;
char buf2[MAX_RAW_INPUT_LENGTH], buf[MAX_RAW_INPUT_LENGTH]; /* raw? */
int num_of_tokens = 0, num;
@@ -698,27 +697,16 @@ static int perform_complex_alias(struct txt_q *input_q, char *orig, struct alias
} else if (*temp == ALIAS_VAR_CHAR) {
temp++;
if ((num = *temp - '1') < num_of_tokens && num >= 0) {
if ((write_point - buf) + strlen(tokens[num]) >= MAX_RAW_INPUT_LENGTH)
goto overflow;
strcpy(write_point, tokens[num]);
strcpy(write_point, tokens[num]); /* strcpy: OK */
write_point += strlen(tokens[num]);
} else if (*temp == ALIAS_GLOB_CHAR) {
skip_spaces(&orig);
if ((write_point - buf) + strlen(orig) >= MAX_RAW_INPUT_LENGTH)
goto overflow;
strcpy(write_point, orig);
strcpy(write_point, orig); /* strcpy: OK */
write_point += strlen(orig);
} else {
if (write_point - buf + 2 >= MAX_RAW_INPUT_LENGTH)
goto overflow;
if ((*(write_point++) = *temp) == '$') /* redouble $ for act safety */
*(write_point++) = '$';
}
} else {
if (write_point - buf + 1 >= MAX_RAW_INPUT_LENGTH)
goto overflow;
} else if ((*(write_point++) = *temp) == '$') /* redouble $ for act safety */
*(write_point++) = '$';
} else
*(write_point++) = *temp;
}
}
*write_point = '\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;
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.
@@ -777,8 +755,7 @@ int perform_alias(struct descriptor_data *d, char *orig, size_t maxlen)
strlcpy(orig, a->replacement, maxlen);
return (0);
} else {
if (perform_complex_alias(&d->input, ptr, a) < 0)
send_to_char(d->character, "Alias expansion too long.\r\n");
perform_complex_alias(&d->input, ptr, a);
return (1);
}
}

View File

@@ -2,7 +2,6 @@
* @file test_interpreter.c
* Unit tests for pure string-handling functions in src/interpreter.c:
* 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"
@@ -11,7 +10,6 @@
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
extern FILE *logfile;
@@ -19,28 +17,6 @@ extern FILE *logfile;
void setUp(void) { logfile = stderr; }
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
* ========================================================= */
@@ -231,219 +207,6 @@ void test_one_word_leading_spaces_skipped(void)
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
* ========================================================= */
@@ -489,14 +252,5 @@ int main(void)
RUN_TEST(test_one_word_empty_quoted);
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();
}