#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); }