From 763ce1c4852eaee22c648c6909190f73d2ca775b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 26 Nov 2015 01:29:52 -0700 Subject: [PATCH] add tests --- CMakeLists.txt | 20 +++++- README.md | 13 +++- src/os.cpp | 84 ++++++++++++++++++++++++++ src/os.hpp | 4 ++ test/add.h | 1 - test/add.zig | 3 - test/standalone.cpp | 144 ++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 262 insertions(+), 7 deletions(-) delete mode 100644 test/add.h delete mode 100644 test/add.zig create mode 100644 test/standalone.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a1dd49ac7..f1c80245f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,17 +33,30 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/os.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}/test/standalone.cpp" +) + + set(CONFIGURE_OUT_FILE "${CMAKE_BINARY_DIR}/config.h") configure_file ( "${CMAKE_SOURCE_DIR}/src/config.h.in" ${CONFIGURE_OUT_FILE} ) +include_directories( + ${CMAKE_SOURCE_DIR} + ${CMAKE_BINARY_DIR} + "${CMAKE_SOURCE_DIR}/src" +) + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Wno-unused-variable -Wno-unused-but-set-variable") set(EXE_CFLAGS "-std=c++11 -fno-exceptions -fno-rtti -D_GNU_SOURCE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -Werror -Wall -Werror=strict-prototypes -Werror=old-style-definition -Werror=missing-prototypes") - add_executable(zig ${ZIG_SOURCES}) set_target_properties(zig PROPERTIES COMPILE_FLAGS ${EXE_CFLAGS}) @@ -52,3 +65,8 @@ target_link_libraries(zig LINK_PUBLIC ) install(TARGETS zig DESTINATION bin) +add_executable(run_tests ${TEST_SOURCES}) +target_link_libraries(run_tests) +set_target_properties(run_tests PROPERTIES + COMPILE_FLAGS ${EXE_CFLAGS} +) diff --git a/README.md b/README.md index 273bc16f7..bc92675ce 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ readable, safe, optimal, and concise code to solve any computing problem. ## Roadmap - * Unit tests. * C style comments. * Simple .so library * Multiple files @@ -66,7 +65,7 @@ Root : many(TopLevelDecl) token(EOF) TopLevelDecl : FnDef | ExternBlock -ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnProtoDecl) token(RBrace) +ExternBlock : many(Directive) token(Extern) token(LBrace) many(FnDecl) token(RBrace) FnProto : token(Fn) token(Symbol) ParamDeclList option(token(Arrow) Type) @@ -96,3 +95,13 @@ FnCall : token(Symbol) token(LParen) list(Expression, token(Comma)) token(RParen Directive : token(NumberSign) token(Symbol) token(LParen) token(String) token(RParen) ``` + +### Building + +``` +mkdir build +cd build +cmake .. +make +./run_tests +``` diff --git a/src/os.cpp b/src/os.cpp index cb9f9623a..5ce9178ff 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -10,6 +10,11 @@ #include #include +#include +#include +#include +#include +#include void os_spawn_process(const char *exe, ZigList &args, bool detached) { pid_t pid = fork(); @@ -32,6 +37,24 @@ void os_spawn_process(const char *exe, ZigList &args, bool detache zig_panic("execvp failed: %s", strerror(errno)); } +static void read_all_fd(int fd, Buf *out_buf) { + static const ssize_t buf_size = 8192; + buf_resize(out_buf, buf_size); + ssize_t actual_buf_len = 0; + for (;;) { + ssize_t amt_read = read(fd, buf_ptr(out_buf), buf_len(out_buf)); + if (amt_read < 0) + zig_panic("fd read error"); + actual_buf_len += amt_read; + if (amt_read == 0) { + buf_resize(out_buf, actual_buf_len); + return; + } + + buf_resize(out_buf, actual_buf_len + buf_size); + } +} + void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename) { int last_index = buf_len(full_path) - 1; if (last_index >= 0 && buf_ptr(full_path)[last_index] == '/') { @@ -49,3 +72,64 @@ void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename) { buf_init_from_buf(out_basename, full_path); } +void os_exec_process(const char *exe, ZigList &args, + int *return_code, Buf *out_stderr, Buf *out_stdout) +{ + int stdin_pipe[2]; + int stdout_pipe[2]; + int stderr_pipe[2]; + + int err; + if ((err = pipe(stdin_pipe))) + zig_panic("pipe failed"); + if ((err = pipe(stdout_pipe))) + zig_panic("pipe failed"); + if ((err = pipe(stderr_pipe))) + zig_panic("pipe failed"); + + pid_t pid = fork(); + if (pid == -1) + zig_panic("fork failed"); + if (pid == 0) { + // child + if (dup2(stdin_pipe[0], STDIN_FILENO) == -1) + zig_panic("dup2 failed"); + + if (dup2(stdout_pipe[1], STDOUT_FILENO) == -1) + zig_panic("dup2 failed"); + + if (dup2(stderr_pipe[1], STDERR_FILENO) == -1) + zig_panic("dup2 failed"); + + const char **argv = allocate(args.length + 2); + argv[0] = exe; + argv[args.length + 1] = nullptr; + for (int i = 0; i < args.length; i += 1) { + argv[i + 1] = args.at(i); + } + execvp(exe, const_cast(argv)); + zig_panic("execvp failed: %s", strerror(errno)); + } else { + // parent + close(stdin_pipe[0]); + close(stdout_pipe[1]); + close(stderr_pipe[1]); + + waitpid(pid, return_code, 0); + + read_all_fd(stdout_pipe[0], out_stdout); + read_all_fd(stderr_pipe[0], out_stderr); + + } +} + +void os_write_file(Buf *full_path, Buf *contents) { + int fd; + if ((fd = open(buf_ptr(full_path), O_CREAT|O_CLOEXEC|O_WRONLY|O_TRUNC, S_IRWXU)) == -1) + zig_panic("open failed"); + ssize_t amt_written = write(fd, buf_ptr(contents), buf_len(contents)); + if (amt_written != buf_len(contents)) + zig_panic("write failed: %s", strerror(errno)); + if (close(fd) == -1) + zig_panic("close failed"); +} diff --git a/src/os.hpp b/src/os.hpp index 8fc307fee..a17c2b56f 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -12,8 +12,12 @@ #include "buffer.hpp" void os_spawn_process(const char *exe, ZigList &args, bool detached); +void os_exec_process(const char *exe, ZigList &args, + int *return_code, Buf *out_stderr, Buf *out_stdout); void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename); +void os_write_file(Buf *full_path, Buf *contents); + #endif diff --git a/test/add.h b/test/add.h deleted file mode 100644 index 6295ab95c..000000000 --- a/test/add.h +++ /dev/null @@ -1 +0,0 @@ -int add(int a, int b); diff --git a/test/add.zig b/test/add.zig deleted file mode 100644 index bb0175bff..000000000 --- a/test/add.zig +++ /dev/null @@ -1,3 +0,0 @@ -export fn add(a: i32, b: i32) -> i32 { - return a + b; -} diff --git a/test/standalone.cpp b/test/standalone.cpp new file mode 100644 index 000000000..1aec55768 --- /dev/null +++ b/test/standalone.cpp @@ -0,0 +1,144 @@ +/* + * 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 + +struct TestSourceFile { + const char *relative_path; + const char *text; +}; + +struct TestCase { + const char *case_name; + const char *output; + const char *source; + ZigList compile_errors; + ZigList compiler_args; + ZigList program_args; +}; + +ZigList test_cases = {0}; +const char *tmp_source_path = ".tmp_source.zig"; +const char *tmp_exe_path = "./.tmp_exe"; + +static void add_simple_case(const char *case_name, const char *source, const char *output) { + TestCase *test_case = allocate(1); + test_case->case_name = case_name; + test_case->output = output; + test_case->source = source; + + 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); +} + +static void add_all_test_cases(void) { + add_simple_case("hello world with libc", R"SOURCE( + #link("c") + extern { + fn puts(s: *mut u8) -> i32; + fn exit(code: i32) -> unreachable; + } + + fn _start() -> unreachable { + puts("Hello, world!"); + exit(0); + } + )SOURCE", "Hello, world!\n"); + + add_simple_case("function call", R"SOURCE( + #link("c") + extern { + fn puts(s: *mut u8) -> i32; + fn exit(code: i32) -> unreachable; + } + + fn _start() -> unreachable { + this_is_a_function(); + } + + fn this_is_a_function() -> unreachable { + puts("OK"); + exit(0); + } + )SOURCE", "OK\n"); +} + +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; + os_exec_process("./zig", test_case->compiler_args, &return_code, &zig_stderr, &zig_stdout); + + if (return_code != 0) { + printf("\nCompile failed with return code %d:\n", return_code); + printf("zig"); + 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)); + exit(1); + } + + Buf program_stderr = BUF_INIT; + Buf program_stdout = BUF_INIT; + os_exec_process(tmp_exe_path, test_case->program_args, &return_code, &program_stderr, &program_stdout); + + if (return_code != 0) { + printf("\nProgram exited with return code %d:\n", return_code); + printf("zig"); + 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(&program_stderr)); + exit(1); + } + + if (!buf_eql_str(&program_stdout, test_case->output)) { + printf("\n"); + 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); + } +} + +static void run_all_tests(void) { + for (int i = 0; i < test_cases.length; i += 1) { + TestCase *test_case = test_cases.at(i); + printf("Test %d/%d %s...", i + 1, test_cases.length, test_case->case_name); + run_test(test_case); + printf("OK\n"); + } + printf("%d tests passed.\n", test_cases.length); +} + +static void cleanup(void) { + remove(tmp_source_path); + remove(tmp_exe_path); +} + +int main(int argc, char **argv) { + add_all_test_cases(); + run_all_tests(); + cleanup(); +}