pa1.2: add unit tests
This commit is contained in:
parent
9cca9de2a8
commit
e19e89f70e
8 changed files with 235 additions and 14 deletions
1
nemu/.gitignore
vendored
1
nemu/.gitignore
vendored
|
@ -5,6 +5,7 @@ build/
|
||||||
.cache/
|
.cache/
|
||||||
.direnv/
|
.direnv/
|
||||||
.config
|
.config
|
||||||
|
.config.old
|
||||||
.envrc
|
.envrc
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
|
|
||||||
|
|
|
@ -59,3 +59,14 @@ else
|
||||||
# Include rules to build NEMU
|
# Include rules to build NEMU
|
||||||
include $(NEMU_HOME)/scripts/native.mk
|
include $(NEMU_HOME)/scripts/native.mk
|
||||||
endif
|
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
|
||||||
|
|
|
@ -40,9 +40,12 @@
|
||||||
gnumake
|
gnumake
|
||||||
flex
|
flex
|
||||||
bison
|
bison
|
||||||
|
pkg-config
|
||||||
|
python3 # for testing
|
||||||
];
|
];
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
|
check
|
||||||
readline
|
readline
|
||||||
libllvm
|
libllvm
|
||||||
];
|
];
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
|
||||||
0[xX][0-9a-fA-F]+ { yylval = strtol(yytext, NULL, 16); return HEX_NUMBER; }
|
0[xX][0-9a-fA-F]+ { yylval = strtoul(yytext, NULL, 16); return HEX_NUMBER; }
|
||||||
[0-9]+ { yylval = atoi(yytext); return NUMBER; }
|
[0-9]+ { yylval = strtoul(yytext, NULL, 10); return NUMBER; }
|
||||||
[+\-*/()] { return *yytext; }
|
[+\-*/()] { return *yytext; }
|
||||||
[ \t] { }
|
[ \t] { }
|
||||||
. { printf("Unexpected character: %s\n", yytext); }
|
. { printf("Unexpected character: %s\n", yytext); }
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
extern int yylex(void);
|
extern int yylex(void);
|
||||||
void yyerror(uint32_t *result, const char *s) {
|
void yyerror(uint32_t *result, const char *err) {
|
||||||
fprintf(stderr, "Error: %s\n", s);
|
fprintf(stderr, "Error: %s\n", err);
|
||||||
}
|
}
|
||||||
%}
|
%}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
%start input
|
%start input
|
||||||
%define api.value.type { uint32_t }
|
%define api.value.type { uint32_t }
|
||||||
%parse-param { uint32_t *result }
|
%parse-param { uint32_t *result }
|
||||||
%left '+' '-'
|
%left '-' '+'
|
||||||
%left '*' '/'
|
%left '*' '/'
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -21,13 +21,19 @@ input
|
||||||
;
|
;
|
||||||
|
|
||||||
expression
|
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 { $$ = $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; }
|
| '-' number { $$ = -$2; }
|
||||||
| '(' expression ')' { $$ = $2; }
|
| '(' expression ')' { $$ = $2; }
|
||||||
| number { $$ = $1; }
|
|
||||||
|
|
||||||
number
|
number
|
||||||
: NUMBER
|
: NUMBER
|
||||||
|
|
|
@ -38,11 +38,11 @@ static int cmd_info(char *args);
|
||||||
static int cmd_info_r(char *args);
|
static int cmd_info_r(char *args);
|
||||||
static int cmd_info_w(char *args);
|
static int cmd_info_w(char *args);
|
||||||
|
|
||||||
static struct CMDTable {
|
static struct CommandTable {
|
||||||
const char *name;
|
const char *name;
|
||||||
const char *description;
|
const char *description;
|
||||||
int (*handler)(char *);
|
int (*handler)(char *);
|
||||||
struct CMDTable *subcommand;
|
struct CommandTable *subcommand;
|
||||||
int nr_subcommand;
|
int nr_subcommand;
|
||||||
} cmd_info_table[] =
|
} cmd_info_table[] =
|
||||||
{
|
{
|
||||||
|
@ -191,7 +191,8 @@ static int cmd_x(char *args) {
|
||||||
word_t n = parse_uint(arg, &res);
|
word_t n = parse_uint(arg, &res);
|
||||||
if (!res)
|
if (!res)
|
||||||
goto wrong_usage;
|
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);
|
word_t addr = parse_expr(arg, &res);
|
||||||
if (!res)
|
if (!res)
|
||||||
goto wrong_usage;
|
goto wrong_usage;
|
||||||
|
@ -205,7 +206,7 @@ static int cmd_x(char *args) {
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
wrong_usage:
|
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: <expr>]\n");
|
printf("Usage: x [N: uint] [EXPR: <expr>]\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -230,7 +231,7 @@ wrong_usage:
|
||||||
return 0;
|
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 cur_nr_cmd) {
|
||||||
int i;
|
int i;
|
||||||
char *arg = strtok(NULL, " ");
|
char *arg = strtok(NULL, " ");
|
||||||
|
@ -270,7 +271,7 @@ static int cmd_help(char *args) {
|
||||||
printf("-- %s\n", cmd_table[i].description);
|
printf("-- %s\n", cmd_table[i].description);
|
||||||
// Print available subcommands
|
// Print available subcommands
|
||||||
for (int j = 0; j < cmd_table[i].nr_subcommand; j++) {
|
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,
|
printf(" > %s -- %s\n", sub_cmd_table[j].name,
|
||||||
sub_cmd_table[j].description);
|
sub_cmd_table[j].description);
|
||||||
}
|
}
|
||||||
|
|
8
nemu/tests/Makefile
Normal file
8
nemu/tests/Makefile
Normal file
|
@ -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
|
191
nemu/tests/expr_test.c
Normal file
191
nemu/tests/expr_test.c
Normal file
|
@ -0,0 +1,191 @@
|
||||||
|
#include "sys/types.h"
|
||||||
|
#include "unistd.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <check.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <addrexp.h>
|
||||||
|
#include <addrexp_lex.h>
|
||||||
|
|
||||||
|
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 <stdio.h>\n"
|
||||||
|
"#include <stdint.h>\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;
|
||||||
|
}
|
Loading…
Reference in a new issue