From e19e89f70e4fbd6a9b2998438a4619145d439a88 Mon Sep 17 00:00:00 2001 From: xinyangli Date: Sat, 13 Jan 2024 10:38:20 +0800 Subject: [PATCH] pa1.2: add unit tests --- nemu/.gitignore | 1 + nemu/Makefile | 11 ++ nemu/flake.nix | 3 + nemu/src/monitor/sdb/addrexp.l | 4 +- nemu/src/monitor/sdb/addrexp.y | 18 ++-- nemu/src/monitor/sdb/sdb.c | 13 +-- nemu/tests/Makefile | 8 ++ nemu/tests/expr_test.c | 191 +++++++++++++++++++++++++++++++++ 8 files changed, 235 insertions(+), 14 deletions(-) create mode 100644 nemu/tests/Makefile create mode 100644 nemu/tests/expr_test.c diff --git a/nemu/.gitignore b/nemu/.gitignore index 7faa353..77fe490 100644 --- a/nemu/.gitignore +++ b/nemu/.gitignore @@ -5,6 +5,7 @@ build/ .cache/ .direnv/ .config +.config.old .envrc compile_commands.json diff --git a/nemu/Makefile b/nemu/Makefile index c94e04f..03c8a4e 100644 --- a/nemu/Makefile +++ b/nemu/Makefile @@ -59,3 +59,14 @@ else # Include rules to build NEMU include $(NEMU_HOME)/scripts/native.mk endif + +.PHONY: test + +include $(NEMU_HOME)/tests/Makefile +all-tests: TEST_OBJS = $(filter-out $(OBJ_DIR)/src/nemu-main.o, $(OBJS)) +all-tests: CFLAGS += $(shell pkg-config --cflags check) +all-tests: LDFLAGS += $(shell pkg-config --libs check) +all-tests: $(TEST_SRCS:%.c=$(OBJ_DIR)/%) + +test: all-tests + @$(OBJ_DIR)/tests/expr_test diff --git a/nemu/flake.nix b/nemu/flake.nix index 95ec4cb..47bb216 100644 --- a/nemu/flake.nix +++ b/nemu/flake.nix @@ -40,9 +40,12 @@ gnumake flex bison + pkg-config + python3 # for testing ]; buildInputs = [ + check readline libllvm ]; diff --git a/nemu/src/monitor/sdb/addrexp.l b/nemu/src/monitor/sdb/addrexp.l index cb7dea4..137d042 100644 --- a/nemu/src/monitor/sdb/addrexp.l +++ b/nemu/src/monitor/sdb/addrexp.l @@ -5,8 +5,8 @@ %% -0[xX][0-9a-fA-F]+ { yylval = strtol(yytext, NULL, 16); return HEX_NUMBER; } -[0-9]+ { yylval = atoi(yytext); return NUMBER; } +0[xX][0-9a-fA-F]+ { yylval = strtoul(yytext, NULL, 16); return HEX_NUMBER; } +[0-9]+ { yylval = strtoul(yytext, NULL, 10); return NUMBER; } [+\-*/()] { return *yytext; } [ \t] { } . { printf("Unexpected character: %s\n", yytext); } diff --git a/nemu/src/monitor/sdb/addrexp.y b/nemu/src/monitor/sdb/addrexp.y index 68659ef..6aadc7a 100644 --- a/nemu/src/monitor/sdb/addrexp.y +++ b/nemu/src/monitor/sdb/addrexp.y @@ -3,8 +3,8 @@ #include #include extern int yylex(void); - void yyerror(uint32_t *result, const char *s) { - fprintf(stderr, "Error: %s\n", s); + void yyerror(uint32_t *result, const char *err) { + fprintf(stderr, "Error: %s\n", err); } %} @@ -12,7 +12,7 @@ %start input %define api.value.type { uint32_t } %parse-param { uint32_t *result } -%left '+' '-' +%left '-' '+' %left '*' '/' %% @@ -21,13 +21,19 @@ input ; expression - : expression '+' expression { $$ = $1 + $3; } + : number { $$ = $1; } + | expression '+' expression { $$ = $1 + $3; } | expression '-' expression { $$ = $1 - $3; } | expression '*' expression { $$ = $1 * $3; } - | expression '/' expression { $$ = $1 / $3; } + | expression '/' expression { + if($3 == 0) { + fprintf(stderr, "Error: divide by zero at %u / %u\n", $1, $3); + YYABORT; + }; + $$ = $1 / $3; + } | '-' number { $$ = -$2; } | '(' expression ')' { $$ = $2; } - | number { $$ = $1; } number : NUMBER diff --git a/nemu/src/monitor/sdb/sdb.c b/nemu/src/monitor/sdb/sdb.c index be1e25e..82b39b0 100644 --- a/nemu/src/monitor/sdb/sdb.c +++ b/nemu/src/monitor/sdb/sdb.c @@ -38,11 +38,11 @@ static int cmd_info(char *args); static int cmd_info_r(char *args); static int cmd_info_w(char *args); -static struct CMDTable { +static struct CommandTable { const char *name; const char *description; int (*handler)(char *); - struct CMDTable *subcommand; + struct CommandTable *subcommand; int nr_subcommand; } cmd_info_table[] = { @@ -191,7 +191,8 @@ static int cmd_x(char *args) { word_t n = parse_uint(arg, &res); if (!res) goto wrong_usage; - arg = strtok(NULL, " "); + // No deliminter here, just pass all the remain argument to `parse_expr()` + arg = strtok(NULL, ""); word_t addr = parse_expr(arg, &res); if (!res) goto wrong_usage; @@ -205,7 +206,7 @@ static int cmd_x(char *args) { return 0; wrong_usage: - printf("Invalid argument for command x: %s\n", args); + printf("Invalid argument for command x: %s\n", arg); printf("Usage: x [N: uint] [EXPR: ]\n"); return 0; } @@ -230,7 +231,7 @@ wrong_usage: return 0; } -static int cmd_help_print(char *args, struct CMDTable *cur_cmd_table, +static int cmd_help_print(char *args, struct CommandTable *cur_cmd_table, int cur_nr_cmd) { int i; char *arg = strtok(NULL, " "); @@ -270,7 +271,7 @@ static int cmd_help(char *args) { printf("-- %s\n", cmd_table[i].description); // Print available subcommands for (int j = 0; j < cmd_table[i].nr_subcommand; j++) { - struct CMDTable *sub_cmd_table = cmd_table[i].subcommand; + struct CommandTable *sub_cmd_table = cmd_table[i].subcommand; printf(" > %s -- %s\n", sub_cmd_table[j].name, sub_cmd_table[j].description); } diff --git a/nemu/tests/Makefile b/nemu/tests/Makefile new file mode 100644 index 0000000..7709a27 --- /dev/null +++ b/nemu/tests/Makefile @@ -0,0 +1,8 @@ +TEST_SRCS += tests/expr_test.c + +$(OBJ_DIR)/%: %.c $(TEST_OBJS) app + @mkdir -p $(dir $@) + @echo + CC $< + @$(CC) $(CFLAGS) -o $@.o -c $< + @echo + LD $@ + @$(LD) $(LIBS) $(LDFLAGS) -o $@ $(TEST_OBJS) $@.o diff --git a/nemu/tests/expr_test.c b/nemu/tests/expr_test.c new file mode 100644 index 0000000..87c1882 --- /dev/null +++ b/nemu/tests/expr_test.c @@ -0,0 +1,191 @@ +#include "sys/types.h" +#include "unistd.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +char buf[65536] = {}, ref_buf[65536] = {}; +static char code_buf[65536 + 128] = {}; // a little larger than `buf` +const int buf_start_pos = 0; +char *buf_ptr = buf + buf_start_pos, *ref_buf_ptr = ref_buf; +static char *code_format = "#include \n" + "#include \n" + "int main() { " + " uint32_t result = %s; " + " printf(\"%%u\", result); " + " return 0; " + "}"; + +void gen(char c) { + *(buf_ptr++) = c; + *(ref_buf_ptr++) = c; +} + +void gen_num(void) { + uint32_t num = rand(); + int len = 0, ref_len = 0; + switch (rand() % 3) { + case 0: + len = snprintf(buf_ptr, 100, "%u", num); + ref_len = snprintf(ref_buf_ptr, 100, "%uU", num); + break; + case 1: + len = snprintf(buf_ptr, 100, "0x%x", num); + ref_len = snprintf(ref_buf_ptr, 100, "%uU", num); + break; + case 2: + len = snprintf(buf_ptr, 100, "%d", num); + ref_len = snprintf(ref_buf_ptr, 100, "%d", num); + break; + default: + assert(0); + } + buf_ptr += len; + ref_buf_ptr += ref_len; +} + +void gen_rand_op(void) { + switch (rand() % 4) { + case 0: + gen('+'); + break; + case 1: + gen('-'); + break; + case 2: + gen('*'); + break; + case 3: + gen('/'); + break; + } +} + +void gen_rand_expr(void) { + int choice = rand() % 3; + if (buf_ptr - buf > 2000) { + choice = 0; + } + switch (choice) { + case 0: + gen_num(); + break; + case 1: + gen('('); + gen_rand_expr(); + gen(')'); + break; + default: + gen_rand_expr(); + gen(' '); + gen_rand_op(); + gen(' '); + gen_rand_expr(); + break; + } +} + +START_TEST(test_expr_random_100) { + srand(time(0) + _i * 100); + gen_rand_expr(); + + sprintf(code_buf, code_format, ref_buf); + + FILE *fp = fopen("/tmp/.code.c", "w"); + ck_assert(fp != NULL); + fputs(code_buf, fp); + fclose(fp); + + int ret = + system("gcc /tmp/.code.c -Werror=div-by-zero -o /tmp/.expr 2>/dev/null"); + if (ret == 256) { + // Probably devide by zero. Skip + goto clean_up; + } + ck_assert_msg(!ret, "system ret: %d, error: %s", ret, strerror(ret)); + + fp = popen("/tmp/.expr", "r"); + ck_assert(fp != NULL); + + uint32_t reference; + ret = fscanf(fp, "%u", &reference); + ck_assert(ret == 1); + pclose(fp); + // fprintf(stderr, "\n\tbuf = %s\n\taddr = %u, reference = %u", buf, addr, + // reference); + + yy_scan_string(buf + buf_start_pos); + uint32_t addr; + ck_assert(!yyparse(&addr)); + yylex_destroy(); + + ck_assert_msg(addr == reference, + "\n\tbuf = %s\n\t(addr = %u) != (reference = %u)\n", buf, addr, + reference); + +clean_up: + while (buf_ptr != buf + buf_start_pos) { + *(--buf_ptr) = '\0'; + } + while (ref_buf_ptr != ref_buf) { + *(--ref_buf_ptr) = '\0'; + } +} +END_TEST + +struct { + const char *expr; + uint32_t reference; +} exprs[] = { + {"-1", 0xFFFFFFFFU}, + {"-0x1", 0xFFFFFFFFU}, + {"0--1", 0x1}, + {"0--0x1", 0x1}, +}; +START_TEST(test_expr_negative_operand) { + yy_scan_string(exprs[_i].expr); + uint32_t addr; + ck_assert(!yyparse(&addr)); + yylex_destroy(); + + ck_assert_msg(addr == exprs[_i].reference, + "\n\texpr = %s\n\t(addr = %u) != (reference = %u)\n", exprs[_i].expr, + addr, exprs[_i].reference); +} +END_TEST + +Suite *expr_suite(void) { + Suite *s; + TCase *tc_core; + + s = suite_create("Expr test"); + tc_core = tcase_create("Core"); + + tcase_add_loop_test(tc_core, test_expr_random_100, 0, 20); + tcase_add_loop_test(tc_core, test_expr_negative_operand, 0, + sizeof(exprs) / sizeof(exprs[0])); + suite_add_tcase(s, tc_core); + + return s; +} + +int main(void) { + int number_failed; + Suite *s; + SRunner *sr; + + s = expr_suite(); + sr = srunner_create(s); + + srunner_run_all(sr, CK_NORMAL); + number_failed = srunner_ntests_failed(sr); + srunner_free(sr); + + return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +}