add tests for compile errors

This commit is contained in:
Andrew Kelley 2015-11-27 18:55:06 -07:00
parent 4068897b6b
commit 4cc95174a7
5 changed files with 147 additions and 15 deletions

View file

@ -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.

View file

@ -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

View file

@ -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<CodeGenNode>(1);
node->codegen_node->data.fn_def_node.skip = true;
} else {
FnTableEntry *fn_table_entry = allocate<FnTableEntry>(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:

View file

@ -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))

View file

@ -10,6 +10,7 @@
#include "os.hpp"
#include <stdio.h>
#include <stdarg.h>
struct TestSourceFile {
const char *relative_path;
@ -25,9 +26,10 @@ struct TestCase {
ZigList<const char *> program_args;
};
ZigList<TestCase*> test_cases = {0};
const char *tmp_source_path = ".tmp_source.zig";
const char *tmp_exe_path = "./.tmp_exe";
static ZigList<TestCase*> 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<TestCase>(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<TestCase>(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();
}