diff --git a/CMakeLists.txt b/CMakeLists.txt index 67adb26041..86dca3aea5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,14 +63,6 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp" ) -set(TEST_SOURCES - "${CMAKE_SOURCE_DIR}/src/buffer.cpp" - "${CMAKE_SOURCE_DIR}/src/util.cpp" - "${CMAKE_SOURCE_DIR}/src/os.cpp" - "${CMAKE_SOURCE_DIR}/src/error.cpp" - "${CMAKE_SOURCE_DIR}/test/run_tests.cpp" -) - set(C_HEADERS "${CMAKE_SOURCE_DIR}/c_headers/Intrin.h" "${CMAKE_SOURCE_DIR}/c_headers/__stddef_max_align_t.h" @@ -248,19 +240,12 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${Z install(FILES "${CMAKE_SOURCE_DIR}/std/special/zigrt.zig" DESTINATION "${ZIG_STD_DEST}/special") install(FILES "${CMAKE_SOURCE_DIR}/std/target.zig" DESTINATION "${ZIG_STD_DEST}") -add_executable(run_tests ${TEST_SOURCES}) -target_link_libraries(run_tests) -set_target_properties(run_tests PROPERTIES - COMPILE_FLAGS ${EXE_CFLAGS} - LINK_FLAGS ${EXE_LDFLAGS} -) - if (ZIG_TEST_COVERAGE) add_custom_target(coverage DEPENDS run_tests WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND lcov --directory . --zerocounters --rc lcov_branch_coverage=1 - COMMAND ./run_tests + COMMAND ./zig build --build-file ../build.zig test COMMAND lcov --directory . --capture --output-file coverage.info --rc lcov_branch_coverage=1 COMMAND lcov --remove coverage.info '/usr/*' --output-file coverage.info.cleaned --rc lcov_branch_coverage=1 COMMAND genhtml -o coverage coverage.info.cleaned --rc lcov_branch_coverage=1 diff --git a/build.zig b/build.zig index fc9df1149f..918a629cf6 100644 --- a/build.zig +++ b/build.zig @@ -44,4 +44,5 @@ pub fn build(b: &Builder) { test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); + test_step.dependOn(tests.addParseHTests(b, test_filter)); } diff --git a/test/parseh.zig b/test/parseh.zig new file mode 100644 index 0000000000..08740a0acd --- /dev/null +++ b/test/parseh.zig @@ -0,0 +1,243 @@ +const tests = @import("tests.zig"); + +pub fn addCases(cases: &tests.ParseHContext) { + cases.addAllowWarnings("simple data types", + \\#include + \\int foo(char a, unsigned char b, signed char c); + \\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype + \\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); + \\void baz(int8_t a, int16_t b, int32_t c, int64_t d); + , + \\pub extern fn foo(a: u8, b: u8, c: i8) -> c_int; + , + \\pub extern fn bar(a: u8, b: u16, c: u32, d: u64); + , + \\pub extern fn baz(a: i8, b: i16, c: i32, d: i64); + ); + + cases.add("noreturn attribute", + \\void foo(void) __attribute__((noreturn)); + , + \\pub extern fn foo() -> noreturn; + ); + + cases.add("enums", + \\enum Foo { + \\ FooA, + \\ FooB, + \\ Foo1, + \\}; + , + \\pub const enum_Foo = extern enum { + \\ A, + \\ B, + \\ @"1", + \\}; + , + \\pub const FooA = 0; + , + \\pub const FooB = 1; + , + \\pub const Foo1 = 2; + , + \\pub const Foo = enum_Foo + ); + + cases.add("restrict -> noalias", + \\void foo(void *restrict bar, void *restrict); + , + \\pub extern fn foo(noalias bar: ?&c_void, noalias arg1: ?&c_void); + ); + + cases.add("simple struct", + \\struct Foo { + \\ int x; + \\ char *y; + \\}; + , + \\const struct_Foo = extern struct { + \\ x: c_int, + \\ y: ?&u8, + \\}; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("qualified struct and enum", + \\struct Foo { + \\ int x; + \\ int y; + \\}; + \\enum Bar { + \\ BarA, + \\ BarB, + \\}; + \\void func(struct Foo *a, enum Bar **b); + , + \\pub const struct_Foo = extern struct { + \\ x: c_int, + \\ y: c_int, + \\}; + , + \\pub const enum_Bar = extern enum { + \\ A, + \\ B, + \\}; + , + \\pub const BarA = 0; + , + \\pub const BarB = 1; + , + \\pub extern fn func(a: ?&struct_Foo, b: ?&?&enum_Bar); + , + \\pub const Foo = struct_Foo; + , + \\pub const Bar = enum_Bar; + ); + + cases.add("constant size array", + \\void func(int array[20]); + , + \\pub extern fn func(array: ?&c_int); + ); + + cases.add("self referential struct with function pointer", + \\struct Foo { + \\ void (*derp)(struct Foo *foo); + \\}; + , + \\pub const struct_Foo = extern struct { + \\ derp: ?extern fn(?&struct_Foo), + \\}; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("struct prototype used in func", + \\struct Foo; + \\struct Foo *some_func(struct Foo *foo, int x); + , + \\pub const struct_Foo = @OpaqueType(); + , + \\pub extern fn some_func(foo: ?&struct_Foo, x: c_int) -> ?&struct_Foo; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("#define a char literal", + \\#define A_CHAR 'a' + , + \\pub const A_CHAR = 97; + ); + + cases.add("#define an unsigned integer literal", + \\#define CHANNEL_COUNT 24 + , + \\pub const CHANNEL_COUNT = 24; + ); + + cases.add("#define referencing another #define", + \\#define THING2 THING1 + \\#define THING1 1234 + , + \\pub const THING1 = 1234; + , + \\pub const THING2 = THING1; + ); + + cases.add("variables", + \\extern int extern_var; + \\static const int int_var = 13; + , + \\pub extern var extern_var: c_int; + , + \\pub const int_var: c_int = 13; + ); + + cases.add("circular struct definitions", + \\struct Bar; + \\ + \\struct Foo { + \\ struct Bar *next; + \\}; + \\ + \\struct Bar { + \\ struct Foo *next; + \\}; + , + \\pub const struct_Bar = extern struct { + \\ next: ?&struct_Foo, + \\}; + , + \\pub const struct_Foo = extern struct { + \\ next: ?&struct_Bar, + \\}; + ); + + cases.add("typedef void", + \\typedef void Foo; + \\Foo fun(Foo *a); + , + \\pub const Foo = c_void; + , + \\pub extern fn fun(a: ?&c_void); + ); + + cases.add("generate inline func for #define global extern fn", + \\extern void (*fn_ptr)(void); + \\#define foo fn_ptr + \\ + \\extern char (*fn_ptr2)(int, float); + \\#define bar fn_ptr2 + , + \\pub extern var fn_ptr: ?extern fn(); + , + \\pub fn foo(); + , + \\pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8; + , + \\pub fn bar(arg0: c_int, arg1: f32) -> u8; + ); + + cases.add("#define string", + \\#define foo "a string" + , + \\pub const foo: &const u8 = &(c str lit); + ); + + cases.add("__cdecl doesn't mess up function pointers", + \\void foo(void (__cdecl *fn_ptr)(void)); + , + \\pub extern fn foo(fn_ptr: ?extern fn()); + ); + + cases.add("comment after integer literal", + \\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , + \\pub const SDL_INIT_VIDEO = 32; + ); + + cases.add("zig keywords in C code", + \\struct comptime { + \\ int defer; + \\}; + , + \\pub const struct_comptime = extern struct { + \\ @"defer": c_int, + \\}; + , + \\pub const @"comptime" = struct_comptime; + ); + + cases.add("macro defines string literal with octal", + \\#define FOO "aoeu\023 derp" + \\#define FOO2 "aoeu\0234 derp" + \\#define FOO_CHAR '\077' + , + \\pub const FOO: &const u8 = &(c str lit); + , + \\pub const FOO2: &const u8 = &(c str lit); + , + \\pub const FOO_CHAR = 63; + ); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp deleted file mode 100644 index 97776b61d4..0000000000 --- a/test/run_tests.cpp +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright (c) 2015 Andrew Kelley - * - * This file is part of zig, which is MIT licensed. - * See http://opensource.org/licenses/MIT - */ - -#include "list.hpp" -#include "buffer.hpp" -#include "os.hpp" -#include "error.hpp" -#include "config.h" - -#include -#include - -enum TestSpecial { - TestSpecialNone, - TestSpecialLinkStep, -}; - -struct TestSourceFile { - const char *relative_path; - const char *source_code; -}; - -enum AllowWarnings { - AllowWarningsNo, - AllowWarningsYes, -}; - -struct TestCase { - const char *case_name; - const char *output; - ZigList source_files; - ZigList compile_errors; - ZigList compiler_args; - ZigList linker_args; - ZigList program_args; - bool is_parseh; - TestSpecial special; - bool is_release_mode; - AllowWarnings allow_warnings; -}; - -static ZigList test_cases = {0}; -static const char *tmp_source_path = ".tmp_source.zig"; -static const char *tmp_h_path = ".tmp_header.h"; - -#if defined(_WIN32) -static const char *tmp_exe_path = "./.tmp_exe.exe"; -static const char *zig_exe = "./zig.exe"; -#define NL "\r\n" -#else -static const char *tmp_exe_path = "./.tmp_exe"; -static const char *zig_exe = "./zig"; -#define NL "\n" -#endif - -static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warnings, - const char *source, size_t count, ...) -{ - va_list ap; - va_start(ap, count); - - TestCase *test_case = allocate(1); - test_case->case_name = case_name; - test_case->is_parseh = true; - test_case->allow_warnings = allow_warnings; - - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_h_path; - test_case->source_files.at(0).source_code = source; - - for (size_t 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("parseh"); - test_case->compiler_args.append(tmp_h_path); - //test_case->compiler_args.append("--verbose"); - - test_cases.append(test_case); - - va_end(ap); - return test_case; -} - -////////////////////////////////////////////////////////////////////////////// - -static void add_parseh_test_cases(void) { - add_parseh_case("simple data types", AllowWarningsYes, R"SOURCE( -#include -int foo(char a, unsigned char b, signed char c); -int foo(char a, unsigned char b, signed char c); // test a duplicate prototype -void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); -void baz(int8_t a, int16_t b, int32_t c, int64_t d); - )SOURCE", 3, - "pub extern fn foo(a: u8, b: u8, c: i8) -> c_int;", - "pub extern fn bar(a: u8, b: u16, c: u32, d: u64);", - "pub extern fn baz(a: i8, b: i16, c: i32, d: i64);"); - - add_parseh_case("noreturn attribute", AllowWarningsNo, R"SOURCE( -void foo(void) __attribute__((noreturn)); - )SOURCE", 1, R"OUTPUT(pub extern fn foo() -> noreturn;)OUTPUT"); - - add_parseh_case("enums", AllowWarningsNo, R"SOURCE( -enum Foo { - FooA, - FooB, - Foo1, -}; - )SOURCE", 5, R"(pub const enum_Foo = extern enum { - A, - B, - @"1", -};)", - R"(pub const FooA = 0;)", - R"(pub const FooB = 1;)", - R"(pub const Foo1 = 2;)", - R"(pub const Foo = enum_Foo;)"); - - add_parseh_case("restrict -> noalias", AllowWarningsNo, R"SOURCE( -void foo(void *restrict bar, void *restrict); - )SOURCE", 1, R"OUTPUT(pub extern fn foo(noalias bar: ?&c_void, noalias arg1: ?&c_void);)OUTPUT"); - - add_parseh_case("simple struct", AllowWarningsNo, R"SOURCE( -struct Foo { - int x; - char *y; -}; - )SOURCE", 2, - R"OUTPUT(const struct_Foo = extern struct { - x: c_int, - y: ?&u8, -};)OUTPUT", R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - add_parseh_case("qualified struct and enum", AllowWarningsNo, R"SOURCE( -struct Foo { - int x; - int y; -}; -enum Bar { - BarA, - BarB, -}; -void func(struct Foo *a, enum Bar **b); - )SOURCE", 7, R"OUTPUT(pub const struct_Foo = extern struct { - x: c_int, - y: c_int, -};)OUTPUT", R"OUTPUT(pub const enum_Bar = extern enum { - A, - B, -};)OUTPUT", - R"OUTPUT(pub const BarA = 0;)OUTPUT", - R"OUTPUT(pub const BarB = 1;)OUTPUT", - "pub extern fn func(a: ?&struct_Foo, b: ?&?&enum_Bar);", - R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT", - R"OUTPUT(pub const Bar = enum_Bar;)OUTPUT"); - - add_parseh_case("constant size array", AllowWarningsNo, R"SOURCE( -void func(int array[20]); - )SOURCE", 1, "pub extern fn func(array: ?&c_int);"); - - - add_parseh_case("self referential struct with function pointer", - AllowWarningsNo, R"SOURCE( -struct Foo { - void (*derp)(struct Foo *foo); -}; - )SOURCE", 2, R"OUTPUT(pub const struct_Foo = extern struct { - derp: ?extern fn(?&struct_Foo), -};)OUTPUT", R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - - add_parseh_case("struct prototype used in func", AllowWarningsNo, R"SOURCE( -struct Foo; -struct Foo *some_func(struct Foo *foo, int x); - )SOURCE", 3, R"OUTPUT(pub const struct_Foo = @OpaqueType();)OUTPUT", - R"OUTPUT(pub extern fn some_func(foo: ?&struct_Foo, x: c_int) -> ?&struct_Foo;)OUTPUT", - R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - - add_parseh_case("#define a char literal", AllowWarningsNo, R"SOURCE( -#define A_CHAR 'a' - )SOURCE", 1, R"OUTPUT(pub const A_CHAR = 97;)OUTPUT"); - - - add_parseh_case("#define an unsigned integer literal", AllowWarningsNo, - R"SOURCE( -#define CHANNEL_COUNT 24 - )SOURCE", 1, R"OUTPUT(pub const CHANNEL_COUNT = 24;)OUTPUT"); - - - add_parseh_case("#define referencing another #define", AllowWarningsNo, - R"SOURCE( -#define THING2 THING1 -#define THING1 1234 - )SOURCE", 2, - "pub const THING1 = 1234;", - "pub const THING2 = THING1;"); - - - add_parseh_case("variables", AllowWarningsNo, R"SOURCE( -extern int extern_var; -static const int int_var = 13; - )SOURCE", 2, - "pub extern var extern_var: c_int;", - "pub const int_var: c_int = 13;"); - - - add_parseh_case("circular struct definitions", AllowWarningsNo, R"SOURCE( -struct Bar; - -struct Foo { - struct Bar *next; -}; - -struct Bar { - struct Foo *next; -}; - )SOURCE", 2, - R"SOURCE(pub const struct_Bar = extern struct { - next: ?&struct_Foo, -};)SOURCE", - R"SOURCE(pub const struct_Foo = extern struct { - next: ?&struct_Bar, -};)SOURCE"); - - - add_parseh_case("typedef void", AllowWarningsNo, R"SOURCE( -typedef void Foo; -Foo fun(Foo *a); - )SOURCE", 2, - "pub const Foo = c_void;", - "pub extern fn fun(a: ?&c_void);"); - - add_parseh_case("generate inline func for #define global extern fn", AllowWarningsNo, - R"SOURCE( -extern void (*fn_ptr)(void); -#define foo fn_ptr - -extern char (*fn_ptr2)(int, float); -#define bar fn_ptr2 - )SOURCE", 4, - "pub extern var fn_ptr: ?extern fn();", - "pub fn foo();", - "pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8;", - "pub fn bar(arg0: c_int, arg1: f32) -> u8;"); - - - add_parseh_case("#define string", AllowWarningsNo, R"SOURCE( -#define foo "a string" - )SOURCE", 1, "pub const foo: &const u8 = &(c str lit);"); - - add_parseh_case("__cdecl doesn't mess up function pointers", AllowWarningsNo, R"SOURCE( -void foo(void (__cdecl *fn_ptr)(void)); - )SOURCE", 1, "pub extern fn foo(fn_ptr: ?extern fn());"); - - add_parseh_case("comment after integer literal", AllowWarningsNo, R"SOURCE( -#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - )SOURCE", 1, "pub const SDL_INIT_VIDEO = 32;"); - - add_parseh_case("zig keywords in C code", AllowWarningsNo, R"SOURCE( -struct comptime { - int defer; -}; - )SOURCE", 2, R"(pub const struct_comptime = extern struct { - @"defer": c_int, -};)", R"(pub const @"comptime" = struct_comptime;)"); - - add_parseh_case("macro defines string literal with octal", AllowWarningsNo, R"SOURCE( -#define FOO "aoeu\023 derp" -#define FOO2 "aoeu\0234 derp" -#define FOO_CHAR '\077' - )SOURCE", 3, - R"(pub const FOO: &const u8 = &(c str lit);)", - R"(pub const FOO2: &const u8 = &(c str lit);)", - R"(pub const FOO_CHAR = 63;)"); -} - -static void print_compiler_invocation(TestCase *test_case) { - printf("%s", zig_exe); - for (size_t i = 0; i < test_case->compiler_args.length; i += 1) { - printf(" %s", test_case->compiler_args.at(i)); - } - printf("\n"); -} - -static void print_linker_invocation(TestCase *test_case) { - printf("%s", zig_exe); - for (size_t i = 0; i < test_case->linker_args.length; i += 1) { - printf(" %s", test_case->linker_args.at(i)); - } - printf("\n"); -} - - -static void print_exe_invocation(TestCase *test_case) { - printf("%s", tmp_exe_path); - for (size_t i = 0; i < test_case->program_args.length; i += 1) { - printf(" %s", test_case->program_args.at(i)); - } - printf("\n"); -} - -static void run_test(TestCase *test_case) { - for (size_t i = 0; i < test_case->source_files.length; i += 1) { - TestSourceFile *test_source = &test_case->source_files.at(i); - os_write_file( - buf_create_from_str(test_source->relative_path), - buf_create_from_str(test_source->source_code)); - } - - Buf zig_stderr = BUF_INIT; - Buf zig_stdout = BUF_INIT; - int err; - Termination term; - if ((err = os_exec_process(zig_exe, test_case->compiler_args, &term, &zig_stderr, &zig_stdout))) { - fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err)); - } - - if (!test_case->is_parseh && test_case->compile_errors.length) { - if (term.how != TerminationIdClean || term.code != 0) { - for (size_t 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_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - return; // success - } else { - printf("\nCompile failed with return code 0 (Expected failure):\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nCompile failed:\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - - if (test_case->is_parseh) { - if (buf_len(&zig_stderr) > 0) { - printf("\nparseh emitted warnings:\n"); - printf("------------------------------\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - printf("------------------------------\n"); - if (test_case->allow_warnings == AllowWarningsNo) { - exit(1); - } - } - - for (size_t i = 0; i < test_case->compile_errors.length; i += 1) { - const char *output = test_case->compile_errors.at(i); - - if (!strstr(buf_ptr(&zig_stdout), output)) { - printf("\n"); - printf("========= Expected this output: =========\n"); - printf("%s\n", output); - printf("================================================\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stdout)); - exit(1); - } - } - } else { - if (test_case->special == TestSpecialLinkStep) { - Buf link_stderr = BUF_INIT; - Buf link_stdout = BUF_INIT; - int err; - Termination term; - if ((err = os_exec_process(zig_exe, test_case->linker_args, &term, &link_stderr, &link_stdout))) { - fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err)); - } - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nLink failed:\n"); - print_linker_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - - Buf program_stderr = BUF_INIT; - Buf program_stdout = BUF_INIT; - os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout); - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nProgram exited with error\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("%s\n", buf_ptr(&program_stderr)); - exit(1); - } - - if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) { - printf("\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("==== Test failed. Expected output: ====\n"); - printf("%s\n", test_case->output); - printf("========= Actual output: ==============\n"); - printf("%s\n", buf_ptr(&program_stdout)); - printf("=======================================\n"); - exit(1); - } - } - - for (size_t i = 0; i < test_case->source_files.length; i += 1) { - TestSourceFile *test_source = &test_case->source_files.at(i); - remove(test_source->relative_path); - } -} - -static void run_all_tests(const char *grep_text) { - for (size_t i = 0; i < test_cases.length; i += 1) { - TestCase *test_case = test_cases.at(i); - if (grep_text != nullptr && strstr(test_case->case_name, grep_text) == nullptr) { - continue; - } - - printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name); - fflush(stdout); - run_test(test_case); - printf("OK\n"); - } - printf("%zu tests passed.\n", test_cases.length); -} - -static void cleanup(void) { - remove(tmp_source_path); - remove(tmp_h_path); - remove(tmp_exe_path); -} - -static int usage(const char *arg0) { - fprintf(stderr, "Usage: %s [--grep text]\n", arg0); - return 1; -} - -int main(int argc, char **argv) { - const char *grep_text = nullptr; - for (int i = 1; i < argc; i += 1) { - const char *arg = argv[i]; - if (i + 1 >= argc) { - return usage(argv[0]); - } else { - i += 1; - if (strcmp(arg, "--grep") == 0) { - grep_text = argv[i]; - } else { - return usage(argv[0]); - } - } - } - add_parseh_test_cases(); - run_all_tests(grep_text); - cleanup(); -} diff --git a/test/tests.zig b/test/tests.zig index 8e15bcc126..88e2231304 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -10,13 +10,14 @@ const mem = std.mem; const fmt = std.fmt; const List = std.list.List; -error TestFailed; +const compare_output = @import("compare_output.zig"); +const build_examples = @import("build_examples.zig"); +const compile_errors = @import("compile_errors.zig"); +const assemble_and_link = @import("assemble_and_link.zig"); +const debug_safety = @import("debug_safety.zig"); +const parseh = @import("parseh.zig"); -pub const compare_output = @import("compare_output.zig"); -pub const build_examples = @import("build_examples.zig"); -pub const compile_errors = @import("compile_errors.zig"); -pub const assemble_and_link = @import("assemble_and_link.zig"); -pub const debug_safety = @import("debug_safety.zig"); +error TestFailed; pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { const cases = %%b.allocator.create(CompareOutputContext); @@ -88,6 +89,20 @@ pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> & return cases.step; } +pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(ParseHContext); + *cases = ParseHContext { + .b = b, + .step = b.step("test-parseh", "Run the C header file parsing tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + parseh.addCases(cases); + + return cases.step; +} + pub const CompareOutputContext = struct { b: &build.Builder, step: &build.Step, @@ -645,3 +660,188 @@ pub const BuildExamplesContext = struct { } } }; + +pub const ParseHContext = struct { + b: &build.Builder, + step: &build.Step, + test_index: usize, + test_filter: ?[]const u8, + + const TestCase = struct { + name: []const u8, + sources: List(SourceFile), + expected_lines: List([]const u8), + allow_warnings: bool, + + const SourceFile = struct { + filename: []const u8, + source: []const u8, + }; + + pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) { + %%self.sources.append(SourceFile { + .filename = filename, + .source = source, + }); + } + + pub fn addExpectedError(self: &TestCase, text: []const u8) { + %%self.expected_lines.append(text); + } + }; + + const ParseHCmpOutputStep = struct { + step: build.Step, + context: &ParseHContext, + name: []const u8, + test_index: usize, + case: &const TestCase, + + pub fn create(context: &ParseHContext, name: []const u8, case: &const TestCase) -> &ParseHCmpOutputStep { + const allocator = context.b.allocator; + const ptr = %%allocator.create(ParseHCmpOutputStep); + *ptr = ParseHCmpOutputStep { + .step = build.Step.init("ParseHCmpOutput", allocator, make), + .context = context, + .name = name, + .test_index = context.test_index, + .case = case, + }; + context.test_index += 1; + return ptr; + } + + fn make(step: &build.Step) -> %void { + const self = @fieldParentPtr(ParseHCmpOutputStep, "step", step); + const b = self.context.b; + + const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename); + + var zig_args = List([]const u8).init(b.allocator); + %%zig_args.append("parseh"); + %%zig_args.append(b.pathFromRoot(root_src)); + + %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + + if (b.verbose) { + printInvocation(b.zig_exe, zig_args.toSliceConst()); + } + + var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map, + StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err| + { + debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err)); + }; + + const term = child.wait() %% |err| { + debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err)); + }; + switch (term) { + Term.Clean => |code| { + if (code != 0) { + %%io.stderr.printf("Compilation failed with exit code {}\n", code); + return error.TestFailed; + } + }, + Term.Signal => |code| { + %%io.stderr.printf("Compilation failed with signal {}\n", code); + return error.TestFailed; + }, + else => { + %%io.stderr.printf("Compilation terminated unexpectedly\n"); + return error.TestFailed; + }, + }; + + var stdout_buf = %%Buffer0.initEmpty(b.allocator); + var stderr_buf = %%Buffer0.initEmpty(b.allocator); + + %%(??child.stdout).readAll(&stdout_buf); + %%(??child.stderr).readAll(&stderr_buf); + + const stdout = stdout_buf.toSliceConst(); + const stderr = stderr_buf.toSliceConst(); + + if (stderr.len != 0 and !self.case.allow_warnings) { + %%io.stderr.printf( + \\====== parseh emitted warnings: ============ + \\{} + \\============================================ + \\ + , stderr); + return error.TestFailed; + } + + for (self.case.expected_lines.toSliceConst()) |expected_line| { + if (mem.indexOf(u8, stdout, expected_line) == null) { + %%io.stderr.printf( + \\ + \\========= Expected this output: ================ + \\{} + \\================================================ + \\{} + \\ + , expected_line, stdout); + return error.TestFailed; + } + } + %%io.stderr.printf("OK\n"); + } + }; + + fn printInvocation(exe_path: []const u8, args: []const []const u8) { + %%io.stderr.printf("{}", exe_path); + for (args) |arg| { + %%io.stderr.printf(" {}", arg); + } + %%io.stderr.printf("\n"); + } + + pub fn create(self: &ParseHContext, allow_warnings: bool, name: []const u8, + source: []const u8, expected_lines: ...) -> &TestCase + { + const tc = %%self.b.allocator.create(TestCase); + *tc = TestCase { + .name = name, + .sources = List(TestCase.SourceFile).init(self.b.allocator), + .expected_lines = List([]const u8).init(self.b.allocator), + .allow_warnings = allow_warnings, + }; + tc.addSourceFile("source.h", source); + comptime var arg_i = 0; + inline while (arg_i < expected_lines.len; arg_i += 1) { + // TODO mem.dupe is because of issue #336 + tc.addExpectedError(%%mem.dupe(self.b.allocator, u8, expected_lines[arg_i])); + } + return tc; + } + + pub fn add(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create(false, name, source, expected_lines); + self.addCase(tc); + } + + pub fn addAllowWarnings(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create(true, name, source, expected_lines); + self.addCase(tc); + } + + pub fn addCase(self: &ParseHContext, case: &const TestCase) { + const b = self.b; + + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "parseh {}", case.name); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; + } + + const parseh_and_cmp = ParseHCmpOutputStep.create(self, annotated_case_name, case); + self.step.dependOn(&parseh_and_cmp.step); + + for (case.sources.toSliceConst()) |src_file| { + const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename); + const write_src = b.addWriteFile(expanded_src_path, src_file.source); + parseh_and_cmp.step.dependOn(&write_src.step); + } + } +};