mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-07 16:18:52 +00:00
Add animation detection support to test runner
This commit is contained in:
+33
-16
@@ -45,6 +45,7 @@ PER_THREAD_MEMORY : int : #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_S
|
|||||||
// The format is: `package.test_name,test_name_only,...`
|
// The format is: `package.test_name,test_name_only,...`
|
||||||
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
|
TEST_NAMES : string : #config(ODIN_TEST_NAMES, "")
|
||||||
// Show the fancy animated progress report.
|
// Show the fancy animated progress report.
|
||||||
|
// This requires terminal color support, as well as STDOUT to not be redirected to a file.
|
||||||
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
|
FANCY_OUTPUT : bool : #config(ODIN_TEST_FANCY, true)
|
||||||
// Copy failed tests to the clipboard when done.
|
// Copy failed tests to the clipboard when done.
|
||||||
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
|
USE_CLIPBOARD : bool : #config(ODIN_TEST_CLIPBOARD, false)
|
||||||
@@ -72,6 +73,7 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@(private) global_log_colors_disabled: bool
|
@(private) global_log_colors_disabled: bool
|
||||||
|
@(private) global_ansi_disabled: bool
|
||||||
|
|
||||||
JSON :: struct {
|
JSON :: struct {
|
||||||
total: int,
|
total: int,
|
||||||
@@ -219,7 +221,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
stdout := io.to_writer(os.stream_from_handle(os.stdout))
|
stdout := io.to_writer(os.stream_from_handle(os.stdout))
|
||||||
stderr := io.to_writer(os.stream_from_handle(os.stderr))
|
stderr := io.to_writer(os.stream_from_handle(os.stderr))
|
||||||
|
|
||||||
|
// The animations are only ever shown through STDOUT;
|
||||||
|
// STDERR is used exclusively for logging regardless of error level.
|
||||||
global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
|
global_log_colors_disabled = !terminal.color_enabled || !terminal.is_terminal(os.stderr)
|
||||||
|
global_ansi_disabled = !terminal.is_terminal(os.stdout)
|
||||||
|
|
||||||
|
should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
|
||||||
|
|
||||||
// -- Prepare test data.
|
// -- Prepare test data.
|
||||||
|
|
||||||
@@ -278,12 +285,12 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
total_done_count := 0
|
total_done_count := 0
|
||||||
total_test_count := len(internal_tests)
|
total_test_count := len(internal_tests)
|
||||||
|
|
||||||
when !FANCY_OUTPUT {
|
|
||||||
// This is strictly for updating the window title when the progress
|
// This is strictly for updating the window title when the progress
|
||||||
// report is disabled. We're otherwise able to depend on the call to
|
// report is disabled. We're otherwise able to depend on the call to
|
||||||
// `needs_to_redraw`.
|
// `needs_to_redraw`.
|
||||||
last_done_count := -1
|
last_done_count := -1
|
||||||
}
|
|
||||||
|
|
||||||
if total_test_count == 0 {
|
if total_test_count == 0 {
|
||||||
// Exit early.
|
// Exit early.
|
||||||
@@ -352,7 +359,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
|
fmt.assertf(alloc_error == nil, "Error allocating memory for test report: %v", alloc_error)
|
||||||
defer destroy_report(&report)
|
defer destroy_report(&report)
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
|
||||||
// We cannot make use of the ANSI save/restore cursor codes, because they
|
// We cannot make use of the ANSI save/restore cursor codes, because they
|
||||||
// work by absolute screen coordinates. This will cause unnecessary
|
// work by absolute screen coordinates. This will cause unnecessary
|
||||||
// scrollback if we print at the bottom of someone's terminal.
|
// scrollback if we print at the bottom of someone's terminal.
|
||||||
@@ -376,7 +383,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
|
assert(len(thread_count_status_string) > 0, "Error allocating thread count status string.")
|
||||||
}
|
}
|
||||||
defer delete(thread_count_status_string)
|
defer delete(thread_count_status_string)
|
||||||
}
|
|
||||||
|
|
||||||
task_data_slots: []Task_Data = ---
|
task_data_slots: []Task_Data = ---
|
||||||
task_data_slots, alloc_error = make([]Task_Data, thread_count)
|
task_data_slots, alloc_error = make([]Task_Data, thread_count)
|
||||||
@@ -496,11 +503,13 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
|
|
||||||
setup_signal_handler()
|
setup_signal_handler()
|
||||||
|
|
||||||
|
if !global_ansi_disabled {
|
||||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
|
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_HIDE)
|
||||||
|
}
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
|
||||||
signals_were_raised := false
|
signals_were_raised := false
|
||||||
|
|
||||||
|
if should_show_animations {
|
||||||
redraw_report(stdout, report)
|
redraw_report(stdout, report)
|
||||||
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
|
draw_status_bar(stdout, thread_count_status_string, total_done_count, total_test_count)
|
||||||
}
|
}
|
||||||
@@ -718,7 +727,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
break main_loop
|
break main_loop
|
||||||
}
|
}
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
|
||||||
// Because the bounds checking procs send directly to STDERR with
|
// Because the bounds checking procs send directly to STDERR with
|
||||||
// no way to redirect or handle them, we need to at least try to
|
// no way to redirect or handle them, we need to at least try to
|
||||||
// let the user see those messages when using the animated progress
|
// let the user see those messages when using the animated progress
|
||||||
@@ -733,7 +742,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
// should be enough to clue the user in that something dire has
|
// should be enough to clue the user in that something dire has
|
||||||
// occurred.
|
// occurred.
|
||||||
bypass_progress_overwrite := false
|
bypass_progress_overwrite := false
|
||||||
}
|
|
||||||
|
|
||||||
if test_index, reason, ok := should_stop_test(); ok {
|
if test_index, reason, ok := should_stop_test(); ok {
|
||||||
#no_bounds_check report.all_test_states[test_index] = .Failed
|
#no_bounds_check report.all_test_states[test_index] = .Failed
|
||||||
@@ -767,7 +776,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
|
log.fatalf("Caught signal to stop test #%i %s.%s for: %v.", test_index, it.pkg, it.name, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
if should_show_animations {
|
||||||
bypass_progress_overwrite = true
|
bypass_progress_overwrite = true
|
||||||
signals_were_raised = true
|
signals_were_raised = true
|
||||||
}
|
}
|
||||||
@@ -781,7 +790,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
|
|
||||||
// -- Redraw.
|
// -- Redraw.
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
if should_show_animations {
|
||||||
if len(log_messages) == 0 && !needs_to_redraw(report) {
|
if len(log_messages) == 0 && !needs_to_redraw(report) {
|
||||||
continue main_loop
|
continue main_loop
|
||||||
}
|
}
|
||||||
@@ -791,7 +800,9 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if total_done_count != last_done_count {
|
if total_done_count != last_done_count {
|
||||||
|
if !global_ansi_disabled {
|
||||||
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
|
fmt.wprintf(stdout, OSC_WINDOW_TITLE, total_done_count, total_test_count)
|
||||||
|
}
|
||||||
last_done_count = total_done_count
|
last_done_count = total_done_count
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -816,7 +827,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
clear(&log_messages)
|
clear(&log_messages)
|
||||||
bytes.buffer_reset(&batch_buffer)
|
bytes.buffer_reset(&batch_buffer)
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
if should_show_animations {
|
||||||
redraw_report(batch_writer, report)
|
redraw_report(batch_writer, report)
|
||||||
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
|
draw_status_bar(batch_writer, thread_count_status_string, total_done_count, total_test_count)
|
||||||
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
|
fmt.wprint(stdout, bytes.buffer_to_string(&batch_buffer))
|
||||||
@@ -837,7 +848,7 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
|
|
||||||
finished_in := time.since(start_time)
|
finished_in := time.since(start_time)
|
||||||
|
|
||||||
when !FANCY_OUTPUT {
|
if !should_show_animations || !terminal.is_terminal(os.stderr) {
|
||||||
// One line to space out the results, since we don't have the status
|
// One line to space out the results, since we don't have the status
|
||||||
// bar in plain mode.
|
// bar in plain mode.
|
||||||
fmt.wprintln(batch_writer)
|
fmt.wprintln(batch_writer)
|
||||||
@@ -851,24 +862,28 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
|
|
||||||
if total_done_count != total_test_count {
|
if total_done_count != total_test_count {
|
||||||
not_run_count := total_test_count - total_done_count
|
not_run_count := total_test_count - total_done_count
|
||||||
|
message := " %i %s left undone." if global_log_colors_disabled else " " + SGR_READY + "%i" + SGR_RESET + " %s left undone."
|
||||||
fmt.wprintf(batch_writer,
|
fmt.wprintf(batch_writer,
|
||||||
" " + SGR_READY + "%i" + SGR_RESET + " %s left undone.",
|
message,
|
||||||
not_run_count,
|
not_run_count,
|
||||||
"test was" if not_run_count == 1 else "tests were")
|
"test was" if not_run_count == 1 else "tests were")
|
||||||
}
|
}
|
||||||
|
|
||||||
if total_success_count == total_test_count {
|
if total_success_count == total_test_count {
|
||||||
|
message := " %s successful." if global_log_colors_disabled else " %s " + SGR_SUCCESS + "successful." + SGR_RESET
|
||||||
fmt.wprintfln(batch_writer,
|
fmt.wprintfln(batch_writer,
|
||||||
" %s " + SGR_SUCCESS + "successful." + SGR_RESET,
|
message,
|
||||||
"The test was" if total_test_count == 1 else "All tests were")
|
"The test was" if total_test_count == 1 else "All tests were")
|
||||||
} else if total_failure_count > 0 {
|
} else if total_failure_count > 0 {
|
||||||
if total_failure_count == total_test_count {
|
if total_failure_count == total_test_count {
|
||||||
|
message := " %s failed." if global_log_colors_disabled else " %s " + SGR_FAILED + "failed." + SGR_RESET
|
||||||
fmt.wprintfln(batch_writer,
|
fmt.wprintfln(batch_writer,
|
||||||
" %s " + SGR_FAILED + "failed." + SGR_RESET,
|
message,
|
||||||
"The test" if total_test_count == 1 else "All tests")
|
"The test" if total_test_count == 1 else "All tests")
|
||||||
} else {
|
} else {
|
||||||
|
message := " %i test%s failed." if global_log_colors_disabled else " " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed."
|
||||||
fmt.wprintfln(batch_writer,
|
fmt.wprintfln(batch_writer,
|
||||||
" " + SGR_FAILED + "%i" + SGR_RESET + " test%s failed.",
|
message,
|
||||||
total_failure_count,
|
total_failure_count,
|
||||||
"" if total_failure_count == 1 else "s")
|
"" if total_failure_count == 1 else "s")
|
||||||
}
|
}
|
||||||
@@ -922,9 +937,11 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !global_ansi_disabled {
|
||||||
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
|
fmt.wprint(stdout, ansi.CSI + ansi.DECTCEM_SHOW)
|
||||||
|
}
|
||||||
|
|
||||||
when FANCY_OUTPUT {
|
if should_show_animations {
|
||||||
if signals_were_raised {
|
if signals_were_raised {
|
||||||
fmt.wprintln(batch_writer, `
|
fmt.wprintln(batch_writer, `
|
||||||
Signals were raised during this test run. Log messages are likely to have collided with each other.
|
Signals were raised during this test run. Log messages are likely to have collided with each other.
|
||||||
|
|||||||
@@ -63,9 +63,11 @@ stop_test_callback :: proc "c" (sig: libc.int) {
|
|||||||
// NOTE(Feoramund): Using these write calls in a signal handler is
|
// NOTE(Feoramund): Using these write calls in a signal handler is
|
||||||
// undefined behavior in C99 but possibly tolerated in POSIX 2008.
|
// undefined behavior in C99 but possibly tolerated in POSIX 2008.
|
||||||
// Either way, we may as well try to salvage what we can.
|
// Either way, we may as well try to salvage what we can.
|
||||||
|
if !global_ansi_disabled {
|
||||||
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
|
show_cursor := ansi.CSI + ansi.DECTCEM_SHOW
|
||||||
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
|
libc.fwrite(raw_data(show_cursor), size_of(byte), len(show_cursor), libc.stdout)
|
||||||
libc.fflush(libc.stdout)
|
libc.fflush(libc.stdout)
|
||||||
|
}
|
||||||
|
|
||||||
// This is an attempt at being compliant by avoiding printf.
|
// This is an attempt at being compliant by avoiding printf.
|
||||||
sigbuf: [8]byte
|
sigbuf: [8]byte
|
||||||
|
|||||||
Reference in New Issue
Block a user