diff --git a/cmds/dump.c b/cmds/dump.c index 26fecd346..174828499 100644 --- a/cmds/dump.c +++ b/cmds/dump.c @@ -1810,3 +1810,68 @@ int command_dump(int argc, char *argv[], struct uftrace_opts *opts) return ret; } + +#ifdef UNIT_TEST +TEST_CASE(dump_command1) +{ + struct uftrace_opts opts = { + .dirname = "dump-file-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + struct uftrace_raw_dump dump = { + .ops = { + .header = dump_raw_header, + .task_start = dump_raw_task_start, + .inverted_time = dump_raw_inverted_time, + .task_rstack = dump_raw_task_rstack, + .task_event = dump_raw_task_event, + .kernel_start = dump_raw_kernel_start, + .cpu_start = dump_raw_cpu_start, + .kernel_func = dump_raw_kernel_rstack, + .kernel_event = dump_raw_kernel_event, + .lost = dump_raw_kernel_lost, + .perf_start = dump_raw_perf_start, + .perf_event = dump_raw_perf_event, + }, + }; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("dump each file contents\n"); + do_dump_file(&dump.ops, &opts, &handle); + + release_test_data(&opts, &handle); + return TEST_OK; +} + +TEST_CASE(dump_command2) +{ + struct uftrace_opts opts = { + .dirname = "dump-replay-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + struct uftrace_raw_dump dump = { + .ops = { + .header = dump_chrome_header, + .task_rstack = dump_chrome_task_rstack, + .kernel_func = dump_chrome_kernel_rstack, + .perf_event = dump_chrome_perf_event, + .footer = dump_chrome_footer, + }, + }; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("dump contents in time order\n"); + do_dump_replay(&dump.ops, &opts, &handle); + + release_test_data(&opts, &handle); + return TEST_OK; +} +#endif /* UNIT_TEST */ diff --git a/cmds/graph.c b/cmds/graph.c index c10519ff1..2c667e42b 100644 --- a/cmds/graph.c +++ b/cmds/graph.c @@ -1030,3 +1030,54 @@ int command_graph(int argc, char *argv[], struct uftrace_opts *opts) return 0; } + +#ifdef UNIT_TEST +TEST_CASE(graph_command) +{ + struct uftrace_opts opts = { + .dirname = "graph-cmd-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + struct session_graph *graph; + struct graph_backtrace *bt, *btmp; + char *func; + int ret = 0; + + func = "_start"; + full_graph = true; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("construct full function graph\n"); + build_graph(&opts, &handle, func); + + graph = graph_list; + while (graph && !uftrace_done) { + pr_dbg("print graph for %s\n", graph->func); + ret += print_graph(graph, &opts); + graph = graph->next; + } + TEST_NE(ret, 0); + + while (graph_list) { + graph = graph_list; + graph_list = graph->next; + + pr_dbg("destroy graph for %s\n", graph->func); + free(graph->func); + list_for_each_entry_safe(bt, btmp, &graph->bt_list, list) { + list_del(&bt->list); + free(bt); + } + graph_destroy(&graph->ug); + free(graph); + } + graph_remove_task(); + + release_test_data(&opts, &handle); + return TEST_OK; +} +#endif /* UNIT_TEST */ diff --git a/cmds/info.c b/cmds/info.c index 1949f9cad..2be7a91a8 100644 --- a/cmds/info.c +++ b/cmds/info.c @@ -1269,3 +1269,32 @@ int command_info(int argc, char *argv[], struct uftrace_opts *opts) return 0; } + +#ifdef UNIT_TEST +TEST_CASE(info_command) +{ + struct uftrace_opts opts = { + .dirname = "info-cmd-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("process info section in the data\n"); + process_uftrace_info(&handle, &opts, print_info, NULL); + + if (handle.hdr.feat_mask & PERF_EVENT) { + if (setup_perf_data(&handle) == 0) + update_perf_task_comm(&handle); + } + + pr_dbg("print task info in the data\n"); + print_task_info(&handle); + + release_test_data(&opts, &handle); + return TEST_OK; +} +#endif /* UNIT_TEST */ diff --git a/cmds/replay.c b/cmds/replay.c index 060bbe247..e2020f2b3 100644 --- a/cmds/replay.c +++ b/cmds/replay.c @@ -1180,3 +1180,77 @@ int command_replay(int argc, char *argv[], struct uftrace_opts *opts) return ret; } + +#ifdef UNIT_TEST +static const char *record_type_str(struct uftrace_record *rec) +{ + switch (rec->type) { + case UFTRACE_ENTRY: + return "ENTRY"; + case UFTRACE_EXIT: + return "EXIT"; + case UFTRACE_LOST: + return "LOST"; + case UFTRACE_EVENT: + return "EVENT"; + default: + break; + } + return "UNKNOWN"; +} + +TEST_CASE(replay_command) +{ + struct uftrace_opts opts = { + .dirname = "replay-graph-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + struct uftrace_task_reader *task; + uint64_t prev_time = 0; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + setup_field(&output_fields, &opts, &setup_default_field, field_table, + ARRAY_SIZE(field_table)); + if (peek_rstack(&handle, &task) == 0) + print_header(&output_fields, "#", "FUNCTION", 1, false); + if (!list_empty(&output_fields)) + pr_out("\n"); + + pr_dbg("replay test data in graph format\n"); + while (read_rstack(&handle, &task) == 0) { + struct uftrace_record *rstack = task->rstack; + uint64_t curr_time = rstack->time; + + if (!fstack_check_opts(task, &opts)) { + pr_dbg("task=%d time=%lu skip\n", task->tid, rstack->time); + continue; + } + + pr_dbg("task=%d time=%lu depth=%d type=%s\n", task->tid, rstack->time, + rstack->depth, record_type_str(rstack)); + + /* + * data sanity check: timestamp should be ordered. + * But print_graph_rstack() may change task->rstack + * during fstack_skip(). So check the timestamp here. + */ + if (curr_time) { + if (prev_time > curr_time) + print_warning(task); + prev_time = rstack->time; + } + + /* this will merge adjacent ENTRY and EXIT */ + TEST_EQ(print_graph_rstack(&handle, task, &opts), 0); + } + + print_remaining_stack(&opts, &handle); + + release_test_data(&opts, &handle); + return TEST_OK; +} +#endif /* UNIT_TEST */ diff --git a/cmds/report.c b/cmds/report.c index e3facfc04..e1321c4dd 100644 --- a/cmds/report.c +++ b/cmds/report.c @@ -554,3 +554,54 @@ int command_report(int argc, char *argv[], struct uftrace_opts *opts) return 0; } +#ifdef UNIT_TEST +TEST_CASE(report_command1) +{ + struct uftrace_opts opts = { + .dirname = "report-func-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + char *sort_keys; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("report setup sort key\n"); + sort_keys = convert_sort_keys(opts.sort_keys, AVG_TOTAL); + TEST_EQ(report_setup_sort(sort_keys), 1); + + pr_dbg("report functions\n"); + report_functions(&handle, &opts); + + release_test_data(&opts, &handle); + free(sort_keys); + return TEST_OK; +} + +TEST_CASE(report_command2) +{ + struct uftrace_opts opts = { + .dirname = "report-task-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + char *sort_keys; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("report setup sort key\n"); + sort_keys = convert_sort_keys("self", AVG_SELF); + TEST_EQ(report_setup_sort(sort_keys), 1); + + pr_dbg("report task\n"); + report_task(&handle, &opts); + + release_test_data(&opts, &handle); + free(sort_keys); + return TEST_OK; +} +#endif /* UNIT_TEST */ diff --git a/cmds/tui.c b/cmds/tui.c index 5389dd0c4..62ea102dd 100644 --- a/cmds/tui.c +++ b/cmds/tui.c @@ -3068,6 +3068,41 @@ int command_tui(int argc, char *argv[], struct uftrace_opts *opts) return 0; } +#ifdef UNIT_TEST +TEST_CASE(tui_command) +{ + struct uftrace_opts opts = { + .dirname = "tui-cmd-test", + .exename = read_exename(), + .max_stack = 10, + .depth = OPT_DEPTH_DEFAULT, + }; + struct uftrace_data handle; + struct uftrace_task_reader *task; + + TEST_EQ(prepare_test_data(&opts, &handle), 0); + + pr_dbg("construct data structure for TUI\n"); + tui_setup(&handle, &opts); + + while (read_rstack(&handle, &task) == 0) { + struct uftrace_record *rec = task->rstack; + + TEST_NE(fstack_check_opts(task, &opts), 0); + TEST_NE(fstack_check_filter(task), 0); + TEST_EQ(build_tui_node(task, rec, &opts), 0); + + fstack_check_filter_done(task); + } + add_remaining_node(&opts, &handle); + + tui_cleanup(); + + release_test_data(&opts, &handle); + return TEST_OK; +} +#endif /* UNIT_TEST */ + #else /* !HAVE_LIBNCURSES */ #include "uftrace.h" diff --git a/uftrace.h b/uftrace.h index b483b177b..5c3c06d33 100644 --- a/uftrace.h +++ b/uftrace.h @@ -647,4 +647,8 @@ struct uftrace_event { "\n" \ "\n" +/* for unit tests */ +int prepare_test_data(struct uftrace_opts *opts, struct uftrace_data *handle); +int release_test_data(struct uftrace_opts *opts, struct uftrace_data *handle); + #endif /* UFTRACE_H */ diff --git a/utils/data-file.c b/utils/data-file.c index cf456beca..000b21f5a 100644 --- a/utils/data-file.c +++ b/utils/data-file.c @@ -613,9 +613,9 @@ void __close_data_file(struct uftrace_opts *opts, struct uftrace_data *handle, b #ifdef UNIT_TEST #define TEST_SESSION_ID "test-session-id" #define TEST_EXIT_STATUS 37 -#define TEST_INFO_MASK ((1U << EXE_NAME) | (1U << EXIT_STATUS)) +#define TEST_INFO_MASK (EXE_NAME | EXIT_STATUS | TASKINFO) -static int create_data(struct uftrace_opts *opts) +static int create_test_data(struct uftrace_opts *opts) { struct uftrace_msg_sess sess_msg = { .task = { .time = 100, .pid = 10 }, @@ -636,6 +636,84 @@ static int create_data(struct uftrace_opts *opts) .sid = TEST_SESSION_ID, .base_addr = 0x123000, }; + struct uftrace_record parent_funcs[] = { + /* ignore symbols for now */ + { + .time = 201, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 0, + }, + { + .time = 202, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 1, + }, + { + .time = 203, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 2, + }, + { + .time = 204, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 3, + }, + { + .time = 205, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 3, + }, + { + .time = 206, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 2, + }, + { + .time = 207, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 1, + }, + { + .time = 208, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 0, + }, + }; + struct uftrace_record child_funcs[] = { + { + .time = 301, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 0, + }, + { + .time = 302, + .type = UFTRACE_ENTRY, + .magic = RECORD_MAGIC, + .depth = 1, + }, + { + .time = 303, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 1, + }, + { + .time = 304, + .type = UFTRACE_EXIT, + .magic = RECORD_MAGIC, + .depth = 0, + }, + }; + char *filename = NULL; FILE *fp; @@ -654,10 +732,15 @@ static int create_data(struct uftrace_opts *opts) creat(filename, 0644); free(filename); - /* open_data_file() requires at least one non-empty data file */ TEST_GE(asprintf(&filename, "%s/%d.dat", opts->dirname, task_msg.pid), 0); fp = fopen(filename, "w"); - fprintf(fp, "%s\n", "empty file"); + fwrite(parent_funcs, sizeof(*parent_funcs), ARRAY_SIZE(parent_funcs), fp); + fclose(fp); + free(filename); + + TEST_GE(asprintf(&filename, "%s/%d.dat", opts->dirname, fork_msg.tid), 0); + fp = fopen(filename, "w"); + fwrite(child_funcs, sizeof(*child_funcs), ARRAY_SIZE(child_funcs), fp); fclose(fp); free(filename); @@ -666,11 +749,56 @@ static int create_data(struct uftrace_opts *opts) return 0; } -static int remove_data(struct uftrace_opts *opts) +static int remove_test_data(struct uftrace_opts *opts) { return remove_directory(opts->dirname); } +int prepare_test_data(struct uftrace_opts *opts, struct uftrace_data *handle) +{ + FILE *dev_null; + + if (create_test_data(opts) < 0) + return -1; + + dev_null = fopen("/dev/null", "w+"); + if (dev_null == NULL) { + remove_test_data(opts); + return -1; + } + + /* prevent any output message during the test */ + outfp = dev_null; + + open_data_file(opts, handle); + fstack_setup_filters(opts, handle); + + if (handle->sessions.first == NULL) { + close_data_file(opts, handle); + remove_test_data(opts); + return -1; + } + handle->sessions.first->sym_info.kernel_base = -1ULL; + + return 0; +} + +int release_test_data(struct uftrace_opts *opts, struct uftrace_data *handle) +{ + FILE *dev_null = outfp; + + close_data_file(opts, handle); + + outfp = stdout; + fclose(dev_null); + + if (remove_test_data(opts) < 0) { + pr_dbg("failed to remove data\n"); + return -1; + } + return 0; +} + TEST_CASE(data_basic) { struct uftrace_opts opts = { @@ -683,7 +811,7 @@ TEST_CASE(data_basic) struct uftrace_task *t; pr_dbg("create test data\n"); - if (create_data(&opts) < 0) + if (create_test_data(&opts) < 0) return TEST_SKIP; open_data_file(&opts, &handle); @@ -715,7 +843,7 @@ TEST_CASE(data_basic) close_data_file(&opts, &handle); pr_dbg("delete test data\n"); - if (remove_data(&opts) < 0) + if (remove_test_data(&opts) < 0) pr_dbg("failed to remove data\n"); return TEST_OK;