Browse Source

Begin rudimentary work on implementing `odin test` tooling with `*_test.odin` files

gingerBill 5 năm trước cách đây
mục cha
commit
2bd0fd932a
7 tập tin đã thay đổi với 102 bổ sung16 xóa
  1. 16 2
      src/build_settings.cpp
  2. 48 2
      src/checker.cpp
  3. 2 0
      src/checker.hpp
  4. 10 3
      src/ir.cpp
  5. 14 4
      src/llvm_backend.cpp
  6. 10 5
      src/main.cpp
  7. 2 0
      src/parser.hpp

+ 16 - 2
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;

+ 48 - 2
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);

+ 2 - 0
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<Entity *>      minimum_dependency_set;
@@ -283,6 +284,7 @@ struct CheckerInfo {
 
 	Map<AtomOpMapEntry>   atom_op_map; // Key: Ast *
 
+	Array<Entity *> testing_procedures;
 
 	bool allow_identifier_uses;
 	Array<Ast *> identifier_uses; // only used by 'odin query'

+ 10 - 3
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<irValue *> 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<irValue *> args = {};
-				ir_emit_call(proc, *found, args);
+				ir_emit_call(proc, *found, empty_args);
 			}
 		}
 

+ 14 - 4
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);

+ 10 - 5
src/main.cpp

@@ -675,7 +675,7 @@ ExactValue build_param_to_exact_value(String name, String param) {
 bool parse_build_flags(Array<String> args) {
 	auto build_flags = array_make<BuildFlag>(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<String> 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<String> run_args = array_make<String>(heap_allocator(), 0, arg_count);
 		defer (array_free(&run_args));

+ 2 - 0
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