From 2bd0fd932a4eb1c2e6bc8702450c230186bb3c44 Mon Sep 17 00:00:00 2001 From: gingerBill Date: Tue, 17 Nov 2020 18:50:30 +0000 Subject: [PATCH] Begin rudimentary work on implementing `odin test` tooling with `*_test.odin` files --- src/build_settings.cpp | 18 +++++++++++++-- src/checker.cpp | 50 ++++++++++++++++++++++++++++++++++++++++-- src/checker.hpp | 2 ++ src/ir.cpp | 13 ++++++++--- src/llvm_backend.cpp | 18 +++++++++++---- src/main.cpp | 15 ++++++++----- src/parser.hpp | 2 ++ 7 files changed, 102 insertions(+), 16 deletions(-) diff --git a/src/build_settings.cpp b/src/build_settings.cpp index 0e8f2c5bc..35dc9a7c1 100644 --- a/src/build_settings.cpp +++ b/src/build_settings.cpp @@ -111,9 +111,10 @@ enum CommandKind : u32 { Command_query = 1<<4, Command_doc = 1<<5, Command_version = 1<<6, + Command_test = 1<<7, - Command__does_check = Command_run|Command_build|Command_check|Command_query|Command_doc, - Command__does_build = Command_run|Command_build, + Command__does_check = Command_run|Command_build|Command_check|Command_query|Command_doc|Command_test, + Command__does_build = Command_run|Command_build|Command_test, Command_all = ~(u32)0, }; @@ -333,6 +334,19 @@ bool is_excluded_target_filename(String name) { String original_name = name; name = remove_extension_from_path(name); + if (string_starts_with(name, str_lit("."))) { + // Ignore .*.odin files + return true; + } + + String test_suffix = str_lit("_test"); + if (build_context.command_kind != Command_test) { + if (string_ends_with(name, test_suffix) && name != test_suffix) { + // Ignore *_test.odin files + return true; + } + } + String str1 = {}; String str2 = {}; isize n = 0; diff --git a/src/checker.cpp b/src/checker.cpp index 88aed4c62..61b1e4bae 100644 --- a/src/checker.cpp +++ b/src/checker.cpp @@ -839,6 +839,8 @@ void init_checker_info(CheckerInfo *i) { array_init(&i->variable_init_order, a); array_init(&i->required_foreign_imports_through_force, a); array_init(&i->required_global_variables, a); + array_init(&i->testing_procedures, a, 0, 0); + i->allow_identifier_uses = build_context.query_data_set_settings.kind == QueryDataSet_GoToDefinitions; if (i->allow_identifier_uses) { @@ -1854,7 +1856,50 @@ void generate_minimum_dependency_set(Checker *c, Entity *start) { } } - add_dependency_to_set(c, start); + if (build_context.command_kind == Command_test) { + AstPackage *pkg = c->info.init_package; + Scope *s = pkg->scope; + for_array(i, s->elements.entries) { + Entity *e = s->elements.entries[i].value; + if (e->kind != Entity_Procedure) { + continue; + } + String name = e->token.string; + String prefix = str_lit("test_"); + + + if (!string_starts_with(name, prefix)) { + continue; + } + + bool is_tester = false; + if (name != prefix) { + is_tester = true; + } else { + if (e->file && e->file->is_test) { + error(e->token, "Invalid testing procedure name: %.*s", LIT(name)); + } + } + + Type *t = base_type(e->type); + GB_ASSERT(t->kind == Type_Proc); + if (t->Proc.param_count == 0 && t->Proc.result_count == 0) { + // Good + } else { + gbString str = type_to_string(t); + error(e->token, "Testing procedures must have a signature type of proc(), got %s", str); + gb_string_free(str); + is_tester = false; + } + + if (is_tester) { + add_dependency_to_set(c, e); + array_add(&c->info.testing_procedures, e); + } + } + } else { + add_dependency_to_set(c, start); + } } bool is_entity_a_dependency(Entity *e) { @@ -4344,6 +4389,7 @@ void check_parsed_files(Checker *c) { string_map_set(&c->info.packages, p->fullpath, p); if (scope->flags&ScopeFlag_Init) { + c->info.init_package = p; c->info.init_scope = scope; } if (p->kind == Package_Runtime) { @@ -4613,7 +4659,7 @@ void check_parsed_files(Checker *c) { TIME_SECTION("check entry point"); - if (build_context.build_mode == BuildMode_Executable && !build_context.no_entry_point) { + if (build_context.build_mode == BuildMode_Executable && !build_context.no_entry_point && build_context.command_kind != Command_test) { Scope *s = c->info.init_scope; GB_ASSERT(s != nullptr); GB_ASSERT(s->flags&ScopeFlag_Init); diff --git a/src/checker.hpp b/src/checker.hpp index b986296e0..72c4b148c 100644 --- a/src/checker.hpp +++ b/src/checker.hpp @@ -273,6 +273,7 @@ struct CheckerInfo { AstPackage * builtin_package; AstPackage * runtime_package; + AstPackage * init_package; Scope * init_scope; Entity * entry_point; PtrSet minimum_dependency_set; @@ -283,6 +284,7 @@ struct CheckerInfo { Map atom_op_map; // Key: Ast * + Array testing_procedures; bool allow_identifier_uses; Array identifier_uses; // only used by 'odin query' diff --git a/src/ir.cpp b/src/ir.cpp index 1f2819ccf..819442030 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12781,11 +12781,18 @@ void ir_gen_tree(irGen *s) { ir_fill_slice(proc, global_args, argv, ir_emit_conv(proc, argc, t_int)); ir_emit(proc, ir_alloc_instr(proc, irInstr_StartupRuntime)); - { + Array empty_args = {}; + if (build_context.command_kind == Command_test) { + for_array(i, m->info->testing_procedures) { + Entity *e = m->info->testing_procedures[i]; + irValue **found = map_get(&proc->module->values, hash_entity(e)); + GB_ASSERT(found != nullptr); + ir_emit_call(proc, *found, empty_args); + } + } else { irValue **found = map_get(&proc->module->values, hash_entity(entry_point)); if (found != nullptr) { - Array args = {}; - ir_emit_call(proc, *found, args); + ir_emit_call(proc, *found, empty_args); } } diff --git a/src/llvm_backend.cpp b/src/llvm_backend.cpp index 50d200551..ea5081be4 100644 --- a/src/llvm_backend.cpp +++ b/src/llvm_backend.cpp @@ -12875,11 +12875,21 @@ void lb_generate_code(lbGenerator *gen) { lb_begin_procedure_body(p); - lbValue *found = map_get(&m->values, hash_entity(entry_point)); - GB_ASSERT(found != nullptr); - LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, startup_runtime->type)), startup_runtime->value, nullptr, 0, ""); - LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, found->type)), found->value, nullptr, 0, ""); + + if (build_context.command_kind == Command_test) { + for_array(i, m->info->testing_procedures) { + Entity *e = m->info->testing_procedures[i]; + lbValue *found = map_get(&m->values, hash_entity(e)); + GB_ASSERT(found != nullptr); + lb_emit_call(p, *found, {}); + } + } else { + lbValue *found = map_get(&m->values, hash_entity(entry_point)); + GB_ASSERT(found != nullptr); + LLVMBuildCall2(p->builder, LLVMGetElementType(lb_type(m, found->type)), found->value, nullptr, 0, ""); + } + LLVMBuildRet(p->builder, LLVMConstInt(lb_type(m, t_i32), 0, false)); lb_end_procedure_body(p); diff --git a/src/main.cpp b/src/main.cpp index 5faedbccb..b83c420ae 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -675,7 +675,7 @@ ExactValue build_param_to_exact_value(String name, String param) { bool parse_build_flags(Array args) { auto build_flags = array_make(heap_allocator(), 0, BuildFlag_COUNT); add_flag(&build_flags, BuildFlag_Help, str_lit("help"), BuildFlagParam_None, Command_all); - add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build); + add_flag(&build_flags, BuildFlag_OutFile, str_lit("out"), BuildFlagParam_String, Command__does_build &~ Command_test); add_flag(&build_flags, BuildFlag_OptimizationLevel, str_lit("opt"), BuildFlagParam_Integer, Command__does_build); add_flag(&build_flags, BuildFlag_ShowTimings, str_lit("show-timings"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_ShowMoreTimings, str_lit("show-more-timings"), BuildFlagParam_None, Command__does_check); @@ -693,7 +693,7 @@ bool parse_build_flags(Array args) { add_flag(&build_flags, BuildFlag_NoBoundsCheck, str_lit("no-bounds-check"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoDynamicLiterals, str_lit("no-dynamic-literals"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_NoCRT, str_lit("no-crt"), BuildFlagParam_None, Command__does_build); - add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check); + add_flag(&build_flags, BuildFlag_NoEntryPoint, str_lit("no-entry-point"), BuildFlagParam_None, Command__does_check &~ Command_test); add_flag(&build_flags, BuildFlag_UseLLD, str_lit("lld"), BuildFlagParam_None, Command__does_build); add_flag(&build_flags, BuildFlag_Vet, str_lit("vet"), BuildFlagParam_None, Command__does_check); add_flag(&build_flags, BuildFlag_UseLLVMApi, str_lit("llvm-api"), BuildFlagParam_None, Command__does_build); @@ -1540,6 +1540,8 @@ void print_show_help(String const arg0, String const &command) { print_usage_line(1, "run same as 'build', but also then runs the newly compiled executable."); } else if (command == "check") { print_usage_line(1, "check parse and type check .odin file"); + } else if (command == "test") { + print_usage_line(1, "test build ands runs 'test_*' procedures in the initial package"); } else if (command == "query") { print_usage_line(1, "query [experimental] parse, type check, and output a .json file containing information about the program"); } else if (command == "doc") { @@ -1550,9 +1552,9 @@ void print_show_help(String const arg0, String const &command) { bool doc = command == "doc"; bool build = command == "build"; - bool run_or_build = command == "run" || command == "build"; + bool run_or_build = command == "run" || command == "build" || command == "test"; bool check_only = command == "check"; - bool check = command == "run" || command == "build" || command == "check"; + bool check = run_or_build || command == "check"; print_usage_line(0, ""); print_usage_line(1, "Flags"); @@ -1850,12 +1852,15 @@ int main(int arg_count, char const **arg_ptr) { String run_args_string = {}; bool run_output = false; - if (command == "run") { + if (command == "run" || command == "test") { if (args.count < 3) { usage(args[0]); return 1; } build_context.command_kind = Command_run; + if (command == "test") { + build_context.command_kind = Command_test; + } Array run_args = array_make(heap_allocator(), 0, arg_count); defer (array_free(&run_args)); diff --git a/src/parser.hpp b/src/parser.hpp index 3be7939fa..48af0b293 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -107,6 +107,8 @@ struct AstFile { f64 time_to_tokenize; // seconds f64 time_to_parse; // seconds + bool is_test; + CommentGroup *lead_comment; // Comment (block) before the decl CommentGroup *line_comment; // Comment after the semicolon CommentGroup *docs; // current docs