core/testing
testing
Types
20Channel_Event
Channel_Event :: union {
Event_New_Test,
Event_State_Change,
Event_Set_Fail_Timeout,
Event_Log_Message,
}SourceEvent_Log_Message
Event_Log_Message :: struct {
level: runtime.Logger_Level,
text: string,
time: time.Time,
formatted_text: string,
}SourceEvent_New_Test
Event_New_Test :: struct {
test_index: int,
}SourceEvent_Set_Fail_Timeout
Event_Set_Fail_Timeout :: struct {
at_time: time.Time,
location: runtime.Source_Code_Location,
}SourceEvent_State_Change
Event_State_Change :: struct {
new_state: Test_State,
}SourceInternal_Cleanup
Internal_Cleanup :: struct {
procedure: proc(),
user_data: rawptr,
ctx: runtime.Context,
}SourceInternal_Test
Internal_Test :: struct {
pkg: string,
name: string,
p: Test_Signature,
}SourceIMPORTANT NOTE: Compiler requires this layout
JSON
JSON :: struct {
total: int,
success: int,
duration: time.Duration,
packages: map[string][dynamic]JSON_Test,
}SourceJSON_Test
JSON_Test :: struct {
success: bool,
name: string,
}SourceLog_Message
Log_Message :: struct {
level: runtime.Logger_Level,
text: string,
time: time.Time,
// `text` may be allocated differently, depending on where a log message
// originates from.
allocator: runtime.Allocator,
}SourceMemory_Verifier_Proc
Memory_Verifier_Proc :: proc(t: ^T, ta: ^mem.Tracking_Allocator)SourceOptions
Options :: struct {
// Equivalent to the TEST_NAMES compile-time definition, but used dynamically at runtime.
test_names: string,
}SourcePackage_Run
Package_Run :: struct {
name: string,
header: string,
frame_ready: bool,
redraw_buffer: [LINE_BUFFER_SIZE]u8,
redraw_string: string,
last_change_state: Test_State,
last_change_name: string,
tests: []Internal_Test,
test_states: []Test_State,
}SourceReport
Report :: struct {
packages: []Package_Run,
packages_by_name: map[string]^Package_Run,
pkg_column_len: int,
test_column_len: int,
progress_width: int,
all_tests: []Internal_Test,
all_test_states: []Test_State,
}SourceStop_Reason
Stop_Reason :: enum int {
Unknown = 0,
Successful_Stop = 1,
Illegal_Instruction = 2,
Arithmetic_Error = 3,
Segmentation_Fault = 4,
Unhandled_Trap = 5,
}SourceT
T :: struct {
error_count: int,
// If your test needs to perform random operations, it's advised to use
// this value to seed a local random number generator rather than relying
// on the non-thread-safe global one.
//
// This way, your results will be deterministic.
//
// This value is chosen at startup of the test runner, logged, and may be
// specified by the user. It is the same for all tests of a single run.
seed: u64,
channel: Update_Channel_Sender,
cleanups: [dynamic]Internal_Cleanup,
// This allocator is shared between the test runner and its threads for
// cloning log strings, so they can outlive the lifetime of individual
// tests during channel transmission.
_log_allocator: runtime.Allocator,
_fail_now_called: bool,
}SourceTask_Channel
Task_Channel :: struct {
channel: Update_Channel,
test_index: int,
}SourceTask_Timeout
Task_Timeout :: struct {
test_index: int,
at_time: time.Time,
location: runtime.Source_Code_Location,
}SourceTest_Signature
Test_Signature :: proc(^T)SourceIMPORTANT NOTE: Compiler requires this layout
Test_State
Test_State :: enum int {
Ready = 0,
Running = 1,
Successful = 2,
Failed = 3,
}SourceConstants
27ALWAYS_REPORT_MEMORY
ALWAYS_REPORT_MEMORY :: bool = #config(ODIN_TEST_ALWAYS_REPORT_MEMORY, false)SourceAlways report how much memory is used, even when there are no leaks or bad frees.
FAIL_ON_BAD_MEMORY
FAIL_ON_BAD_MEMORY :: bool = #config(ODIN_TEST_FAIL_ON_BAD_MEMORY, false)SourceTreat memory leaks and bad frees as errors.
FANCY_OUTPUT
FANCY_OUTPUT :: bool = #config(ODIN_TEST_FANCY, true)SourceShow the fancy animated progress report. This requires terminal color support, as well as STDOUT to not be redirected to a file.
GO_TO_ERROR
GO_TO_ERROR :: bool = #config(ODIN_TEST_GO_TO_ERROR, false)SourcePrint the full file path for failed test cases on a new line in a way that's friendly to regex capture for an editor's "go to error".
JSON_REPORT
JSON_REPORT :: string = #config(ODIN_TEST_JSON_REPORT, "")SourceOutput a report of the tests to the given path.
LINE_BUFFER_SIZE
LINE_BUFFER_SIZE :: _ = (MAX_PROGRESS_WIDTH * 8 + 224) * runtime.ByteSourceMore than enough bytes to cover long package names, long test names, dozens of ANSI codes, et cetera.
LOG_LEVEL
LOG_LEVEL :: string = #config(ODIN_TEST_LOG_LEVEL, LOG_LEVEL_DEFAULT)SourceLOG_STATE_CHANGES
LOG_STATE_CHANGES :: bool = #config(ODIN_TEST_LOG_STATE_CHANGES, false)SourceReport a message at the info level when a test has changed its state.
MAX_EXPECTED_ASSERTIONS_PER_TEST
MAX_EXPECTED_ASSERTIONS_PER_TEST :: 5SourceMAX_PROGRESS_WIDTH
MAX_PROGRESS_WIDTH :: 100SourcePER_THREAD_MEMORY
PER_THREAD_MEMORY :: int = #config(ODIN_TEST_THREAD_MEMORY, mem.ROLLBACK_STACK_DEFAULT_BLOCK_SIZE)SourceSpecify how much memory each thread allocator starts with.
PROGRESS_COLUMN_SPACING
PROGRESS_COLUMN_SPACING :: 2SourcePROGRESS_WIDTH
PROGRESS_WIDTH :: int = #config(ODIN_TEST_PROGRESS_WIDTH, 24)SourceHow many test results to show at a time per package.
SGR_FAILED
SGR_FAILED :: _ = ansi.CSI + ansi.FG_RED + ansi.SGRSourceSGR_READY
SGR_READY :: _ = ansi.CSI + ansi.FG_BRIGHT_BLACK + ansi.SGRSourceSGR_RESET
SGR_RESET :: _ = ansi.CSI + ansi.RESET + ansi.SGRSourceDefinitions of colors for use in the test runner.
SGR_RUNNING
SGR_RUNNING :: _ = ansi.CSI + ansi.FG_YELLOW + ansi.SGRSourceSGR_SUCCESS
SGR_SUCCESS :: _ = ansi.CSI + ansi.FG_GREEN + ansi.SGRSourceSHARED_RANDOM_SEED
SHARED_RANDOM_SEED :: u64 = #config(ODIN_TEST_RANDOM_SEED, 0)SourceThis is the random seed that will be sent to each test. If it is unspecified, it will be set to the system cycle counter at startup.
SIGTRAP
SIGTRAP :: 5SourceWindows does not appear to have a SIGTRAP, so this is defined here, instead of in the libc package, just so there's no confusion about it being available there.
TEST_NAMES
TEST_NAMES :: string = #config(ODIN_TEST_NAMES, "")SourceSelect a specific set of tests to run by name. Each test is separated by a comma and may optionally include the package name. This may be useful when running tests on multiple packages with -all-packages. The format is: package.test_name,test_name_only,...
TEST_THREADS
TEST_THREADS :: int = #config(ODIN_TEST_THREADS, 0)SourceSpecify how many threads to use when running tests.
TRACKING_MEMORY
TRACKING_MEMORY :: bool = #config(ODIN_TEST_TRACK_MEMORY, true)SourceTrack the memory used by each test.
USE_CLIPBOARD
USE_CLIPBOARD :: bool = #config(ODIN_TEST_CLIPBOARD, false)SourceCopy failed tests to the clipboard when done.
USING_SHORT_LOGS
USING_SHORT_LOGS :: bool = #config(ODIN_TEST_SHORT_LOGS, false)SourceShow only the most necessary logging information.
Update_Channel
Update_Channel :: _ = chan.Chan(Channel_Event)SourceUpdate_Channel_Sender
Update_Channel_Sender :: _ = chan.Chan(Channel_Event, .Send)SourceProcedures
39__setup_signal_handler
__setup_signal_handler :: proc()Source_setup_signal_handler
_setup_signal_handler :: proc()Source_setup_task_signal_handler
_setup_task_signal_handler :: proc(test_index: int)Source_should_stop_runner
_should_stop_runner :: proc() -> (bool)Source_should_stop_test
_should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)Source_test_thread_cancel
_test_thread_cancel :: proc()Sourcecleanup
cleanup :: proc(t: ^T, procedure: proc(), user_data: rawptr)Sourcecleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete. Cleanup procedures will be called in LIFO (last added, first called) order.
Each procedure will use a copy of the context at the time of registering, and if the test failed due to a timeout, failed assertion, panic, bounds-checking error, memory access violation, or any other signal-based fault, this procedure will run with greater privilege in the test runner's main thread.
That means that any cleanup procedure absolutely must not fail in the same way, or it will take down the entire test runner with it. This is for when you need something to run no matter what, if a test failed.
For almost every usual case, defer should be preferable and sufficient.
destroy_report
destroy_report :: proc(report: ^Report)Sourcedo_go_to_error_friendly_location
do_go_to_error_friendly_location :: proc(opts: log.Options, buf: ^strings.Builder, location = #caller_location)Sourcedraw_status_bar
draw_status_bar :: proc(w: io.Writer, threads_string: string, total_done_count: int, total_test_count: int)Sourceend_t
end_t :: proc(t: ^T)Sourceexpect
expect :: proc(t: ^T, ok: bool, msg: untyped string = "", expr = #caller_expression(ok), loc = #caller_location) -> (bool)Sourceexpect_assert_from
expect_assert_from :: proc(t: ^T, expected_place: runtime.Source_Code_Location, caller_loc = #caller_location)SourceLet the test runner know that it should expect an assertion failure from a specific location in the source code for this test.
In the event that an assertion fails, a debug message will be logged with its exact message and location in a copyable format to make it convenient to write tests which use this API.
This procedure may be called up to 5 times with different locations.
This is a limitation for the sake of simplicity in the implementation, and you should consider breaking up your tests into smaller procedures if you need to check for asserts in more than 2 places.
expect_assert_message
expect_assert_message :: proc(t: ^T, expected_message: string, caller_loc = #caller_location)SourceLet the test runner know that it should expect an assertion failure with a specific message for this test.
In the event that an assertion fails, a debug message will be logged with its exact message and location in a copyable format to make it convenient to write tests which use this API.
This procedure may be called up to 5 times with different messages.
This is a limitation for the sake of simplicity in the implementation, and you should consider breaking up your tests into smaller procedures if you need to check for more than a couple different assertion messages.
expect_leaks
expect_leaks :: proc(t: ^T, client_test: proc(t: ^T), verifier: Memory_Verifier_Proc)Sourceexpect_signal
expect_signal :: proc(t: ^T, sig: i32)SourceLet the test runner know that it should expect a signal to be raised within this test.
This API is for advanced users, as arbitrary signals will not be caught; only the ones already handled by the test runner, such as
- SIGINT, (interrupt)
- SIGTERM, (polite termination)
- SIGILL, (illegal instruction)
- SIGFPE, (arithmetic error)
- SIGSEGV, and (segmentation fault)
- SIGTRAP (only on POSIX systems). (trap / debug trap)
Note that only one signal can be expected per test.
expect_value
expect_value :: proc(t: ^T, value: T, expected: T, loc = #caller_location, value_expr = #caller_expression(value)) -> (bool)Sourceexpectf
expectf :: proc(t: ^T, ok: bool, format: string, args, loc = #caller_location) -> (bool)Sourcefail
fail :: proc(t: ^T, loc = #caller_location)Sourcefail_now
fail_now :: proc(t: ^T, msg: untyped string = "", loc = #caller_location) -> ()Sourcefail_now will cause a test to immediately fail and abort, much in the same way a failed assertion or panic call will stop a thread.
It is for when you absolutely need a test to fail without calling any of its deferred statements. It will be cleaner than a regular assert or panic, as the test runner will know to expect the signal this procedure will raise.
failed
failed :: proc(t: ^T) -> (bool)Sourceformat_log_text
format_log_text :: proc(
level: runtime.Logger_Level,
text: string,
options: runtime.Logger_Options,
location: runtime.Source_Code_Location,
at_time: time.Time,
allocator: mem.Allocator = context.allocator,
) -> (string)Sourceget_log_level
get_log_level :: proc() -> (runtime.Logger_Level)Sourcemake_report
make_report :: proc(internal_tests: []Internal_Test) -> (report: Report, error: runtime.Allocator_Error)SourceOrganize all tests by package and sort out test state data.
needs_to_redraw
needs_to_redraw :: proc(report: Report) -> (bool)Sourceparse_cli_options
parse_cli_options :: proc(argv: []string, opts: ^Options, stdout: io.Writer, stderr: io.Writer)Sourceredraw_package
redraw_package :: proc(w: io.Writer, report: Report, pkg: ^Package_Run)Sourceredraw_report
redraw_report :: proc(w: io.Writer, report: Report)Sourcerun_test_task
run_test_task :: proc(task: thread.Task)Sourcerunner
runner :: proc(internal_tests: []Internal_Test) -> (bool)Sourcerunner_logger_proc
runner_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location = #caller_location)Sourceset_fail_timeout
set_fail_timeout :: proc(t: ^T, duration: time.Duration, loc = #caller_location)Sourcesetup_signal_handler
setup_signal_handler :: proc()Sourcesetup_task_signal_handler
setup_task_signal_handler :: proc(test_index: int)Sourceshould_stop_runner
should_stop_runner :: proc() -> (bool)Sourceshould_stop_test
should_stop_test :: proc() -> (test_index: int, reason: Stop_Reason, ok: bool)Sourcetest_assertion_failure_proc
test_assertion_failure_proc :: proc(prefix: string, message: string, loc: runtime.Source_Code_Location) -> ()Sourcetest_logger_proc
test_logger_proc :: proc(logger_data: rawptr, level: runtime.Logger_Level, text: string, options: runtime.Logger_Options, location = #caller_location)Sourcewrite_memory_report
write_memory_report :: proc(w: io.Writer, tracker: ^mem.Tracking_Allocator, pkg: string, name: string)Source