aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorgingerBill <bill@gingerbill.org>2021-03-14 18:01:31 +0000
committergingerBill <bill@gingerbill.org>2021-03-14 18:01:31 +0000
commit2aa588209e784274136b516224372fdd677d3e8f (patch)
treed7fb6dd1051e34cd039cd49cc6895e4e58391afc
parent10f91a0d3f64902687683ac53dd286b25d3f7d5e (diff)
`odin test` to work with the new `core:testing` package
-rw-r--r--core/testing/runner.odin60
-rw-r--r--core/testing/testing.odin68
-rw-r--r--src/build_settings.cpp2
-rw-r--r--src/checker.cpp41
-rw-r--r--src/ir.cpp33
-rw-r--r--src/llvm_backend.cpp52
-rw-r--r--src/parser.cpp16
7 files changed, 262 insertions, 10 deletions
diff --git a/core/testing/runner.odin b/core/testing/runner.odin
new file mode 100644
index 000000000..35f7afde1
--- /dev/null
+++ b/core/testing/runner.odin
@@ -0,0 +1,60 @@
+//+private
+package testing
+
+import "core:io"
+import "core:os"
+import "core:strings"
+
+reset_t :: proc(t: ^T) {
+ clear(&t.cleanups);
+ t.error_count = 0;
+}
+end_t :: proc(t: ^T) {
+ for i := len(t.cleanups)-1; i >= 0; i -= 1 {
+ c := t.cleanups[i];
+ c.procedure(c.user_data);
+ }
+}
+
+runner :: proc(internal_tests: []Internal_Test) -> bool {
+ stream := os.stream_from_handle(os.stdout);
+ w, _ := io.to_writer(stream);
+
+ t := &T{};
+ t.w = w;
+ reserve(&t.cleanups, 1024);
+ defer delete(t.cleanups);
+
+ total_success_count := 0;
+ total_test_count := len(internal_tests);
+
+ for it in internal_tests {
+ if it.p == nil {
+ total_test_count -= 1;
+ continue;
+ }
+
+ free_all(context.temp_allocator);
+ reset_t(t);
+ defer end_t(t);
+
+ name := strings.trim_prefix(it.name, "test_");
+
+ logf(t, "[Test: %q]", name);
+
+ // TODO(bill): Catch panics
+ {
+ it.p(t);
+ }
+
+ if t.error_count != 0 {
+ logf(t, "[%q : FAILURE]", name);
+ } else {
+ logf(t, "[%q : SUCCESS]", name);
+ total_success_count += 1;
+ }
+ }
+ logf(t, "----------------------------------------");
+ logf(t, "%d/%d SUCCESSFUL", total_success_count, total_test_count);
+ return total_success_count == total_test_count;
+}
diff --git a/core/testing/testing.odin b/core/testing/testing.odin
new file mode 100644
index 000000000..d9d4a53a3
--- /dev/null
+++ b/core/testing/testing.odin
@@ -0,0 +1,68 @@
+package testing
+
+import "core:fmt"
+import "core:io"
+
+Test_Signature :: proc(^T);
+
+Internal_Test :: struct {
+ name: string,
+ p: Test_Signature,
+}
+
+
+Internal_Cleanup :: struct {
+ procedure: proc(rawptr),
+ user_data: rawptr,
+}
+
+T :: struct {
+ error_count: int,
+
+ w: io.Writer,
+
+ cleanups: [dynamic]Internal_Cleanup,
+}
+
+
+error :: proc(t: ^T, args: ..any, loc := #caller_location) {
+ log(t=t, args=args, loc=loc);
+ t.error_count += 1;
+}
+
+errorf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
+ logf(t=t, format=format, args=args, loc=loc);
+ t.error_count += 1;
+}
+
+fail :: proc(t: ^T) {
+ error(t, "FAIL");
+ t.error_count += 1;
+}
+
+failed :: proc(t: ^T) -> bool {
+ return t.error_count != 0;
+}
+
+log :: proc(t: ^T, args: ..any, loc := #caller_location) {
+ fmt.wprintln(t.w, ..args);
+}
+
+logf :: proc(t: ^T, format: string, args: ..any, loc := #caller_location) {
+ fmt.wprintf(t.w, format, ..args);
+ fmt.wprintln(t.w);
+}
+
+
+// cleanup registers a procedure and user_data, which will be called when the test, and all its subtests, complete
+// cleanup proceduers will be called in LIFO (last added, first called) order.
+cleanup :: proc(t: ^T, procedure: proc(rawptr), user_data: rawptr) {
+ append(&t.cleanups, Internal_Cleanup{procedure, user_data});
+}
+
+expect :: proc(t: ^T, ok: bool, msg: string = "", loc := #caller_location) -> bool {
+ if !ok {
+ error(t=t, args={msg}, loc=loc);
+ }
+ return ok;
+}
diff --git a/src/build_settings.cpp b/src/build_settings.cpp
index 8ffc4955a..e1192eea5 100644
--- a/src/build_settings.cpp
+++ b/src/build_settings.cpp
@@ -365,8 +365,8 @@ bool is_excluded_target_filename(String name) {
return true;
}
- String test_suffix = str_lit("_test");
if (build_context.command_kind != Command_test) {
+ String test_suffix = str_lit("_test");
if (string_ends_with(name, test_suffix) && name != test_suffix) {
// Ignore *_test.odin files
return true;
diff --git a/src/checker.cpp b/src/checker.cpp
index d5d6bf5c3..fdf9cefea 100644
--- a/src/checker.cpp
+++ b/src/checker.cpp
@@ -1865,6 +1865,20 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
}
if (build_context.command_kind == Command_test) {
+ AstPackage *testing_package = get_core_package(&c->info, str_lit("testing"));
+ Scope *testing_scope = testing_package->scope;
+
+ // Add all of testing library as a dependency
+ for_array(i, testing_scope->elements.entries) {
+ Entity *e = testing_scope->elements.entries[i].value;
+ if (e != nullptr) {
+ e->flags |= EntityFlag_Used;
+ add_dependency_to_set(c, e);
+ }
+ }
+
+ Entity *test_signature = scope_lookup_current(testing_scope, str_lit("Test_Signature"));
+
AstPackage *pkg = c->info.init_package;
Scope *s = pkg->scope;
for_array(i, s->elements.entries) {
@@ -1884,6 +1898,7 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
continue;
}
+
bool is_tester = false;
if (name != prefix) {
is_tester = true;
@@ -1893,11 +1908,11 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) {
Type *t = base_type(e->type);
GB_ASSERT(t->kind == Type_Proc);
- if (t->Proc.param_count == 0 && t->Proc.result_count == 0) {
+ if (are_types_identical(t, base_type(test_signature->type))) {
// Good
} else {
gbString str = type_to_string(t);
- error(e->token, "Testing procedures must have a signature type of proc(), got %s", str);
+ error(e->token, "Testing procedures must have a signature type of proc(^testing.T), got %s", str);
gb_string_free(str);
is_tester = false;
}
@@ -2103,6 +2118,28 @@ Type *find_core_type(Checker *c, String name) {
return e->type;
}
+
+Entity *find_entity_in_pkg(CheckerInfo *info, String const &pkg, String const &name) {
+ AstPackage *package = get_core_package(info, pkg);
+ Entity *e = scope_lookup_current(package->scope, name);
+ if (e == nullptr) {
+ compiler_error("Could not find type declaration for '%.*s.%.*s'\n", LIT(pkg), LIT(name));
+ // NOTE(bill): This will exit the program as it's cannot continue without it!
+ }
+ return e;
+}
+
+Type *find_type_in_pkg(CheckerInfo *info, String const &pkg, String const &name) {
+ AstPackage *package = get_core_package(info, pkg);
+ Entity *e = scope_lookup_current(package->scope, name);
+ if (e == nullptr) {
+ compiler_error("Could not find type declaration for '%.*s.%.*s'\n", LIT(pkg), LIT(name));
+ // NOTE(bill): This will exit the program as it's cannot continue without it!
+ }
+ GB_ASSERT(e->type != nullptr);
+ return e->type;
+}
+
CheckerTypePath *new_checker_type_path() {
gbAllocator a = heap_allocator();
auto *tp = gb_alloc_item(a, CheckerTypePath);
diff --git a/src/ir.cpp b/src/ir.cpp
index 0f235b914..3022fd869 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -12930,12 +12930,39 @@ void ir_gen_tree(irGen *s) {
ir_emit(proc, ir_alloc_instr(proc, irInstr_StartupRuntime));
Array<irValue *> empty_args = {};
if (build_context.command_kind == Command_test) {
+ Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test"));
+ Type *array_type = alloc_type_array(t_Internal_Test, m->info->testing_procedures.count);
+ Type *slice_type = alloc_type_slice(t_Internal_Test);
+ irValue *all_tests_array = ir_add_global_generated(proc->module, array_type, nullptr);
+
for_array(i, m->info->testing_procedures) {
- Entity *e = m->info->testing_procedures[i];
- irValue **found = map_get(&proc->module->values, hash_entity(e));
+ Entity *testing_proc = m->info->testing_procedures[i];
+ String name = testing_proc->token.string;
+ irValue **found = map_get(&m->values, hash_entity(testing_proc));
GB_ASSERT(found != nullptr);
- ir_emit_call(proc, *found, empty_args);
+
+ irValue *v_name = ir_find_or_add_entity_string(m, name);
+ irValue *v_p = *found;
+
+
+ irValue *elem_ptr = ir_emit_array_epi(proc, all_tests_array, cast(i32)i);
+ irValue *name_ptr = ir_emit_struct_ep(proc, elem_ptr, 0);
+ irValue *p_ptr = ir_emit_struct_ep(proc, elem_ptr, 1);
+ ir_emit_store(proc, name_ptr, v_name);
+ ir_emit_store(proc, p_ptr, v_p);
}
+
+ irValue *all_tests_slice = ir_add_local_generated(proc, slice_type, true);
+ ir_fill_slice(proc, all_tests_slice,
+ ir_array_elem(proc, all_tests_array),
+ ir_const_int(m->info->testing_procedures.count));
+
+
+ irValue *runner = ir_get_package_value(m, str_lit("testing"), str_lit("runner"));
+
+ auto args = array_make<irValue *>(temporary_allocator(), 1);
+ args[0] = ir_emit_load(proc, all_tests_slice);
+ ir_emit_call(proc, runner, args);
} else {
irValue **found = map_get(&proc->module->values, hash_entity(entry_point));
if (found != nullptr) {
diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp
index 09c4ef2fd..ef08edaa4 100644
--- a/src/llvm_backend.cpp
+++ b/src/llvm_backend.cpp
@@ -11496,6 +11496,13 @@ lbValue lb_find_runtime_value(lbModule *m, String const &name) {
lbValue value = *found;
return value;
}
+lbValue lb_find_package_value(lbModule *m, String const &pkg, String const &name) {
+ Entity *e = find_entity_in_pkg(m->info, pkg, name);
+ lbValue *found = map_get(&m->values, hash_entity(e));
+ GB_ASSERT_MSG(found != nullptr, "Unable to find value '%.*s.%.*s'", LIT(pkg), LIT(name));
+ lbValue value = *found;
+ return value;
+}
lbValue lb_get_type_info_ptr(lbModule *m, Type *type) {
i32 index = cast(i32)lb_type_info_index(m->info, type);
@@ -12885,12 +12892,51 @@ void lb_generate_code(lbGenerator *gen) {
LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, startup_runtime->type)), startup_runtime->value, nullptr, 0, "");
if (build_context.command_kind == Command_test) {
+ Type *t_Internal_Test = find_type_in_pkg(m->info, str_lit("testing"), str_lit("Internal_Test"));
+ Type *array_type = alloc_type_array(t_Internal_Test, m->info->testing_procedures.count);
+ Type *slice_type = alloc_type_slice(t_Internal_Test);
+ lbAddr all_tests_array_addr = lb_add_global_generated(p->module, array_type, {});
+ lbValue all_tests_array = lb_addr_get_ptr(p, all_tests_array_addr);
+
+ LLVMTypeRef lbt_Internal_Test = lb_type(m, t_Internal_Test);
+
+ LLVMValueRef indices[2] = {};
+ indices[0] = LLVMConstInt(lb_type(m, t_i32), 0, false);
+
for_array(i, m->info->testing_procedures) {
- Entity *e = m->info->testing_procedures[i];
- lbValue *found = map_get(&m->values, hash_entity(e));
+ Entity *testing_proc = m->info->testing_procedures[i];
+ String name = testing_proc->token.string;
+ lbValue *found = map_get(&m->values, hash_entity(testing_proc));
GB_ASSERT(found != nullptr);
- lb_emit_call(p, *found, {});
+
+ lbValue v_name = lb_find_or_add_entity_string(m, name);
+ lbValue v_proc = *found;
+
+ indices[1] = LLVMConstInt(lb_type(m, t_int), i, false);
+
+ LLVMValueRef vals[2] = {};
+ vals[0] = v_name.value;
+ vals[1] = v_proc.value;
+ GB_ASSERT(LLVMIsConstant(vals[0]));
+ GB_ASSERT(LLVMIsConstant(vals[1]));
+
+ LLVMValueRef dst = LLVMConstInBoundsGEP(all_tests_array.value, indices, gb_count_of(indices));
+ LLVMValueRef src = LLVMConstNamedStruct(lbt_Internal_Test, vals, gb_count_of(vals));
+
+ LLVMBuildStore(p->builder, src, dst);
}
+
+ lbAddr all_tests_slice = lb_add_local_generated(p, slice_type, true);
+ lb_fill_slice(p, all_tests_slice,
+ lb_array_elem(p, all_tests_array),
+ lb_const_int(m, t_int, m->info->testing_procedures.count));
+
+
+ lbValue runner = lb_find_package_value(m, str_lit("testing"), str_lit("runner"));
+
+ auto args = array_make<lbValue>(heap_allocator(), 1);
+ args[0] = lb_addr_load(p, all_tests_slice);
+ lb_emit_call(p, runner, args);
} else {
lbValue *found = map_get(&m->values, hash_entity(entry_point));
GB_ASSERT(found != nullptr);
diff --git a/src/parser.cpp b/src/parser.cpp
index c1cda84c2..1b3a37c3b 100644
--- a/src/parser.cpp
+++ b/src/parser.cpp
@@ -5287,7 +5287,6 @@ ParseFileError process_imported_file(Parser *p, ImportedFile const &imported_fil
AstFile *file = gb_alloc_item(heap_allocator(), AstFile);
file->pkg = pkg;
file->id = cast(i32)(imported_file.index+1);
-
TokenPos err_pos = {0};
ParseFileError err = init_ast_file(file, fi->fullpath, &err_pos);
err_pos.file_id = file->id;
@@ -5328,6 +5327,16 @@ ParseFileError process_imported_file(Parser *p, ImportedFile const &imported_fil
}
}
+ if (build_context.command_kind == Command_test) {
+ String name = file->fullpath;
+ name = remove_extension_from_path(name);
+
+ String test_suffix = str_lit("_test");
+ if (string_ends_with(name, test_suffix) && name != test_suffix) {
+ file->is_test = true;
+ }
+ }
+
if (parse_file(p, file)) {
gb_mutex_lock(&p->file_add_mutex);
defer (gb_mutex_unlock(&p->file_add_mutex));
@@ -5373,6 +5382,11 @@ ParseFileError parse_packages(Parser *p, String init_filename) {
try_add_import_path(p, init_fullpath, init_fullpath, init_pos, Package_Init);
p->init_fullpath = init_fullpath;
+ if (build_context.command_kind == Command_test) {
+ String s = get_fullpath_core(heap_allocator(), str_lit("testing"));
+ try_add_import_path(p, s, s, init_pos, Package_Normal);
+ }
+
for_array(i, build_context.extra_packages) {
String path = build_context.extra_packages[i];
String fullpath = path_to_full_path(heap_allocator(), path); // LEAK?