gs_test/harness.jai

294 lines
6.5 KiB
Plaintext

#import "Basic";
#import "String";
///////////////////////////////////////////////////////////////////////
// BEGIN INTERFACE
// Register a function to run before each test
Before_Each :: (proc: Test_Proc, loc := #caller_location) #expand
{
h := harness();
if h.before_each
{
print_error("You cannot register more than one Before_Each handler", loc);
exit(1);
}
h.before_each = proc;
}
// Register a function to run after each test
After_Each :: (proc: Test_Proc, loc := #caller_location) #expand
{
h := harness();
if h.after_each
{
print_error("You cannot register more than one After_Each handler", loc);
exit(1);
}
h.after_each = proc;
}
// Wrapper for registering a test procedure you want to have run
Test :: (name: string, proc: Test_Proc, loc := #caller_location, only := false) #expand
{
h := harness();
test_index := h.tests.count;
array_add(*h.tests, .{
name = name,
proc = proc
});
if only
{
if h.only_index < 0
{
h.only_index = test_index;
}
else
{
print_error("You cannot register more than one test to be the only one to run.", loc);
exit(1);
}
}
}
// Call this at the beginning of your main function
Init_Test_Harness :: (prefix: string = "", loc := #caller_location) #expand
{
h := harness();
<< h = Test_Harness.{
test_prefix = prefix,
filename = tprint("%", #file),
};
init_string_builder(*h.message_builder);
apply_command_line_arguments(h);
print("%\n", loc.fully_pathed_filename);
}
// Call this at the end of your main function
Run_Test_Harness :: (loc:= #caller_location) #expand
{
h := harness();
if h.only_index >= 0 && h.only_index < h.tests.count
{
run_single_test(h, h.tests[h.only_index]);
}
else
{
for h.tests
{
run_single_test(h, it);
}
}
return_code := finish_all_tests();
exit(return_code);
}
EXPECT_MACRO :: (input: $T, expected: T, loc: Source_Code_Location, info: string, condition: Code) #expand {
test_begin();
passed := #insert condition;
if passed {
report_test_passed();
} else {
report_test_failed(input, expected, loc, info);
}
}
expect :: (input: $T, expected: T, loc := #caller_location, info := "") {
EXPECT_MACRO(input, expected, loc, info, #code input == expected);
}
expect_strings_equal :: (input: string, expected: string, loc:= #caller_location, info := "") {
EXPECT_MACRO(input, expected, loc, info, #code equal(input, expected));
}
expect_true :: (input: bool, loc:= #caller_location, info := "") {
EXPECT_MACRO(input, true, loc, info, #code input == true);
}
expect_false :: (input: bool, loc:= #caller_location, info := "") {
EXPECT_MACRO(input, false, loc, info, #code input == false);
}
// END INTERFACE
///////////////////////////////////////////////////////////////////////
test_begin :: () {
TESTS_ATTEMPTED += 1;
}
report_test_passed :: () {
TESTS_PASSED += 1;
}
report_test_failed :: (input: $T, expected: T, loc: Source_Code_Location, info: string) {
h := harness();
ANY_TESTS_FAILED = true;
print_to_builder(
*h.message_builder,
" %Test Failed:%\n",
h.colors.red,
h.colors.normal
);
print_to_builder(*h.message_builder,
" %:%:%\n", loc.fully_pathed_filename, loc.line_number, loc.character_number
);
print_to_builder(*h.message_builder,
" Expected: %\n", expected
);
print_to_builder(*h.message_builder,
" Received: %\n", input
);
if info.count > 0 {
print_to_builder(*h.message_builder, " Info: %\n", info);
}
}
#scope_file
ANY_TESTS_FAILED := false;
TESTS_ATTEMPTED := 0;
TESTS_PASSED := 0;
TOTAL_TESTS_ATTEMPTED := 0;
TOTAL_TESTS_PASSED := 0;
Terminal_Color_Codes :: struct {
normal: string;
red: string;
green: string;
blue: string;
};
colors :: Terminal_Color_Codes.{
normal = "\e[m",
red = "\e[1;31m",
green = "\e[1;32m",
blue = "\e[1;34m",
};
no_colors :: Terminal_Color_Codes.{};
Test_Proc :: #type () -> ();
Test_Desc :: struct {
name: string;
proc: Test_Proc;
}
Test_Harness :: struct {
filename: string;
tests: [..]Test_Desc;
// if set, then tests[only_index] will be the only one to run.
// it is an error to set multiple tests to only.
only_index := -1;
before_each: Test_Proc;
after_each: Test_Proc;
test_prefix: string = "";
verbose: bool = false;
colors := no_colors;
message_builder: String_Builder;
}
#add_context global_test_harness: Test_Harness;
harness :: () -> *Test_Harness { return *context.global_test_harness; }
apply_command_line_arguments :: (harness: *Test_Harness)
{
args := get_command_line_arguments();
for arg: args {
if arg[0] != #char "-" continue;
if arg == {
case "-colors"; harness.colors = colors;
case "-verbose"; harness.verbose = true;
}
}
}
test_suite_end :: (name: string)
{
h := harness();
print_test_name :: (h: Test_Harness, name: string, prefix: string)
{
print("%", h.colors.blue);
if (prefix.count) print("% :: %\n", prefix, name);
else print("%\n", name);
print("%", h.colors.normal);
}
if TESTS_ATTEMPTED == TESTS_PASSED
{
if TESTS_ATTEMPTED > 0
{
if (h.verbose)
{
print(" %PASS ", h.colors.green);
print_test_name(h, name, h.test_prefix);
}
}
else
{
print(" %NO TESTS RUN ", h.colors.red);
print_test_name(h, name, h.test_prefix);
}
}
else
{
print(" %FAIL ", h.colors.red);
print_test_name(h, name, h.test_prefix);
}
messages := builder_to_string(*h.message_builder, do_reset = true);
print(messages);
TOTAL_TESTS_ATTEMPTED += TESTS_ATTEMPTED;
TOTAL_TESTS_PASSED += TESTS_PASSED;
TESTS_ATTEMPTED = 0;
TESTS_PASSED = 0;
}
run_single_test :: (h: *Test_Harness, test: Test_Desc)
{
if h.before_each
{
h.before_each();
}
test.proc();
test_suite_end(test.name);
if h.after_each
{
h.after_each();
}
}
finish_all_tests :: () -> s32
{
h := harness();
return_code: s32 = 0;
if !ANY_TESTS_FAILED
{
print("Tests: % / %\n", TOTAL_TESTS_PASSED, TOTAL_TESTS_ATTEMPTED);
print(" SUITE STATUS: %PASS%\n\n", h.colors.green, h.colors.normal);
}
else
{
return_code = 1;
print("Tests: % / %\n", TOTAL_TESTS_PASSED, TOTAL_TESTS_ATTEMPTED);
print(" SUITE_STATUS: %FAIL%\n\n", h.colors.red, h.colors.normal);
}
return return_code;
}
print_error :: (msg: string, loc: Source_Code_Location)
{
h := harness();
print("%", h.colors.red);
print("%:% - %\n", loc.fully_pathed_filename, loc.line_number, msg);
print("%", h.colors.normal);
}