Skip to content

Commit 2aa5882

Browse files
committed
odin test to work with the new core:testing package
1 parent 10f91a0 commit 2aa5882

File tree

7 files changed

+262
-10
lines changed

7 files changed

+262
-10
lines changed

core/testing/runner.odin

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
//+private
2+
package testing
3+
4+
import "core:io"
5+
import "core:os"
6+
import "core:strings"
7+
8+
reset_t :: proc(t: ^T) {
9+
clear(&t.cleanups);
10+
t.error_count = 0;
11+
}
12+
end_t :: proc(t: ^T) {
13+
for i := len(t.cleanups)-1; i >= 0; i -= 1 {
14+
c := t.cleanups[i];
15+
c.procedure(c.user_data);
16+
}
17+
}
18+
19+
runner :: proc(internal_tests: []Internal_Test) -> bool {
20+
stream := os.stream_from_handle(os.stdout);
21+
w, _ := io.to_writer(stream);
22+
23+
t := &T{};
24+
t.w = w;
25+
reserve(&t.cleanups, 1024);
26+
defer delete(t.cleanups);
27+
28+
total_success_count := 0;
29+
total_test_count := len(internal_tests);
30+
31+
for it in internal_tests {
32+
if it.p == nil {
33+
total_test_count -= 1;
34+
continue;
35+
}
36+
37+
free_all(context.temp_allocator);
38+
reset_t(t);
39+
defer end_t(t);
40+
41+
name := strings.trim_prefix(it.name, "test_");
42+
43+
logf(t, "[Test: %q]", name);
44+
45+
// TODO(bill): Catch panics
46+
{
47+
it.p(t);
48+
}
49+
50+
if t.error_count != 0 {
51+
logf(t, "[%q : FAILURE]", name);
52+
} else {
53+
logf(t, "[%q : SUCCESS]", name);
54+
total_success_count += 1;
55+
}
56+
}
57+
logf(t, "----------------------------------------");
58+
logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count);
59+
return total_success_count == total_test_count;
60+
}

core/testing/testing.odin

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package testing
2+
3+
import "core:fmt"
4+
import "core:io"
5+
6+
Test_Signature :: proc(^T);
7+
8+
Internal_Test :: struct {
9+
name: string,
10+
p: Test_Signature,
11+
}
12+
13+
14+
Internal_Cleanup :: struct {
15+
procedure: proc(rawptr),
16+
user_data: rawptr,
17+
}
18+
19+
T :: struct {
20+
error_count: int,
21+
22+
w: io.Writer,
23+
24+
cleanups: [dynamic]Internal_Cleanup,
25+
}
26+
27+
28+
error :: proc(t: ^T, args: ..any, loc := #caller_location) {
29+
log(t=t, args=args, loc=loc);
30+
t.error_count += 1;
31+
}
32+
33+
errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
34+
logf(t=t, format=format, args=args, loc=loc);
35+
t.error_count += 1;
36+
}
37+
38+
fail :: proc(t: ^T) {
39+
error(t, "FAIL");
40+
t.error_count += 1;
41+
}
42+
43+
failed :: proc(t: ^T) -> bool {
44+
return t.error_count != 0;
45+
}
46+
47+
log :: proc(t: ^T, args: ..any, loc := #caller_location) {
48+
fmt.wprintln(t.w, ..args);
49+
}
50+
51+
logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
52+
fmt.wprintf(t.w, format, ..args);
53+
fmt.wprintln(t.w);
54+
}
55+
56+
57+
// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete
58+
// cleanup proceduers will be called in LIFO (last added, first called) order.
59+
cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) {
60+
append(&t.cleanups, Internal_Cleanup{procedure, user_data});
61+
}
62+
63+
expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool {
64+
if !ok {
65+
error(t=t, args={msg}, loc=loc);
66+
}
67+
return ok;
68+
}

src/build_settings.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,8 +365,8 @@ bool is_excluded_target_filename(String name) {
365365
return true;
366366
}
367367

368-
String test_suffix = str_lit("_test");
369368
if (build_context.command_kind != Command_test) {
369+
String test_suffix = str_lit("_test");
370370
if (string_ends_with(name, test_suffix) && name != test_suffix) {
371371
// Ignore *_test.odin files
372372
return true;

src/checker.cpp

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1865,6 +1865,20 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
18651865
}
18661866

18671867
if (build_context.command_kind == Command_test) {
1868+
AstPackage *testing_package = get_core_package(&c->info, str_lit("testing"));
1869+
Scope *testing_scope = testing_package->scope;
1870+
1871+
// Add all of testing library as a dependency
1872+
for_array(i, testing_scope->elements.entries) {
1873+
Entity *e = testing_scope->elements.entries[i].value;
1874+
if (e != nullptr) {
1875+
e->flags |= EntityFlag_Used;
1876+
add_dependency_to_set(c, e);
1877+
}
1878+
}
1879+
1880+
Entity *test_signature = scope_lookup_current(testing_scope, str_lit("Test_Signature"));
1881+
18681882
AstPackage *pkg = c->info.init_package;
18691883
Scope *s = pkg->scope;
18701884
for_array(i, s->elements.entries) {
@@ -1884,6 +1898,7 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
18841898
continue;
18851899
}
18861900

1901+
18871902
bool is_tester = false;
18881903
if (name != prefix) {
18891904
is_tester = true;
@@ -1893,11 +1908,11 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
18931908

18941909
Type *t = base_type(e->type);
18951910
GB_ASSERT(t->kind == Type_Proc);
1896-
if (t->Proc.param_count == 0 && t->Proc.result_count == 0) {
1911+
if (are_types_identical(t, base_type(test_signature->type))) {
18971912
// Good
18981913
} else {
18991914
gbString str = type_to_string(t);
1900-
error(e->token, "Testing procedures must have a signature type of proc(), got %s", str);
1915+
error(e->token, "Testing procedures must have a signature type of proc(^testing.T), got %s", str);
19011916
gb_string_free(str);
19021917
is_tester = false;
19031918
}
@@ -2103,6 +2118,28 @@ Type *find_core_type(Checker *c, String name) {
21032118
return e->type;
21042119
}
21052120

2121+
2122+
Entity *find_entity_in_pkg(CheckerInfo *info, String const &pkg, String const &name) {
2123+
AstPackage *package = get_core_package(info, pkg);
2124+
Entity *e = scope_lookup_current(package->scope, name);
2125+
if (e == nullptr) {
2126+
compiler_error("Could not find type declaration for '%.*s.%.*s'\n", LIT(pkg), LIT(name));
2127+
// NOTE(bill): This will exit the program as it's cannot continue without it!
2128+
}
2129+
return e;
2130+
}
2131+
2132+
Type *find_type_in_pkg(CheckerInfo *info, String const &pkg, String const &name) {
2133+
AstPackage *package = get_core_package(info, pkg);
2134+
Entity *e = scope_lookup_current(package->scope, name);
2135+
if (e == nullptr) {
2136+
compiler_error("Could not find type declaration for '%.*s.%.*s'\n", LIT(pkg), LIT(name));
2137+
// NOTE(bill): This will exit the program as it's cannot continue without it!
2138+
}
2139+
GB_ASSERT(e->type != nullptr);
2140+
return e->type;
2141+
}
2142+
21062143
CheckerTypePath *new_checker_type_path() {
21072144
gbAllocator a = heap_allocator();
21082145
auto *tp = gb_alloc_item(a, CheckerTypePath);

src/ir.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12930,12 +12930,39 @@ void ir_gen_tree(irGen *s) {
1293012930
ir_emit(proc, ir_alloc_instr(proc, irInstr_StartupRuntime));
1293112931
Array<irValue *> empty_args = {};
1293212932
if (build_context.command_kind == Command_test) {
12933+
Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test"));
12934+
Type *array_type = alloc_type_array(t_Internal_Test, m->info->testing_procedures.count);
12935+
Type *slice_type = alloc_type_slice(t_Internal_Test);
12936+
irValue *all_tests_array = ir_add_global_generated(proc->module, array_type, nullptr);
12937+
1293312938
for_array(i, m->info->testing_procedures) {
12934-
Entity *e = m->info->testing_procedures[i];
12935-
irValue **found = map_get(&proc->module->values, hash_entity(e));
12939+
Entity *testing_proc = m->info->testing_procedures[i];
12940+
String name = testing_proc->token.string;
12941+
irValue **found = map_get(&m->values, hash_entity(testing_proc));
1293612942
GB_ASSERT(found != nullptr);
12937-
ir_emit_call(proc, *found, empty_args);
12943+
12944+
irValue *v_name = ir_find_or_add_entity_string(m, name);
12945+
irValue *v_p = *found;
12946+
12947+
12948+
irValue *elem_ptr = ir_emit_array_epi(proc, all_tests_array, cast(i32)i);
12949+
irValue *name_ptr = ir_emit_struct_ep(proc, elem_ptr, 0);
12950+
irValue *p_ptr = ir_emit_struct_ep(proc, elem_ptr, 1);
12951+
ir_emit_store(proc, name_ptr, v_name);
12952+
ir_emit_store(proc, p_ptr, v_p);
1293812953
}
12954+
12955+
irValue *all_tests_slice = ir_add_local_generated(proc, slice_type, true);
12956+
ir_fill_slice(proc, all_tests_slice,
12957+
ir_array_elem(proc, all_tests_array),
12958+
ir_const_int(m->info->testing_procedures.count));
12959+
12960+
12961+
irValue *runner = ir_get_package_value(m, str_lit("testing"), str_lit("runner"));
12962+
12963+
auto args = array_make<irValue *>(temporary_allocator(), 1);
12964+
args[0] = ir_emit_load(proc, all_tests_slice);
12965+
ir_emit_call(proc, runner, args);
1293912966
} else {
1294012967
irValue **found = map_get(&proc->module->values, hash_entity(entry_point));
1294112968
if (found != nullptr) {

src/llvm_backend.cpp

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11496,6 +11496,13 @@ lbValue lb_find_runtime_value(lbModule *m, String const &name) {
1149611496
lbValue value = *found;
1149711497
return value;
1149811498
}
11499+
lbValue lb_find_package_value(lbModule *m, String const &pkg, String const &name) {
11500+
Entity *e = find_entity_in_pkg(m->info, pkg, name);
11501+
lbValue *found = map_get(&m->values, hash_entity(e));
11502+
GB_ASSERT_MSG(found != nullptr, "Unable to find value '%.*s.%.*s'", LIT(pkg), LIT(name));
11503+
lbValue value = *found;
11504+
return value;
11505+
}
1149911506

1150011507
lbValue lb_get_type_info_ptr(lbModule *m, Type *type) {
1150111508
i32 index = cast(i32)lb_type_info_index(m->info, type);
@@ -12885,12 +12892,51 @@ void lb_generate_code(lbGenerator *gen) {
1288512892
LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, startup_runtime->type)), startup_runtime->value, nullptr, 0, "");
1288612893

1288712894
if (build_context.command_kind == Command_test) {
12895+
Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test"));
12896+
Type *array_type = alloc_type_array(t_Internal_Test, m->info->testing_procedures.count);
12897+
Type *slice_type = alloc_type_slice(t_Internal_Test);
12898+
lbAddr all_tests_array_addr = lb_add_global_generated(p->module, array_type, {});
12899+
lbValue all_tests_array = lb_addr_get_ptr(p, all_tests_array_addr);
12900+
12901+
LLVMTypeRef lbt_Internal_Test = lb_type(m, t_Internal_Test);
12902+
12903+
LLVMValueRef indices[2] = {};
12904+
indices[0] = LLVMConstInt(lb_type(m, t_i32), 0, false);
12905+
1288812906
for_array(i, m->info->testing_procedures) {
12889-
Entity *e = m->info->testing_procedures[i];
12890-
lbValue *found = map_get(&m->values, hash_entity(e));
12907+
Entity *testing_proc = m->info->testing_procedures[i];
12908+
String name = testing_proc->token.string;
12909+
lbValue *found = map_get(&m->values, hash_entity(testing_proc));
1289112910
GB_ASSERT(found != nullptr);
12892-
lb_emit_call(p, *found, {});
12911+
12912+
lbValue v_name = lb_find_or_add_entity_string(m, name);
12913+
lbValue v_proc = *found;
12914+
12915+
indices[1] = LLVMConstInt(lb_type(m, t_int), i, false);
12916+
12917+
LLVMValueRef vals[2] = {};
12918+
vals[0] = v_name.value;
12919+
vals[1] = v_proc.value;
12920+
GB_ASSERT(LLVMIsConstant(vals[0]));
12921+
GB_ASSERT(LLVMIsConstant(vals[1]));
12922+
12923+
LLVMValueRef dst = LLVMConstInBoundsGEP(all_tests_array.value, indices, gb_count_of(indices));
12924+
LLVMValueRef src = LLVMConstNamedStruct(lbt_Internal_Test, vals, gb_count_of(vals));
12925+
12926+
LLVMBuildStore(p->builder, src, dst);
1289312927
}
12928+
12929+
lbAddr all_tests_slice = lb_add_local_generated(p, slice_type, true);
12930+
lb_fill_slice(p, all_tests_slice,
12931+
lb_array_elem(p, all_tests_array),
12932+
lb_const_int(m, t_int, m->info->testing_procedures.count));
12933+
12934+
12935+
lbValue runner = lb_find_package_value(m, str_lit("testing"), str_lit("runner"));
12936+
12937+
auto args = array_make<lbValue>(heap_allocator(), 1);
12938+
args[0] = lb_addr_load(p, all_tests_slice);
12939+
lb_emit_call(p, runner, args);
1289412940
} else {
1289512941
lbValue *found = map_get(&m->values, hash_entity(entry_point));
1289612942
GB_ASSERT(found != nullptr);

src/parser.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5287,7 +5287,6 @@ ParseFileError process_imported_file(Parser *p, ImportedFile const &imported_fil
52875287
AstFile *file = gb_alloc_item(heap_allocator(), AstFile);
52885288
file->pkg = pkg;
52895289
file->id = cast(i32)(imported_file.index+1);
5290-
52915290
TokenPos err_pos = {0};
52925291
ParseFileError err = init_ast_file(file, fi->fullpath, &err_pos);
52935292
err_pos.file_id = file->id;
@@ -5328,6 +5327,16 @@ ParseFileError process_imported_file(Parser *p, ImportedFile const &imported_fil
53285327
}
53295328
}
53305329

5330+
if (build_context.command_kind == Command_test) {
5331+
String name = file->fullpath;
5332+
name = remove_extension_from_path(name);
5333+
5334+
String test_suffix = str_lit("_test");
5335+
if (string_ends_with(name, test_suffix) && name != test_suffix) {
5336+
file->is_test = true;
5337+
}
5338+
}
5339+
53315340
if (parse_file(p, file)) {
53325341
gb_mutex_lock(&p->file_add_mutex);
53335342
defer (gb_mutex_unlock(&p->file_add_mutex));
@@ -5373,6 +5382,11 @@ ParseFileError parse_packages(Parser *p, String init_filename) {
53735382
try_add_import_path(p, init_fullpath, init_fullpath, init_pos, Package_Init);
53745383
p->init_fullpath = init_fullpath;
53755384

5385+
if (build_context.command_kind == Command_test) {
5386+
String s = get_fullpath_core(heap_allocator(), str_lit("testing"));
5387+
try_add_import_path(p, s, s, init_pos, Package_Normal);
5388+
}
5389+
53765390
for_array(i, build_context.extra_packages) {
53775391
String path = build_context.extra_packages[i];
53785392
String fullpath = path_to_full_path(heap_allocator(), path); // LEAK?

0 commit comments

Comments
 (0)