Merge pull request #5189 from Feoramund/fix-ansi-log-terminal

Add `core:terminal`, fix test runner/`core:log` ANSI code issues
This commit is contained in:
Jeroen van Rijn
2025-05-21 14:20:18 +02:00
committed by GitHub
13 changed files with 362 additions and 124 deletions
+53 -11
View File
@@ -2,10 +2,12 @@
#+build !orca #+build !orca
package log package log
import "core:encoding/ansi" import "base:runtime"
import "core:fmt" import "core:fmt"
import "core:strings" import "core:strings"
import "core:os" import "core:os"
import "core:terminal"
import "core:terminal/ansi"
import "core:time" import "core:time"
Level_Headers := [?]string{ Level_Headers := [?]string{
@@ -37,11 +39,36 @@ File_Console_Logger_Data :: struct {
ident: string, ident: string,
} }
@(private) global_subtract_stdout_options: Options
@(private) global_subtract_stderr_options: Options
@(init, private)
init_standard_stream_status :: proc() {
// NOTE(Feoramund): While it is technically possible for these streams to
// be redirected during the runtime of the program, the cost of checking on
// every single log message is not worth it to support such an
// uncommonly-used feature.
if terminal.color_enabled {
// This is done this way because it's possible that only one of these
// streams could be redirected to a file.
if !terminal.is_terminal(os.stdout) {
global_subtract_stdout_options = {.Terminal_Color}
}
if !terminal.is_terminal(os.stderr) {
global_subtract_stderr_options = {.Terminal_Color}
}
} else {
// Override any terminal coloring.
global_subtract_stdout_options = {.Terminal_Color}
global_subtract_stderr_options = {.Terminal_Color}
}
}
create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger { create_file_logger :: proc(h: os.Handle, lowest := Level.Debug, opt := Default_File_Logger_Opts, ident := "", allocator := context.allocator) -> Logger {
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = h data.file_handle = h
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{file_logger_proc, data, lowest, opt}
} }
destroy_file_logger :: proc(log: Logger, allocator := context.allocator) { destroy_file_logger :: proc(log: Logger, allocator := context.allocator) {
@@ -56,19 +83,15 @@ create_console_logger :: proc(lowest := Level.Debug, opt := Default_Console_Logg
data := new(File_Console_Logger_Data, allocator) data := new(File_Console_Logger_Data, allocator)
data.file_handle = os.INVALID_HANDLE data.file_handle = os.INVALID_HANDLE
data.ident = ident data.ident = ident
return Logger{file_console_logger_proc, data, lowest, opt} return Logger{console_logger_proc, data, lowest, opt}
} }
destroy_console_logger :: proc(log: Logger, allocator := context.allocator) { destroy_console_logger :: proc(log: Logger, allocator := context.allocator) {
free(log.data, allocator) free(log.data, allocator)
} }
file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) { @(private)
data := cast(^File_Console_Logger_Data)logger_data _file_console_logger_proc :: proc(h: os.Handle, ident: string, level: Level, text: string, options: Options, location: runtime.Source_Code_Location) {
h: os.Handle = os.stdout if level <= Level.Error else os.stderr
if data.file_handle != os.INVALID_HANDLE {
h = data.file_handle
}
backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths. backing: [1024]byte //NOTE(Hoej): 1024 might be too much for a header backing, unless somebody has really long paths.
buf := strings.builder_from_bytes(backing[:]) buf := strings.builder_from_bytes(backing[:])
@@ -86,13 +109,32 @@ file_console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string
fmt.sbprintf(&buf, "[{}] ", os.current_thread_id()) fmt.sbprintf(&buf, "[{}] ", os.current_thread_id())
} }
if data.ident != "" { if ident != "" {
fmt.sbprintf(&buf, "[%s] ", data.ident) fmt.sbprintf(&buf, "[%s] ", ident)
} }
//TODO(Hoej): When we have better atomics and such, make this thread-safe //TODO(Hoej): When we have better atomics and such, make this thread-safe
fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text) fmt.fprintf(h, "%s%s\n", strings.to_string(buf), text)
} }
file_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
data := cast(^File_Console_Logger_Data)logger_data
_file_console_logger_proc(data.file_handle, data.ident, level, text, options, location)
}
console_logger_proc :: proc(logger_data: rawptr, level: Level, text: string, options: Options, location := #caller_location) {
options := options
data := cast(^File_Console_Logger_Data)logger_data
h: os.Handle = ---
if level < Level.Error {
h = os.stdout
options -= global_subtract_stdout_options
} else {
h = os.stderr
options -= global_subtract_stderr_options
}
_file_console_logger_proc(h, data.ident, level, text, options, location)
}
do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) { do_level_header :: proc(opts: Options, str: ^strings.Builder, level: Level) {
RESET :: ansi.CSI + ansi.RESET + ansi.SGR RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+4
View File
@@ -0,0 +1,4 @@
/*
This package is for interacting with the command line interface of the system.
*/
package terminal
+87
View File
@@ -0,0 +1,87 @@
#+private
package terminal
import "core:os"
import "core:strings"
// Reference documentation:
//
// - [[ https://no-color.org/ ]]
// - [[ https://github.com/termstandard/colors ]]
// - [[ https://invisible-island.net/ncurses/terminfo.src.html ]]
get_no_color :: proc() -> bool {
if no_color, ok := os.lookup_env("NO_COLOR"); ok {
defer delete(no_color)
return no_color != ""
}
return false
}
get_environment_color :: proc() -> Color_Depth {
// `COLORTERM` is non-standard but widespread and unambiguous.
if colorterm, ok := os.lookup_env("COLORTERM"); ok {
defer delete(colorterm)
// These are the only values that are typically advertised that have
// anything to do with color depth.
if colorterm == "truecolor" || colorterm == "24bit" {
return .True_Color
}
}
if term, ok := os.lookup_env("TERM"); ok {
defer delete(term)
if strings.contains(term, "-truecolor") {
return .True_Color
}
if strings.contains(term, "-256color") {
return .Eight_Bit
}
if strings.contains(term, "-16color") {
return .Four_Bit
}
// The `terminfo` database, which is stored in binary on *nix
// platforms, has an undocumented format that is not guaranteed to be
// portable, so beyond this point, we can only make safe assumptions.
//
// This section should only be necessary for terminals that do not
// define any of the previous environment values.
//
// Only a small sampling of some common values are checked here.
switch term {
case "ansi": fallthrough
case "konsole": fallthrough
case "putty": fallthrough
case "rxvt": fallthrough
case "rxvt-color": fallthrough
case "screen": fallthrough
case "st": fallthrough
case "tmux": fallthrough
case "vte": fallthrough
case "xterm": fallthrough
case "xterm-color":
return .Three_Bit
}
}
return .None
}
@(init)
init_terminal :: proc() {
_init_terminal()
// We respect `NO_COLOR` specifically as a color-disabler but not as a
// blanket ban on any terminal manipulation codes, hence why this comes
// after `_init_terminal` which will allow Windows to enable Virtual
// Terminal Processing for non-color control sequences.
if !get_no_color() {
color_enabled = color_depth > .None
}
}
@(fini)
fini_terminal :: proc() {
_fini_terminal()
}
+36
View File
@@ -0,0 +1,36 @@
package terminal
import "core:os"
/*
This describes the range of colors that a terminal is capable of supporting.
*/
Color_Depth :: enum {
None, // No color support
Three_Bit, // 8 colors
Four_Bit, // 16 colors
Eight_Bit, // 256 colors
True_Color, // 24-bit true color
}
/*
Returns true if the file `handle` is attached to a terminal.
This is normally true for `os.stdout` and `os.stderr` unless they are
redirected to a file.
*/
@(require_results)
is_terminal :: proc(handle: os.Handle) -> bool {
return _is_terminal(handle)
}
/*
This is true if the terminal is accepting any form of colored text output.
*/
color_enabled: bool
/*
This value reports the color depth support as reported by the terminal at the
start of the program.
*/
color_depth: Color_Depth
+16
View File
@@ -0,0 +1,16 @@
#+private
#+build linux, darwin, netbsd, openbsd, freebsd, haiku
package terminal
import "core:os"
import "core:sys/posix"
_is_terminal :: proc(handle: os.Handle) -> bool {
return bool(posix.isatty(posix.FD(handle)))
}
_init_terminal :: proc() {
color_depth = get_environment_color()
}
_fini_terminal :: proc() { }
+60
View File
@@ -0,0 +1,60 @@
#+private
package terminal
import "core:os"
import "core:sys/windows"
_is_terminal :: proc(handle: os.Handle) -> bool {
is_tty := windows.GetFileType(windows.HANDLE(handle)) == windows.FILE_TYPE_CHAR
return is_tty
}
old_modes: [2]struct{
handle: windows.DWORD,
mode: windows.DWORD,
} = {
{windows.STD_OUTPUT_HANDLE, 0},
{windows.STD_ERROR_HANDLE, 0},
}
@(init)
_init_terminal :: proc() {
vtp_enabled: bool
for &v in old_modes {
handle := windows.GetStdHandle(v.handle)
if handle == windows.INVALID_HANDLE || handle == nil {
return
}
if windows.GetConsoleMode(handle, &v.mode) {
windows.SetConsoleMode(handle, v.mode | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
new_mode: windows.DWORD
windows.GetConsoleMode(handle, &new_mode)
if new_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
vtp_enabled = true
}
}
}
if vtp_enabled {
// This color depth is available on Windows 10 since build 10586.
color_depth = .Four_Bit
} else {
// The user may be on a non-default terminal emulator.
color_depth = get_environment_color()
}
}
@(fini)
_fini_terminal :: proc() {
for v in old_modes {
handle := windows.GetStdHandle(v.handle)
if handle == windows.INVALID_HANDLE || handle == nil {
return
}
windows.SetConsoleMode(handle, v.mode)
}
}
+1 -1
View File
@@ -10,12 +10,12 @@ package testing
*/ */
import "base:runtime" import "base:runtime"
import "core:encoding/ansi"
import "core:fmt" import "core:fmt"
import "core:io" import "core:io"
import "core:mem" import "core:mem"
import "core:path/filepath" import "core:path/filepath"
import "core:strings" import "core:strings"
import "core:terminal/ansi"
// Definitions of colors for use in the test runner. // Definitions of colors for use in the test runner.
SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR SGR_RESET :: ansi.CSI + ansi.RESET + ansi.SGR
+51 -27
View File
@@ -13,7 +13,6 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "base:runtime" import "base:runtime"
import "core:bytes" import "core:bytes"
import "core:encoding/ansi"
@require import "core:encoding/base64" @require import "core:encoding/base64"
@require import "core:encoding/json" @require import "core:encoding/json"
import "core:fmt" import "core:fmt"
@@ -25,6 +24,8 @@ import "core:os"
import "core:slice" import "core:slice"
@require import "core:strings" @require import "core:strings"
import "core:sync/chan" import "core:sync/chan"
import "core:terminal"
import "core:terminal/ansi"
import "core:thread" import "core:thread"
import "core:time" import "core:time"
@@ -44,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)
@@ -70,6 +72,9 @@ get_log_level :: #force_inline proc() -> runtime.Logger_Level {
} }
} }
@(private) global_log_colors_disabled: bool
@(private) global_ansi_disabled: bool
JSON :: struct { JSON :: struct {
total: int, total: int,
success: int, success: int,
@@ -129,11 +134,16 @@ run_test_task :: proc(task: thread.Task) {
context.assertion_failure_proc = test_assertion_failure_proc context.assertion_failure_proc = test_assertion_failure_proc
logger_options := Default_Test_Logger_Opts
if global_log_colors_disabled {
logger_options -= {.Terminal_Color}
}
context.logger = { context.logger = {
procedure = test_logger_proc, procedure = test_logger_proc,
data = &data.t, data = &data.t,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts, options = logger_options,
} }
random_generator_state: runtime.Default_Random_State random_generator_state: runtime.Default_Random_State
@@ -204,13 +214,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
} }
} }
when ODIN_OS == .Windows {
console_ansi_init()
}
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_ansi_disabled = !terminal.is_terminal(os.stdout)
should_show_animations := FANCY_OUTPUT && terminal.color_enabled && !global_ansi_disabled
// -- Prepare test data. // -- Prepare test data.
alloc_error: mem.Allocator_Error alloc_error: mem.Allocator_Error
@@ -268,12 +281,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.
@@ -342,7 +355,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.
@@ -366,7 +379,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)
@@ -442,11 +455,16 @@ runner :: proc(internal_tests: []Internal_Test) -> bool {
// digging through the source to divine everywhere it is used for that. // digging through the source to divine everywhere it is used for that.
shared_log_allocator := context.allocator shared_log_allocator := context.allocator
logger_options := Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}
if global_log_colors_disabled {
logger_options -= {.Terminal_Color}
}
context.logger = { context.logger = {
procedure = runner_logger_proc, procedure = runner_logger_proc,
data = &log_messages, data = &log_messages,
lowest_level = get_log_level(), lowest_level = get_log_level(),
options = Default_Test_Logger_Opts - {.Short_File_Path, .Line, .Procedure}, options = logger_options,
} }
run_index: int run_index: int
@@ -481,11 +499,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)
} }
@@ -703,7 +723,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
@@ -718,7 +738,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
@@ -752,7 +772,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
} }
@@ -766,7 +786,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
} }
@@ -776,7 +796,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
} }
@@ -801,7 +823,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))
@@ -822,7 +844,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)
@@ -836,24 +858,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")
} }
@@ -907,9 +933,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.
@@ -949,9 +977,5 @@ To partly mitigate this, redirect STDERR to a file or use the -define:ODIN_TEST_
fmt.assertf(err == nil, "Error writing JSON report: %v", err) fmt.assertf(err == nil, "Error writing JSON report: %v", err)
} }
when ODIN_OS == .Windows {
console_ansi_fini()
}
return total_success_count == total_test_count return total_success_count == total_test_count
} }
-36
View File
@@ -1,36 +0,0 @@
#+private
package testing
import win32 "core:sys/windows"
old_stdout_mode: u32
old_stderr_mode: u32
console_ansi_init :: proc() {
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
if stdout != win32.INVALID_HANDLE && stdout != nil {
if win32.GetConsoleMode(stdout, &old_stdout_mode) {
win32.SetConsoleMode(stdout, old_stdout_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}
}
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
if stderr != win32.INVALID_HANDLE && stderr != nil {
if win32.GetConsoleMode(stderr, &old_stderr_mode) {
win32.SetConsoleMode(stderr, old_stderr_mode | win32.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
}
}
}
// Restore the cursor on exit
console_ansi_fini :: proc() {
stdout := win32.GetStdHandle(win32.STD_OUTPUT_HANDLE)
if stdout != win32.INVALID_HANDLE && stdout != nil {
win32.SetConsoleMode(stdout, old_stdout_mode)
}
stderr := win32.GetStdHandle(win32.STD_ERROR_HANDLE)
if stderr != win32.INVALID_HANDLE && stderr != nil {
win32.SetConsoleMode(stderr, old_stderr_mode)
}
}
+4 -2
View File
@@ -12,9 +12,9 @@ package testing
import "base:intrinsics" import "base:intrinsics"
import "core:c/libc" import "core:c/libc"
import "core:encoding/ansi"
import "core:sync"
import "core:os" import "core:os"
import "core:sync"
import "core:terminal/ansi"
@(private="file") stop_runner_flag: libc.sig_atomic_t @(private="file") stop_runner_flag: libc.sig_atomic_t
@@ -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
+5 -2
View File
@@ -58,7 +58,6 @@ import trace "core:debug/trace"
import dynlib "core:dynlib" import dynlib "core:dynlib"
import net "core:net" import net "core:net"
import ansi "core:encoding/ansi"
import base32 "core:encoding/base32" import base32 "core:encoding/base32"
import base64 "core:encoding/base64" import base64 "core:encoding/base64"
import cbor "core:encoding/cbor" import cbor "core:encoding/cbor"
@@ -129,6 +128,9 @@ import strings "core:strings"
import sync "core:sync" import sync "core:sync"
import testing "core:testing" import testing "core:testing"
import terminal "core:terminal"
import ansi "core:terminal/ansi"
import edit "core:text/edit" import edit "core:text/edit"
import i18n "core:text/i18n" import i18n "core:text/i18n"
import match "core:text/match" import match "core:text/match"
@@ -201,7 +203,6 @@ _ :: pe
_ :: trace _ :: trace
_ :: dynlib _ :: dynlib
_ :: net _ :: net
_ :: ansi
_ :: base32 _ :: base32
_ :: base64 _ :: base64
_ :: csv _ :: csv
@@ -257,6 +258,8 @@ _ :: strconv
_ :: strings _ :: strings
_ :: sync _ :: sync
_ :: testing _ :: testing
_ :: terminal
_ :: ansi
_ :: scanner _ :: scanner
_ :: i18n _ :: i18n
_ :: match _ :: match