From 4cc95174a77f7cbb42b371bb892c55a8349bc7fa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 27 Nov 2015 18:55:06 -0700 Subject: add tests for compile errors --- README.md | 6 ++- doc/vim/syntax/zig.vim | 4 +- src/codegen.cpp | 19 ++++++- src/util.hpp | 2 + test/run_tests.cpp | 131 ++++++++++++++++++++++++++++++++++++++++++++----- 5 files changed, 147 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 878257448f..fe130e6f46 100644 --- a/README.md +++ b/README.md @@ -32,11 +32,15 @@ readable, safe, optimal, and concise code to solve any computing problem. ## Roadmap - * test framework to test for compile errors * Simple .so library * Multiple files * inline assembly and syscalls * running code at compile time + * print! macro that takes var args + * panic! macro that prints a stack trace to stderr in debug mode and calls + abort() in release mode + * unreachable codegen to panic("unreachable") in debug mode, and nothing in + release mode * implement a simple game using SDL2 * How should the Widget use case be solved? In Genesis I'm using C++ and inheritance. diff --git a/doc/vim/syntax/zig.vim b/doc/vim/syntax/zig.vim index 05ce1533e7..df04a79887 100644 --- a/doc/vim/syntax/zig.vim +++ b/doc/vim/syntax/zig.vim @@ -1,14 +1,16 @@ " Vim syntax file " Language: Zig " Maintainer: Andrew Kelley -" Latest Revision: 24 November 2015 +" Latest Revision: 27 November 2015 if exists("b:current_syntax") finish endif syn keyword zigKeyword fn return mut const extern unreachable export pub +syn keyword zigType bool i8 u8 i16 u16 i32 u32 i64 u64 isize usize f32 f64 f128 void let b:current_syntax = "zig" hi def link zigKeyword Keyword +hi def link zigType Type diff --git a/src/codegen.cpp b/src/codegen.cpp index ab801ab0b1..3f339ad731 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -83,6 +83,7 @@ struct TypeNode { struct FnDefNode { bool add_implicit_return; + bool skip; }; struct CodeGenNode { @@ -214,7 +215,7 @@ static void find_declarations(CodeGen *g, AstNode *node) { if (buf_eql_str(name, "link")) { g->link_table.put(param, true); } else { - add_node_error(g, node, + add_node_error(g, directive_node, buf_sprintf("invalid directive: '%s'", buf_ptr(name))); } } @@ -242,6 +243,9 @@ static void find_declarations(CodeGen *g, AstNode *node) { if (entry) { add_node_error(g, node, buf_sprintf("redefinition of '%s'", buf_ptr(proto_name))); + assert(!node->codegen_node); + node->codegen_node = allocate(1); + node->codegen_node->data.fn_def_node.skip = true; } else { FnTableEntry *fn_table_entry = allocate(1); fn_table_entry->proto_node = proto_node; @@ -261,6 +265,12 @@ static void find_declarations(CodeGen *g, AstNode *node) { } case NodeTypeFnProto: { + for (int i = 0; i < node->data.fn_proto.directives->length; i += 1) { + AstNode *directive_node = node->data.fn_proto.directives->at(i); + Buf *name = &directive_node->data.directive.name; + add_node_error(g, directive_node, + buf_sprintf("invalid directive: '%s'", buf_ptr(name))); + } for (int i = 0; i < node->data.fn_proto.params.length; i += 1) { AstNode *child = node->data.fn_proto.params.at(i); find_declarations(g, child); @@ -363,11 +373,18 @@ static void analyze_node(CodeGen *g, AstNode *node) { break; case NodeTypeFnDef: { + if (node->codegen_node && node->codegen_node->data.fn_def_node.skip) { + // we detected an error with this function definition which prevents us + // from further analyzing it. + break; + } + AstNode *proto_node = node->data.fn_def.fn_proto; assert(proto_node->type == NodeTypeFnProto); analyze_node(g, proto_node); check_fn_def_control_flow(g, node); + analyze_node(g, node->data.fn_def.body); break; } case NodeTypeFnDecl: diff --git a/src/util.hpp b/src/util.hpp index 74fcf85020..b3180528dd 100644 --- a/src/util.hpp +++ b/src/util.hpp @@ -16,6 +16,8 @@ #define BREAKPOINT __asm("int $0x03") +static const int COMPILE_FAILED_ERR_CODE = 10; // chosen with a random number generator + void zig_panic(const char *format, ...) __attribute__((cold)) __attribute__ ((noreturn)) diff --git a/test/run_tests.cpp b/test/run_tests.cpp index a220e1251e..63f83afe59 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -10,6 +10,7 @@ #include "os.hpp" #include +#include struct TestSourceFile { const char *relative_path; @@ -25,9 +26,10 @@ struct TestCase { ZigList program_args; }; -ZigList test_cases = {0}; -const char *tmp_source_path = ".tmp_source.zig"; -const char *tmp_exe_path = "./.tmp_exe"; +static ZigList test_cases = {0}; +static const char *tmp_source_path = ".tmp_source.zig"; +static const char *tmp_exe_path = "./.tmp_exe"; +static const char *zig_exe = "./zig"; static void add_simple_case(const char *case_name, const char *source, const char *output) { TestCase *test_case = allocate(1); @@ -45,7 +47,32 @@ static void add_simple_case(const char *case_name, const char *source, const cha test_cases.append(test_case); } -static void add_all_test_cases(void) { +static void add_compile_fail_case(const char *case_name, const char *source, int count, ...) { + va_list ap; + va_start(ap, count); + + TestCase *test_case = allocate(1); + test_case->case_name = case_name; + test_case->source = source; + + for (int i = 0; i < count; i += 1) { + const char *arg = va_arg(ap, const char *); + test_case->compile_errors.append(arg); + } + + test_case->compiler_args.append("build"); + test_case->compiler_args.append(tmp_source_path); + test_case->compiler_args.append("--output"); + test_case->compiler_args.append(tmp_exe_path); + test_case->compiler_args.append("--release"); + test_case->compiler_args.append("--strip"); + + test_cases.append(test_case); + + va_end(ap); +} + +static void add_compiling_test_cases(void) { add_simple_case("hello world with libc", R"SOURCE( #link("c") extern { @@ -102,23 +129,102 @@ static void add_all_test_cases(void) { )SOURCE", "OK\n"); } +static void add_compile_failure_test_cases(void) { + add_compile_fail_case("multiple function definitions", R"SOURCE( +fn a() {} +fn a() {} + )SOURCE", 1, "Line 3, column 1: redefinition of 'a'"); + + add_compile_fail_case("bad directive", R"SOURCE( +#bogus1("") +extern { + fn b(); +} +#bogus2("") +fn a() {} + )SOURCE", 2, "Line 2, column 1: invalid directive: 'bogus1'", + "Line 6, column 1: invalid directive: 'bogus2'"); + + add_compile_fail_case("unreachable with return", R"SOURCE( +fn a() -> unreachable {return;} + )SOURCE", 1, "Line 2, column 24: return statement in function with unreachable return type"); + + add_compile_fail_case("control reaches end of non-void function", R"SOURCE( +fn a() -> i32 {} + )SOURCE", 1, "Line 2, column 1: control reaches end of non-void function"); + + add_compile_fail_case("undefined function call", R"SOURCE( +fn a() { + b(); +} + )SOURCE", 1, "Line 3, column 5: undefined function: 'b'"); + + add_compile_fail_case("wrong number of arguments", R"SOURCE( +fn a() { + b(1); +} +fn b(a: i32, b: i32, c: i32) { } + )SOURCE", 1, "Line 3, column 5: wrong number of arguments. Expected 3, got 1."); + + add_compile_fail_case("invalid type", R"SOURCE( +fn a() -> bogus {} + )SOURCE", 1, "Line 2, column 11: invalid type name: 'bogus'"); + + add_compile_fail_case("pointer to unreachable", R"SOURCE( +fn a() -> *mut unreachable {} + )SOURCE", 1, "Line 2, column 11: pointer to unreachable not allowed"); + + add_compile_fail_case("unreachable code", R"SOURCE( +fn a() { + return; + b(); +} + +fn b() {} + )SOURCE", 1, "Line 4, column 5: unreachable code"); +} + +static void print_compiler_invokation(TestCase *test_case, Buf *zig_stderr) { + printf("%s", zig_exe); + for (int i = 0; i < test_case->compiler_args.length; i += 1) { + printf(" %s", test_case->compiler_args.at(i)); + } + printf("\n"); + printf("%s\n", buf_ptr(zig_stderr)); +} + static void run_test(TestCase *test_case) { os_write_file(buf_create_from_str(tmp_source_path), buf_create_from_str(test_case->source)); Buf zig_stderr = BUF_INIT; Buf zig_stdout = BUF_INIT; int return_code; - static const char *zig_exe = "./zig"; os_exec_process(zig_exe, test_case->compiler_args, &return_code, &zig_stderr, &zig_stdout); + if (test_case->compile_errors.length) { + if (return_code) { + for (int i = 0; i < test_case->compile_errors.length; i += 1) { + const char *err_text = test_case->compile_errors.at(i); + if (!strstr(buf_ptr(&zig_stderr), err_text)) { + printf("\n"); + printf("========= Expected this compile error: =========\n"); + printf("%s\n", err_text); + printf("================================================\n"); + print_compiler_invokation(test_case, &zig_stderr); + exit(1); + } + } + return; // success + } else { + printf("\nCompile failed with return code 0 (Expected failure):\n"); + print_compiler_invokation(test_case, &zig_stderr); + exit(1); + } + } + if (return_code != 0) { printf("\nCompile failed with return code %d:\n", return_code); - printf("%s", zig_exe); - for (int i = 0; i < test_case->compiler_args.length; i += 1) { - printf(" %s", test_case->compiler_args.at(i)); - } - printf("\n"); - printf("%s\n", buf_ptr(&zig_stderr)); + print_compiler_invokation(test_case, &zig_stderr); exit(1); } @@ -164,7 +270,8 @@ static void cleanup(void) { } int main(int argc, char **argv) { - add_all_test_cases(); + add_compiling_test_cases(); + add_compile_failure_test_cases(); run_all_tests(); cleanup(); } -- cgit v1.2.3