Last active
December 19, 2023 00:00
-
-
Save laytan/c6e6b462403884441befb6953273d5ec to your computer and use it in GitHub Desktop.
Odin test runner
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package generated_tests | |
import test_gen "core:testing/generator" | |
import "core:os" | |
import "core:testing" | |
import test_core_crypto "../../../tests/core/crypto" | |
main :: proc() { | |
tests := []testing.Internal_Test{ | |
{ "test_core_crypto", "test_md5", test_core_crypto.test_md5 }, | |
{ "test_core_crypto", "test_sha1", test_core_crypto.test_sha1 }, | |
{ "test_core_crypto", "test_sha224", test_core_crypto.test_sha224 }, | |
{ "test_core_crypto", "test_sha256", test_core_crypto.test_sha256 }, | |
{ "test_core_crypto", "test_sha384", test_core_crypto.test_sha384 }, | |
{ "test_core_crypto", "test_sha512", test_core_crypto.test_sha512 }, | |
{ "test_core_crypto", "test_sha512_256", test_core_crypto.test_sha512_256 }, | |
{ "test_core_crypto", "test_sha3_224", test_core_crypto.test_sha3_224 }, | |
{ "test_core_crypto", "test_sha3_256", test_core_crypto.test_sha3_256 }, | |
{ "test_core_crypto", "test_sha3_384", test_core_crypto.test_sha3_384 }, | |
{ "test_core_crypto", "test_sha3_512", test_core_crypto.test_sha3_512 }, | |
{ "test_core_crypto", "test_shake_128", test_core_crypto.test_shake_128 }, | |
{ "test_core_crypto", "test_shake_256", test_core_crypto.test_shake_256 }, | |
{ "test_core_crypto", "test_keccak_224", test_core_crypto.test_keccak_224 }, | |
{ "test_core_crypto", "test_keccak_256", test_core_crypto.test_keccak_256 }, | |
{ "test_core_crypto", "test_keccak_384", test_core_crypto.test_keccak_384 }, | |
{ "test_core_crypto", "test_keccak_512", test_core_crypto.test_keccak_512 }, | |
{ "test_core_crypto", "test_blake2b", test_core_crypto.test_blake2b }, | |
{ "test_core_crypto", "test_blake2s", test_core_crypto.test_blake2s }, | |
{ "test_core_crypto", "test_sm3", test_core_crypto.test_sm3 }, | |
{ "test_core_crypto", "test_siphash_2_4", test_core_crypto.test_siphash_2_4 }, | |
{ "test_core_crypto", "test_chacha20", test_core_crypto.test_chacha20 }, | |
{ "test_core_crypto", "test_poly1305", test_core_crypto.test_poly1305 }, | |
{ "test_core_crypto", "test_chacha20poly1305", test_core_crypto.test_chacha20poly1305 }, | |
{ "test_core_crypto", "test_x25519", test_core_crypto.test_x25519 }, | |
{ "test_core_crypto", "test_rand_bytes", test_core_crypto.test_rand_bytes }, | |
{ "test_core_crypto", "bench_modern", test_core_crypto.bench_modern }, | |
} | |
if testing.runner(tests) { | |
os.exit(int(test_gen.Exit_Codes.Success)) | |
} else { | |
os.exit(int(test_gen.Exit_Codes.Failure)) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package test_generator | |
import "core:c/libc" | |
import "core:fmt" | |
import "core:odin/ast" | |
import "core:odin/parser" | |
import "core:os" | |
import "core:path/filepath" | |
import "core:strings" | |
Exit_Codes :: enum { | |
Success, | |
Failure, | |
Generation_Error, | |
Spawn_Error, | |
Run_Error, | |
Run_Unknown_Exit, | |
} | |
Test_Proc :: struct { | |
pkg: string, | |
name: string, | |
} | |
Test_Visitor :: struct { | |
pkg: ^ast.Package, | |
procs: [dynamic]Test_Proc, | |
pkgs_seen: map[string]string, | |
} | |
when ODIN_OS == .Darwin { | |
// #define _W_INT(w) (*(int *)&(w)) /* convert union wait to int */ | |
// #define _WSTATUS(x) (_W_INT(x) & 0177) | |
// #define WIFEXITED(x) (_WSTATUS(x) == 0) | |
// #define WEXITSTATUS(x) ((_W_INT(x) >> 8) & 0x000000ff) | |
_WSTATUS :: proc(x: i32) -> i32 { return x & 0177 } | |
WIFEXITED :: proc(x: i32) -> bool { return _WSTATUS(x) == 0 } | |
WEXITSTATUS :: proc(x: i32) -> i32 { return (x >> 8) & 0x000000ff } | |
} else { | |
WIFEXITED :: proc(x: i32) -> bool{ return true } | |
@(warning="WEXITSTATUS implementation missing for platform, test exit code will not reflect actual status.") | |
WEXITSTATUS :: proc(x: i32) -> i32 { return i32(Exit_Codes.Run_Unknown_Exit) } | |
} | |
main :: proc() { | |
if len(os.args) < 2 { | |
error("`odin test` takes a package as its first argument.") | |
} | |
package_path := os.args[1] | |
OUT_PATH :: ODIN_ROOT + "/core/testing/generated/generated.odin" | |
OUT_DIR :: ODIN_ROOT + "/core/testing/generated" | |
RUN :: "odin run " + ODIN_ROOT + "/core/testing/generated" | |
tv: Test_Visitor | |
collect_tests(&tv, package_path) | |
ws :: strings.write_string | |
buf := strings.builder_make() | |
ws(&buf, "package generated_tests\n\n") | |
ws(&buf, "import test_gen \"core:testing/generator\"\n") | |
ws(&buf, "import \"core:os\"\n") | |
ws(&buf, "import \"core:testing\"\n\n") | |
for fp, pkg_name in tv.pkgs_seen { | |
rp, err := filepath.rel(OUT_DIR, fp) | |
if err != nil { | |
error("Could not create relative path between %q and %q: %v.", OUT_DIR, fp, err) | |
} | |
ws(&buf, "import ") | |
ws(&buf, pkg_name) | |
ws(&buf, " \"") | |
ws(&buf, rp) | |
ws(&buf, "\"\n") | |
} | |
ws(&buf, "\n") | |
ws(&buf, "main :: proc() {\n") | |
ws(&buf, "\ttests := []testing.Internal_Test{\n") | |
for p in tv.procs { | |
ws(&buf, "\t\t{ \"") | |
ws(&buf, p.pkg) | |
ws(&buf, "\", \"") | |
ws(&buf, p.name) | |
ws(&buf, "\", ") | |
ws(&buf, p.pkg) | |
ws(&buf, ".") | |
ws(&buf, p.name) | |
ws(&buf, " },\n") | |
} | |
ws(&buf, "\t}\n\n") | |
ws(&buf, "\tif testing.runner(tests) {\n") | |
ws(&buf, "\t\tos.exit(int(test_gen.Exit_Codes.Success))\n") | |
ws(&buf, "\t} else {\n") | |
ws(&buf, "\t\tos.exit(int(test_gen.Exit_Codes.Failure))\n") | |
ws(&buf, "\t}\n") | |
ws(&buf, "}\n") | |
if errno := os.make_directory(OUT_DIR); errno != 0 && errno != os.EEXIST { | |
error("Could not make generated tests directory at %q, error code: %v.", OUT_DIR, errno) | |
} | |
if !os.write_entire_file(OUT_PATH, buf.buf[:]) { | |
error("Could not write tests to be ran to %q.", OUT_PATH) | |
} | |
res := libc.system(RUN) | |
switch { | |
case res == -1: | |
error("Spawning child process failed, error code: %v.", os.get_last_error(), exit_code=.Spawn_Error) | |
case WIFEXITED(res): | |
os.exit(int(WEXITSTATUS(res))) | |
case: | |
error("Unknown error during testing child process.", exit_code=.Run_Error) | |
} | |
} | |
collect_tests :: proc(tv: ^Test_Visitor, start_pkg: string) { | |
parse_ok: bool | |
tv.pkg, parse_ok = parser.parse_package_from_path(start_pkg) | |
if !parse_ok { | |
error("Could not parse package %q.", start_pkg) | |
} | |
tv.pkgs_seen[tv.pkg.fullpath] = tv.pkg.name | |
v := ast.Visitor{ | |
visit = test_visitor, | |
data = tv, | |
} | |
ast.walk(&v, tv.pkg) | |
} | |
test_visitor :: proc(v: ^ast.Visitor, node: ^ast.Node) -> ^ast.Visitor { | |
if node == nil { | |
return nil | |
} | |
tv := (^Test_Visitor)(v.data) | |
#partial switch n in node.derived { | |
case ^ast.Package, ^ast.File: return v | |
case ^ast.Import_Decl: | |
import_path := n.fullpath[1:len(n.fullpath)-1] | |
// TODO: support collections. | |
if strings.contains(import_path, ":") { | |
break | |
} | |
import_path = filepath.join({tv.pkg.fullpath, import_path}) | |
if import_path in tv.pkgs_seen { | |
break | |
} | |
tv.pkgs_seen[import_path] = tv.pkg.name | |
prev := tv.pkg | |
defer tv.pkg = prev | |
parse_ok: bool | |
tv.pkg, parse_ok = parser.parse_package_from_path(import_path) | |
if !parse_ok { | |
error("parsing package %s for tests failed.", n.fullpath) | |
} | |
ast.walk(v, tv.pkg) | |
case ^ast.Value_Decl: | |
is_test: bool | |
attributes_loop: for attr in n.attributes { | |
for elem in attr.elems { | |
ident, is_ident := elem.derived.(^ast.Ident) | |
if !is_ident { | |
continue | |
} | |
if ident.name == "test" { | |
is_test = true | |
break attributes_loop | |
} | |
} | |
} | |
if !is_test { | |
break | |
} | |
for name in n.names { | |
ident, is_ident := name.derived.(^ast.Ident) | |
if !is_ident { | |
continue | |
} | |
append(&tv.procs, Test_Proc{ | |
pkg = tv.pkg.name, | |
name = ident.name, | |
}) | |
} | |
} | |
return nil | |
} | |
error :: proc(msg: string, args: ..any, exit_code := Exit_Codes.Generation_Error) -> ! { | |
fmt.eprint("ERROR: ") | |
fmt.eprintf(msg, ..args) | |
fmt.eprintln() | |
os.exit(int(exit_code)) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment