#include "t.h" #include #include #include #include #ifndef TOBI_BIN #define TOBI_BIN "./tobi" #endif #ifndef TOBI_SRC_DIR #define TOBI_SRC_DIR "." #endif static char *empty_text(void) { char *buf = malloc(1); if (!buf) { abort(); } buf[0] = '\0'; return buf; } static int write_blob(const char *path, const unsigned char *data, size_t len) { FILE *fp = fopen(path, "wb"); if (!fp) { return 0; } int ok = fwrite(data, 1, len, fp) == len; return fclose(fp) == 0 && ok; } static char *read_text(const char *path) { FILE *fp = fopen(path, "rb"); if (!fp) { return empty_text(); } if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return empty_text(); } long n = ftell(fp); if (n < 0) { fclose(fp); return empty_text(); } rewind(fp); char *buf = malloc((size_t)n + 1u); if (!buf) { fclose(fp); abort(); } size_t got = fread(buf, 1, (size_t)n, fp); buf[got] = '\0'; fclose(fp); return buf; } static int write_all_fd(int fd, const unsigned char *data, size_t len) { size_t off = 0; while (off < len) { ssize_t n = write(fd, data + off, len - off); if (n < 0) { if (errno == EINTR) { continue; } return 0; } off += (size_t)n; } return 1; } static char *read_all_fd(int fd) { size_t cap = 4096; size_t len = 0; char *buf = malloc(cap); if (!buf) { abort(); } for (;;) { if (len + 1 == cap) { cap *= 2u; char *nb = realloc(buf, cap); if (!nb) { free(buf); abort(); } buf = nb; } ssize_t n = read(fd, buf + len, cap - len - 1u); if (n < 0) { if (errno == EINTR) { continue; } free(buf); return empty_text(); } if (n == 0) { break; } len += (size_t)n; } buf[len] = '\0'; return buf; } static char *run_capture_argv(const char *const argv[], const unsigned char *input, size_t input_len, int *status) { int out_pipe[2]; int in_pipe[2] = {-1, -1}; if (pipe(out_pipe) != 0) { *status = 99; return empty_text(); } if (input && pipe(in_pipe) != 0) { close(out_pipe[0]); close(out_pipe[1]); *status = 99; return empty_text(); } pid_t pid = fork(); if (pid < 0) { close(out_pipe[0]); close(out_pipe[1]); if (input) { close(in_pipe[0]); close(in_pipe[1]); } *status = 99; return empty_text(); } if (pid == 0) { if (input) { dup2(in_pipe[0], STDIN_FILENO); } else { int devnull = open("/dev/null", O_RDONLY); if (devnull >= 0) { dup2(devnull, STDIN_FILENO); close(devnull); } } dup2(out_pipe[1], STDOUT_FILENO); int devnull = open("/dev/null", O_WRONLY); if (devnull >= 0) { dup2(devnull, STDERR_FILENO); close(devnull); } close(out_pipe[0]); close(out_pipe[1]); if (input) { close(in_pipe[0]); close(in_pipe[1]); } execv(argv[0], (char *const *)argv); _exit(127); } close(out_pipe[1]); if (input) { close(in_pipe[0]); if (!write_all_fd(in_pipe[1], input, input_len)) { close(in_pipe[1]); close(out_pipe[0]); (void)waitpid(pid, NULL, 0); *status = 99; return empty_text(); } close(in_pipe[1]); } char *buf = read_all_fd(out_pipe[0]); close(out_pipe[0]); int rc = 0; if (waitpid(pid, &rc, 0) < 0) { free(buf); *status = 99; return empty_text(); } *status = WIFEXITED(rc) ? WEXITSTATUS(rc) : 99; return buf; } int t_cli(void) { int st = 0; char path[512]; snprintf(path, sizeof(path), "%s/sam/m0.aml", TOBI_SRC_DIR); const char *argv_default[] = {TOBI_BIN, path, NULL}; char *s = run_capture_argv(argv_default, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "method MTH0"); T_STR(s, "return Local0"); free(s); const char *argv_dis[] = {TOBI_BIN, "--dis", path, NULL}; s = run_capture_argv(argv_dis, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "0x0000"); T_STR(s, "MethodOp"); free(s); const char *argv_json[] = {TOBI_BIN, "--json", path, NULL}; s = run_capture_argv(argv_json, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "\"ir\""); T_STR(s, "\"kind\":\"method\""); T_STR(s, path); T_CHECK(strstr(s, "\"source\":\"raw\"") == NULL); free(s); const char *argv_dot[] = {TOBI_BIN, "--dot", path, NULL}; s = run_capture_argv(argv_dot, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "digraph tobi_cfg"); T_STR(s, "method"); free(s); const char *argv_ast[] = {TOBI_BIN, "--graph", "ast", path, NULL}; s = run_capture_argv(argv_ast, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "digraph tobi_ast"); T_STR(s, "MTH0"); free(s); const char *argv_ns[] = {TOBI_BIN, "--graph", "namespace", path, NULL}; s = run_capture_argv(argv_ns, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "digraph tobi_namespace"); T_STR(s, "MTH0"); free(s); const char *argv_full_labels[] = {TOBI_BIN, "--graph", "ast", "--graph-labels", "full", path, NULL}; s = run_capture_argv(argv_full_labels, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "source="); T_STR(s, "len="); free(s); const char *argv_no_labels[] = {TOBI_BIN, "--dot", "--graph-labels", "none", path, NULL}; s = run_capture_argv(argv_no_labels, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "digraph tobi_cfg"); T_CHECK(strstr(s, "MTH0") == NULL); T_CHECK(strstr(s, "off=0x") == NULL); free(s); { const char *out_path = "/tmp/tobi_cli_graph.dot"; const char *argv_out[] = {TOBI_BIN, "--dot", path, "-o", out_path, NULL}; s = run_capture_argv(argv_out, NULL, 0, &st); T_CHECK(st == 0); T_CHECK(s[0] == '\0'); free(s); s = read_text(out_path); T_STR(s, "digraph tobi_cfg"); free(s); } { unsigned char stdin_file[] = {0x14,0x0c,'M','T','H','0',0x01,0x70,0x0a,0x2a,0x60,0xa4,0x60}; const char *argv_stdin[] = {TOBI_BIN, "--json", "-", NULL}; s = run_capture_argv(argv_stdin, stdin_file, sizeof(stdin_file), &st); T_CHECK(st == 0); T_STR(s, "\"source\":\"\""); T_STR(s, "MTH0"); free(s); } { unsigned char user_file[] = {0x14,0x0c,'M','T','H','0',0x01,0x70,0x0a,0x2a,0x60,0xa4,0x60}; const char *space_path = "/tmp/tobi cli space.aml"; T_CHECK(write_blob(space_path, user_file, sizeof(user_file))); const char *argv_space[] = {TOBI_BIN, "--json", space_path, NULL}; s = run_capture_argv(argv_space, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, space_path); free(s); } { unsigned char user_file[] = {0x14,0x0c,'M','T','H','0',0x01,0x70,0x0a,0x2a,0x60,0xa4,0x60}; char long_path[240] = "/tmp/tobi_cli_"; size_t pos = strlen(long_path); memset(long_path + pos, 'a', 150); strcpy(long_path + pos + 150, ".aml"); T_CHECK(write_blob(long_path, user_file, sizeof(user_file))); const char *argv_long[] = {TOBI_BIN, "--json", long_path, NULL}; s = run_capture_argv(argv_long, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, long_path); free(s); } { unsigned char user_file[] = { 0x14,0x24,'L','L','0','0',0x01, 0x70,0x00,0x60, 0x70,0x0a,0x03,0x61, 0xa2,0x14,0x61, 0xa0,0x06,0x68,0x72,0x60,0x01,0x60, 0xa1,0x06,0x72,0x60,0x0a,0x02,0x60, 0x74,0x61,0x01,0x61, 0xa4,0x60 }; const char *user_path = "/tmp/tobi_cli_user_dot.aml"; T_CHECK(write_blob(user_path, user_file, sizeof(user_file))); const char *argv_user_dot[] = {TOBI_BIN, "--dot", user_path, NULL}; s = run_capture_argv(argv_user_dot, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "digraph tobi_cfg"); T_STR(s, "LL00"); T_STR(s, "while"); T_STR(s, "back"); free(s); } const char *argv_raw[] = {TOBI_BIN, "--raw", path, NULL}; s = run_capture_argv(argv_raw, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "checksum_sum=0x00"); T_STR(s, "checksum_valid=false"); free(s); { unsigned char use_file[] = {0x14,0x0d,'U','S','E','0',0x00,0xa4,'C','A','L','0',0x0a,0x05}; unsigned char cal_file[] = {0x14,0x08,'C','A','L','0',0x01,0xa4,0x68}; const char *use_path = "/tmp/tobi_cli_use.aml"; const char *cal_path = "/tmp/tobi_cli_cal.aml"; T_CHECK(write_blob(use_path, use_file, sizeof(use_file))); T_CHECK(write_blob(cal_path, cal_file, sizeof(cal_file))); const char *argv_multi[] = {TOBI_BIN, use_path, cal_path, NULL}; s = run_capture_argv(argv_multi, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "method USE0"); T_STR(s, "CAL0(0x5)"); free(s); } const char *argv_help[] = {TOBI_BIN, "--help", NULL}; s = run_capture_argv(argv_help, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "usage: tobi"); free(s); const char *argv_version[] = {TOBI_BIN, "--version", NULL}; s = run_capture_argv(argv_version, NULL, 0, &st); T_CHECK(st == 0); T_STR(s, "tobi 0.1.0"); free(s); const char *argv_conflict[] = {TOBI_BIN, "--json", "--dis", path, NULL}; s = run_capture_argv(argv_conflict, NULL, 0, &st); T_CHECK(st == 2); free(s); const char *argv_bad_opt[] = {TOBI_BIN, "--definitely-not-real", NULL}; s = run_capture_argv(argv_bad_opt, NULL, 0, &st); T_CHECK(st == 2); free(s); const char *argv_bad_graph[] = {TOBI_BIN, "--graph", "nope", path, NULL}; s = run_capture_argv(argv_bad_graph, NULL, 0, &st); T_CHECK(st == 2); free(s); const char *argv_bad_graph_labels[] = {TOBI_BIN, "--graph-labels", "verbose", path, NULL}; s = run_capture_argv(argv_bad_graph_labels, NULL, 0, &st); T_CHECK(st == 2); free(s); const char *argv_missing_graph[] = {TOBI_BIN, "--graph", NULL}; s = run_capture_argv(argv_missing_graph, NULL, 0, &st); T_CHECK(st == 2); free(s); snprintf(path, sizeof(path), "%s/sam/bad0.aml", TOBI_SRC_DIR); const char *argv_strict[] = {TOBI_BIN, "--strict", path, NULL}; s = run_capture_argv(argv_strict, NULL, 0, &st); T_CHECK(st != 0); free(s); return 0; }