Commit 37b9a2e6a4

Andrew Kelley <superjoe30@gmail.com>
2017-04-19 07:13:15
convert compare-output tests to use zig build system
1 parent 237dfdb
src/main.cpp
@@ -168,6 +168,7 @@ int main(int argc, char **argv) {
 
         ZigList<const char *> args = {0};
         args.append(zig_exe_path);
+        args.append(NULL); // placeholder
         for (int i = 2; i < argc; i += 1) {
             if (strcmp(argv[i], "--debug-build-verbose") == 0) {
                 verbose = true;
@@ -202,6 +203,8 @@ int main(int argc, char **argv) {
         Buf build_file_dirname = BUF_INIT;
         os_path_split(&build_file_abs, &build_file_dirname, &build_file_basename);
 
+        args.items[1] = buf_ptr(&build_file_dirname);
+
         bool build_file_exists;
         if ((err = os_file_exists(&build_file_abs, &build_file_exists))) {
             fprintf(stderr, "unable to open '%s': %s\n", buf_ptr(&build_file_abs), err_str(err));
std/os/index.zig
@@ -12,6 +12,11 @@ pub const max_noalloc_path_len = 1024;
 pub const ChildProcess = @import("child_process.zig").ChildProcess;
 pub const path = @import("path.zig");
 
+pub const line_sep = switch (@compileVar("os")) {
+    Os.windows => "\r\n",
+    else => "\n",
+};
+
 const debug = @import("../debug.zig");
 const assert = debug.assert;
 
@@ -319,7 +324,8 @@ fn posixExecveErrnoToErr(err: usize) -> error {
         errno.EINVAL, errno.ENOEXEC => error.InvalidExe,
         errno.EIO, errno.ELOOP => error.FileSystem,
         errno.EISDIR => error.IsDir,
-        errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+        errno.ENOENT => error.FileNotFound,
+        errno.ENOTDIR => error.NotDir,
         errno.ETXTBSY => error.FileBusy,
         else => error.Unexpected,
     };
@@ -413,7 +419,8 @@ pub fn symLink(allocator: &Allocator, existing_path: []const u8, new_path: []con
             errno.EIO => error.FileSystem,
             errno.ELOOP => error.SymLinkLoop,
             errno.ENAMETOOLONG => error.NameTooLong,
-            errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+            errno.ENOENT => error.FileNotFound,
+            errno.ENOTDIR => error.NotDir,
             errno.ENOMEM => error.SystemResources,
             errno.ENOSPC => error.NoSpaceLeft,
             errno.EROFS => error.ReadOnlyFileSystem,
@@ -471,7 +478,8 @@ pub fn deleteFile(allocator: &Allocator, file_path: []const u8) -> %void {
             errno.EISDIR => error.IsDir,
             errno.ELOOP => error.SymLinkLoop,
             errno.ENAMETOOLONG => error.NameTooLong,
-            errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+            errno.ENOENT => error.FileNotFound,
+            errno.ENOTDIR => error.NotDir,
             errno.ENOMEM => error.SystemResources,
             errno.EROFS => error.ReadOnlyFileSystem,
             else => error.Unexpected,
@@ -518,7 +526,8 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
             errno.ELOOP => error.SymLinkLoop,
             errno.EMLINK => error.LinkQuotaExceeded,
             errno.ENAMETOOLONG => error.NameTooLong,
-            errno.ENOENT, errno.ENOTDIR => error.FileNotFound,
+            errno.ENOENT => error.FileNotFound,
+            errno.ENOTDIR => error.NotDir,
             errno.ENOMEM => error.SystemResources,
             errno.ENOSPC => error.NoSpaceLeft,
             errno.EEXIST, errno.ENOTEMPTY => error.PathAlreadyExists,
@@ -528,3 +537,51 @@ pub fn rename(allocator: &Allocator, old_path: []const u8, new_path: []const u8)
         };
     }
 }
+
+pub fn makeDir(allocator: &Allocator, dir_path: []const u8) -> %void {
+    const path_buf = %return allocator.alloc(u8, dir_path.len + 1);
+    defer allocator.free(path_buf);
+
+    mem.copy(u8, path_buf, dir_path);
+    path_buf[dir_path.len] = 0;
+
+    const err = posix.getErrno(posix.mkdir(path_buf.ptr, 0o755));
+    if (err > 0) {
+        return switch (err) {
+            errno.EACCES, errno.EPERM => error.AccessDenied,
+            errno.EDQUOT => error.DiskQuota,
+            errno.EEXIST => error.PathAlreadyExists,
+            errno.EFAULT => unreachable,
+            errno.ELOOP => error.SymLinkLoop,
+            errno.EMLINK => error.LinkQuotaExceeded,
+            errno.ENAMETOOLONG => error.NameTooLong,
+            errno.ENOENT => error.FileNotFound,
+            errno.ENOMEM => error.SystemResources,
+            errno.ENOSPC => error.NoSpaceLeft,
+            errno.ENOTDIR => error.NotDir,
+            errno.EROFS => error.ReadOnlyFileSystem,
+            else => error.Unexpected,
+        };
+    }
+}
+
+/// Calls makeDir recursively to make an entire path. Returns success if the path
+/// already exists and is a directory.
+pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void {
+    const child_dir = %return path.dirname(allocator, full_path);
+    defer allocator.free(child_dir);
+
+    if (mem.eql(u8, child_dir, full_path))
+        return;
+
+    makePath(allocator, child_dir) %% |err| {
+        if (err != error.PathAlreadyExists)
+            return err;
+    };
+
+    makeDir(allocator, full_path) %% |err| {
+        if (err != error.PathAlreadyExists)
+            return err;
+        // TODO stat the file and return an error if it's not a directory
+    };
+}
std/os/linux.zig
@@ -273,6 +273,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize {
     arch.syscall2(arch.SYS_getcwd, usize(buf), size)
 }
 
+pub fn mkdir(path: &const u8, mode: usize) -> usize {
+    arch.syscall2(arch.SYS_mkdir, usize(path), mode)
+}
+
 pub fn mmap(address: ?&u8, length: usize, prot: usize, flags: usize, fd: i32, offset: usize)
     -> usize
 {
std/os/path.zig
@@ -4,22 +4,61 @@ const mem = @import("../mem.zig");
 const Allocator = mem.Allocator;
 
 /// Allocates memory for the result, which must be freed by the caller.
-pub fn join(allocator: &Allocator, dirname: []const u8, basename: []const u8) -> %[]const u8 {
-    const buf = %return allocator.alloc(u8, dirname.len + basename.len + 1);
+pub fn join(allocator: &Allocator, paths: ...) -> %[]u8 {
+    assert(paths.len >= 2);
+    var total_paths_len: usize = paths.len; // 1 slash per path
+    {
+        comptime var path_i = 0;
+        inline while (path_i < paths.len; path_i += 1) {
+            const arg = ([]const u8)(paths[path_i]);
+            total_paths_len += arg.len;
+        }
+    }
+
+    const buf = %return allocator.alloc(u8, total_paths_len);
     %defer allocator.free(buf);
 
-    mem.copy(u8, buf, dirname);
-    if (dirname[dirname.len - 1] == '/') {
-        mem.copy(u8, buf[dirname.len...], basename);
-        return buf[0...buf.len - 1];
-    } else {
-        buf[dirname.len] = '/';
-        mem.copy(u8, buf[dirname.len + 1 ...], basename);
-        return buf;
+    var buf_index: usize = 0;
+    comptime var path_i = 0;
+    inline while (true) {
+        const arg = ([]const u8)(paths[path_i]);
+        path_i += 1;
+        mem.copy(u8, buf[buf_index...], arg);
+        buf_index += arg.len;
+        if (path_i >= paths.len) break;
+        if (arg[arg.len - 1] != '/') {
+            buf[buf_index] = '/';
+            buf_index += 1;
+        }
     }
+
+    return buf[0...buf_index];
 }
 
 test "os.path.join" {
     assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b", "c"), "/a/b/c"));
     assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/b/", "c"), "/a/b/c"));
+
+    assert(mem.eql(u8, %%join(&debug.global_allocator, "/", "a", "b/", "c"), "/a/b/c"));
+    assert(mem.eql(u8, %%join(&debug.global_allocator, "/a/", "b/", "c"), "/a/b/c"));
+}
+
+pub fn dirname(allocator: &Allocator, path: []const u8) -> %[]u8 {
+    if (path.len != 0) {
+        var last_index: usize = path.len - 1;
+        if (path[last_index] == '/')
+            last_index -= 1;
+
+        var i: usize = last_index;
+        while (true) {
+            const c = path[i];
+            if (c == '/')
+                return mem.dupe(allocator, u8, path[0...i]);
+            if (i == 0)
+                break;
+            i -= 1;
+        }
+    }
+
+    return mem.dupe(allocator, u8, ".");
 }
std/special/build_file_template.zig
@@ -3,6 +3,8 @@ const Builder = @import("std").build.Builder;
 pub fn build(b: &Builder) {
     const release = b.option(bool, "release", "optimizations on and safety off") ?? false;
 
-    var exe = b.addExe("src/main.zig", "YOUR_NAME_HERE");
+    const exe = b.addExecutable("YOUR_NAME_HERE", "src/main.zig");
     exe.setRelease(release);
+
+    b.default_step.dependOn(&exe.step);
 }
std/special/build_runner.zig
@@ -10,74 +10,85 @@ const List = std.list.List;
 error InvalidArgs;
 
 pub fn main() -> %void {
+    var arg_i: usize = 1;
+
+    const zig_exe = {
+        if (arg_i >= os.args.count()) {
+            %%io.stderr.printf("Expected first argument to be path to zig compiler\n");
+            return error.InvalidArgs;
+        }
+        const result = os.args.at(arg_i);
+        arg_i += 1;
+        result
+    };
+
+    const build_root = {
+        if (arg_i >= os.args.count()) {
+            %%io.stderr.printf("Expected second argument to be build root directory path\n");
+            return error.InvalidArgs;
+        }
+        const result = os.args.at(arg_i);
+        arg_i += 1;
+        result
+    };
+
     // TODO use a more general purpose allocator here
     var inc_allocator = %%mem.IncrementingAllocator.init(10 * 1024 * 1024);
     defer inc_allocator.deinit();
 
     const allocator = &inc_allocator.allocator;
 
-    var builder = Builder.init(allocator);
+    var builder = Builder.init(allocator, zig_exe, build_root);
     defer builder.deinit();
 
-    var maybe_zig_exe: ?[]const u8 = null;
     var targets = List([]const u8).init(allocator);
 
     var prefix: ?[]const u8 = null;
 
-    var arg_i: usize = 1;
     while (arg_i < os.args.count(); arg_i += 1) {
         const arg = os.args.at(arg_i);
         if (mem.startsWith(u8, arg, "-D")) {
             const option_contents = arg[2...];
             if (option_contents.len == 0) {
                 %%io.stderr.printf("Expected option name after '-D'\n\n");
-                return usage(&builder, maybe_zig_exe, false, &io.stderr);
+                return usage(&builder, false, &io.stderr);
             }
             if (const name_end ?= mem.indexOfScalar(u8, option_contents, '=')) {
                 const option_name = option_contents[0...name_end];
-                const option_value = option_contents[name_end...];
+                const option_value = option_contents[name_end + 1...];
                 if (builder.addUserInputOption(option_name, option_value))
-                    return usage(&builder, maybe_zig_exe, false, &io.stderr);
+                    return usage(&builder, false, &io.stderr);
             } else {
                 if (builder.addUserInputFlag(option_contents))
-                    return usage(&builder, maybe_zig_exe, false, &io.stderr);
+                    return usage(&builder, false, &io.stderr);
             }
         } else if (mem.startsWith(u8, arg, "-")) {
             if (mem.eql(u8, arg, "--verbose")) {
                 builder.verbose = true;
             } else if (mem.eql(u8, arg, "--help")) {
-                 return usage(&builder, maybe_zig_exe, false, &io.stdout);
+                 return usage(&builder, false, &io.stdout);
             } else if (mem.eql(u8, arg, "--prefix") and arg_i + 1 < os.args.count()) {
                  arg_i += 1;
                  prefix = os.args.at(arg_i);
             } else {
                 %%io.stderr.printf("Unrecognized argument: {}\n\n", arg);
-                return usage(&builder, maybe_zig_exe, false, &io.stderr);
+                return usage(&builder, false, &io.stderr);
             }
-        } else if (maybe_zig_exe == null) {
-            maybe_zig_exe = arg;
         } else {
             %%targets.append(arg);
         }
     }
 
-    builder.zig_exe = maybe_zig_exe ?? return usage(&builder, null, false, &io.stderr);
     builder.setInstallPrefix(prefix);
-
     root.build(&builder);
 
     if (builder.validateUserInputDidItFail())
-        return usage(&builder, maybe_zig_exe, true, &io.stderr);
+        return usage(&builder, true, &io.stderr);
 
     %return builder.make(targets.toSliceConst());
 }
 
-fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool, out_stream: &io.OutStream) -> %void {
-    const zig_exe = maybe_zig_exe ?? {
-        %%out_stream.printf("Expected first argument to be path to zig compiler\n");
-        return error.InvalidArgs;
-    };
-
+fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) -> %void {
     // run the build script to collect the options
     if (!already_ran_build) {
         builder.setInstallPrefix(null);
@@ -90,7 +101,7 @@ fn usage(builder: &Builder, maybe_zig_exe: ?[]const u8, already_ran_build: bool,
         \\
         \\Steps:
         \\
-    , zig_exe);
+    , builder.zig_exe);
 
     const allocator = builder.allocator;
     for (builder.top_level_steps.toSliceConst()) |top_level_step| {
std/build.zig
@@ -38,6 +38,7 @@ pub const Builder = struct {
     lib_dir: []const u8,
     out_dir: []u8,
     installed_files: List([]const u8),
+    build_root: []const u8,
 
     const UserInputOptionsMap = HashMap([]const u8, UserInputOption, mem.hash_slice_u8, mem.eql_slice_u8);
     const AvailableOptionsMap = HashMap([]const u8, AvailableOption, mem.hash_slice_u8, mem.eql_slice_u8);
@@ -73,8 +74,10 @@ pub const Builder = struct {
         description: []const u8,
     };
 
-    pub fn init(allocator: &Allocator) -> Builder {
+    pub fn init(allocator: &Allocator, zig_exe: []const u8, build_root: []const u8) -> Builder {
         var self = Builder {
+            .zig_exe = zig_exe,
+            .build_root = build_root,
             .verbose = false,
             .invalid_user_input = false,
             .allocator = allocator,
@@ -85,7 +88,6 @@ pub const Builder = struct {
             .available_options_map = AvailableOptionsMap.init(allocator),
             .available_options_list = List(AvailableOption).init(allocator),
             .top_level_steps = List(&TopLevelStep).init(allocator),
-            .zig_exe = undefined,
             .default_step = undefined,
             .env_map = %%os.getEnvMap(allocator),
             .prefix = undefined,
@@ -123,6 +125,12 @@ pub const Builder = struct {
         return exe;
     }
 
+    pub fn addTest(self: &Builder, root_src: []const u8) -> &TestStep {
+        const test_step = %%self.allocator.create(TestStep);
+        *test_step = TestStep.init(self, root_src);
+        return test_step;
+    }
+
     pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary {
         const lib = %%self.allocator.create(CLibrary);
         *lib = CLibrary.initStatic(self, name);
@@ -149,6 +157,19 @@ pub const Builder = struct {
         return cmd;
     }
 
+    pub fn addWriteFile(self: &Builder, file_path: []const u8, data: []const u8) -> &WriteFileStep {
+        const write_file_step = %%self.allocator.create(WriteFileStep);
+        *write_file_step = WriteFileStep.init(self, file_path, data);
+        return write_file_step;
+    }
+
+    pub fn addLog(self: &Builder, comptime format: []const u8, args: ...) -> &LogStep {
+        const data = %%fmt.allocPrint(self.allocator, format, args);
+        const log_step = %%self.allocator.create(LogStep);
+        *log_step = LogStep.init(self, data);
+        return log_step;
+    }
+
     pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version {
         Version {
             .major = major,
@@ -197,9 +218,8 @@ pub const Builder = struct {
     }
 
     fn makeUninstall(uninstall_step: &Step) -> %void {
-        // TODO
-        // const self = @fieldParentPtr(Exe, "step", step);
-        const self = @ptrcast(&Builder, uninstall_step);
+        const uninstall_tls = @fieldParentPtr(TopLevelStep, "step", uninstall_step);
+        const self = @fieldParentPtr(Builder, "uninstall_tls", uninstall_tls);
 
         for (self.installed_files.toSliceConst()) |installed_file| {
             _ = os.deleteFile(self.allocator, installed_file);
@@ -278,7 +298,7 @@ pub const Builder = struct {
     }
 
     pub fn option(self: &Builder, comptime T: type, name: []const u8, description: []const u8) -> ?T {
-        const type_id = typeToEnum(T);
+        const type_id = comptime typeToEnum(T);
         const available_option = AvailableOption {
             .name = name,
             .type_id = type_id,
@@ -313,7 +333,19 @@ pub const Builder = struct {
             },
             TypeId.Int => debug.panic("TODO integer options to build script"),
             TypeId.Float => debug.panic("TODO float options to build script"),
-            TypeId.String => debug.panic("TODO string options to build script"),
+            TypeId.String => switch (entry.value.value) {
+                UserValue.Flag => {
+                    %%io.stderr.printf("Expected -D{} to be a string, but received a boolean.\n", name);
+                    self.markInvalidUserInput();
+                    return null;
+                },
+                UserValue.List => {
+                    %%io.stderr.printf("Expected -D{} to be a string, but received a list.\n", name);
+                    self.markInvalidUserInput();
+                    return null;
+                },
+                UserValue.Scalar => |s| return s,
+            },
             TypeId.List => debug.panic("TODO list options to build script"),
         }
     }
@@ -482,6 +514,10 @@ pub const Builder = struct {
             debug.panic("Unable to copy {} to {}: {}", source_path, dest_path, @errorName(err));
         };
     }
+
+    fn pathFromRoot(self: &Builder, rel_path: []const u8) -> []u8 {
+        return %%os.path.join(self.allocator, self.build_root, rel_path);
+    }
 };
 
 const Version = struct {
@@ -518,7 +554,7 @@ const LinkerScript = enum {
     Path: []const u8,
 };
 
-const Exe = struct {
+pub const Exe = struct {
     step: Step,
     builder: &Builder,
     root_src: []const u8,
@@ -528,6 +564,7 @@ const Exe = struct {
     link_libs: BufSet,
     verbose: bool,
     release: bool,
+    output_path: ?[]const u8,
 
     pub fn init(builder: &Builder, name: []const u8, root_src: []const u8) -> Exe {
         Exe {
@@ -540,6 +577,7 @@ const Exe = struct {
             .linker_script = LinkerScript.None,
             .link_libs = BufSet.init(builder.allocator),
             .step = Step.init(name, builder.allocator, make),
+            .output_path = null,
         }
     }
 
@@ -579,6 +617,10 @@ const Exe = struct {
         self.release = value;
     }
 
+    pub fn setOutputPath(self: &Exe, value: []const u8) {
+        self.output_path = value;
+    }
+
     fn make(step: &Step) -> %void {
         const exe = @fieldParentPtr(Exe, "step", step);
         const builder = exe.builder;
@@ -586,31 +628,36 @@ const Exe = struct {
         var zig_args = List([]const u8).init(builder.allocator);
         defer zig_args.deinit();
 
-        %return zig_args.append("build_exe");
-        %return zig_args.append(exe.root_src);
+        %%zig_args.append("build_exe");
+        %%zig_args.append(builder.pathFromRoot(exe.root_src));
 
         if (exe.verbose) {
-            %return zig_args.append("--verbose");
+            %%zig_args.append("--verbose");
         }
 
         if (exe.release) {
-            %return zig_args.append("--release");
+            %%zig_args.append("--release");
         }
 
-        %return zig_args.append("--name");
-        %return zig_args.append(exe.name);
+        if (const output_path ?= exe.output_path) {
+            %%zig_args.append("--output");
+            %%zig_args.append(builder.pathFromRoot(output_path));
+        }
+
+        %%zig_args.append("--name");
+        %%zig_args.append(exe.name);
 
         switch (exe.target) {
             Target.Native => {},
             Target.Cross => |cross_target| {
-                %return zig_args.append("--target-arch");
-                %return zig_args.append(@enumTagName(cross_target.arch));
+                %%zig_args.append("--target-arch");
+                %%zig_args.append(@enumTagName(cross_target.arch));
 
-                %return zig_args.append("--target-os");
-                %return zig_args.append(@enumTagName(cross_target.os));
+                %%zig_args.append("--target-os");
+                %%zig_args.append(@enumTagName(cross_target.os));
 
-                %return zig_args.append("--target-environ");
-                %return zig_args.append(@enumTagName(cross_target.environ));
+                %%zig_args.append("--target-environ");
+                %%zig_args.append(@enumTagName(cross_target.environ));
             },
         }
 
@@ -620,12 +667,12 @@ const Exe = struct {
                 const tmp_file_name = "linker.ld.tmp"; // TODO issue #298
                 io.writeFile(tmp_file_name, script, builder.allocator)
                     %% |err| debug.panic("unable to write linker script: {}\n", @errorName(err));
-                %return zig_args.append("--linker-script");
-                %return zig_args.append(tmp_file_name);
+                %%zig_args.append("--linker-script");
+                %%zig_args.append(tmp_file_name);
             },
             LinkerScript.Path => |path| {
-                %return zig_args.append("--linker-script");
-                %return zig_args.append(path);
+                %%zig_args.append("--linker-script");
+                %%zig_args.append(path);
             },
         }
 
@@ -633,31 +680,109 @@ const Exe = struct {
             var it = exe.link_libs.iterator();
             while (true) {
                 const entry = it.next() ?? break;
-                %return zig_args.append("--library");
-                %return zig_args.append(entry.key);
+                %%zig_args.append("--library");
+                %%zig_args.append(entry.key);
+            }
+        }
+
+        for (builder.include_paths.toSliceConst()) |include_path| {
+            %%zig_args.append("-isystem");
+            %%zig_args.append(include_path);
+        }
+
+        for (builder.rpaths.toSliceConst()) |rpath| {
+            %%zig_args.append("-rpath");
+            %%zig_args.append(rpath);
+        }
+
+        for (builder.lib_paths.toSliceConst()) |lib_path| {
+            %%zig_args.append("--library-path");
+            %%zig_args.append(lib_path);
+        }
+
+        builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
+    }
+};
+
+pub const TestStep = struct {
+    step: Step,
+    builder: &Builder,
+    root_src: []const u8,
+    release: bool,
+    verbose: bool,
+    link_libs: BufSet,
+
+    pub fn init(builder: &Builder, root_src: []const u8) -> TestStep {
+        const step_name = %%fmt.allocPrint(builder.allocator, "test {}", root_src);
+        TestStep {
+            .step = Step.init(step_name, builder.allocator, make),
+            .builder = builder,
+            .root_src = root_src,
+            .release = false,
+            .verbose = false,
+            .link_libs = BufSet.init(builder.allocator),
+        }
+    }
+
+    pub fn setVerbose(self: &TestStep, value: bool) {
+        self.verbose = value;
+    }
+
+    pub fn setRelease(self: &TestStep, value: bool) {
+        self.release = value;
+    }
+
+    pub fn linkLibrary(self: &TestStep, name: []const u8) {
+        %%self.link_libs.put(name);
+    }
+
+    fn make(step: &Step) -> %void {
+        const self = @fieldParentPtr(TestStep, "step", step);
+        const builder = self.builder;
+
+        var zig_args = List([]const u8).init(builder.allocator);
+        defer zig_args.deinit();
+
+        %%zig_args.append("test");
+        %%zig_args.append(builder.pathFromRoot(self.root_src));
+
+        if (self.verbose) {
+            %%zig_args.append("--verbose");
+        }
+
+        if (self.release) {
+            %%zig_args.append("--release");
+        }
+
+        {
+            var it = self.link_libs.iterator();
+            while (true) {
+                const entry = it.next() ?? break;
+                %%zig_args.append("--library");
+                %%zig_args.append(entry.key);
             }
         }
 
         for (builder.include_paths.toSliceConst()) |include_path| {
-            %return zig_args.append("-isystem");
-            %return zig_args.append(include_path);
+            %%zig_args.append("-isystem");
+            %%zig_args.append(include_path);
         }
 
         for (builder.rpaths.toSliceConst()) |rpath| {
-            %return zig_args.append("-rpath");
-            %return zig_args.append(rpath);
+            %%zig_args.append("-rpath");
+            %%zig_args.append(rpath);
         }
 
         for (builder.lib_paths.toSliceConst()) |lib_path| {
-            %return zig_args.append("--library-path");
-            %return zig_args.append(lib_path);
+            %%zig_args.append("--library-path");
+            %%zig_args.append(lib_path);
         }
 
         builder.spawnChild(builder.zig_exe, zig_args.toSliceConst());
     }
 };
 
-const CLibrary = struct {
+pub const CLibrary = struct {
     step: Step,
     name: []const u8,
     out_filename: []const u8,
@@ -829,7 +954,7 @@ const CLibrary = struct {
     }
 };
 
-const CExecutable = struct {
+pub const CExecutable = struct {
     step: Step,
     builder: &Builder,
     name: []const u8,
@@ -959,7 +1084,7 @@ const CExecutable = struct {
     }
 };
 
-const CommandStep = struct {
+pub const CommandStep = struct {
     step: Step,
     builder: &Builder,
     exe_path: []const u8,
@@ -988,7 +1113,7 @@ const CommandStep = struct {
     }
 };
 
-const InstallCLibraryStep = struct {
+pub const InstallCLibraryStep = struct {
     step: Step,
     builder: &Builder,
     lib: &CLibrary,
@@ -1023,7 +1148,7 @@ const InstallCLibraryStep = struct {
     }
 };
 
-const InstallFileStep = struct {
+pub const InstallFileStep = struct {
     step: Step,
     builder: &Builder,
     src_path: []const u8,
@@ -1047,7 +1172,59 @@ const InstallFileStep = struct {
     }
 };
 
-const Step = struct {
+pub const WriteFileStep = struct {
+    step: Step,
+    builder: &Builder,
+    file_path: []const u8,
+    data: []const u8,
+
+    pub fn init(builder: &Builder, file_path: []const u8, data: []const u8) -> WriteFileStep {
+        return WriteFileStep {
+            .builder = builder,
+            .step = Step.init(
+                %%fmt.allocPrint(builder.allocator, "writefile {}", file_path),
+                builder.allocator, make),
+            .file_path = file_path,
+            .data = data,
+        };
+    }
+
+    fn make(step: &Step) -> %void {
+        const self = @fieldParentPtr(WriteFileStep, "step", step);
+        const full_path = self.builder.pathFromRoot(self.file_path);
+        const full_path_dir = %%os.path.dirname(self.builder.allocator, full_path);
+        os.makePath(self.builder.allocator, full_path_dir) %% |err| {
+            debug.panic("unable to make path {}: {}\n", full_path_dir, @errorName(err));
+        };
+        io.writeFile(full_path, self.data, self.builder.allocator) %% |err| {
+            debug.panic("unable to write {}: {}\n", full_path, @errorName(err));
+        };
+    }
+};
+
+pub const LogStep = struct {
+    step: Step,
+    builder: &Builder,
+    data: []const u8,
+
+    pub fn init(builder: &Builder, data: []const u8) -> LogStep {
+        return LogStep {
+            .builder = builder,
+            .step = Step.init(
+                %%fmt.allocPrint(builder.allocator, "log {}", data),
+                builder.allocator, make),
+            .data = data,
+        };
+    }
+
+    fn make(step: &Step) -> %void {
+        const self = @fieldParentPtr(LogStep, "step", step);
+        %%io.stderr.write(self.data);
+        %%io.stderr.flush();
+    }
+};
+
+pub const Step = struct {
     name: []const u8,
     makeFn: fn(self: &Step) -> %void,
     dependencies: List(&Step),
std/mem.zig
@@ -134,6 +134,13 @@ pub fn eql(comptime T: type, a: []const T, b: []const T) -> bool {
     return true;
 }
 
+/// Copies ::m to newly allocated memory. Caller is responsible to free it.
+pub fn dupe(allocator: &Allocator, comptime T: type, m: []const T) -> %[]T {
+    const new_buf = %return allocator.alloc(T, m.len);
+    copy(T, new_buf, m);
+    return new_buf;
+}
+
 /// Linear search for the index of a scalar value inside a slice.
 pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
     for (slice) |item, i| {
@@ -144,6 +151,27 @@ pub fn indexOfScalar(comptime T: type, slice: []const T, value: T) -> ?usize {
     return null;
 }
 
+// TODO boyer-moore algorithm
+pub fn indexOf(comptime T: type, haystack: []const T, needle: []const T) -> ?usize {
+    if (needle.len > haystack.len)
+        return null;
+
+    var i: usize = 0;
+    const end = haystack.len - needle.len;
+    while (i <= end; i += 1) {
+        if (eql(T, haystack[i...i + needle.len], needle))
+            return i;
+    }
+    return null;
+}
+
+test "mem.indexOf" {
+    assert(??indexOf(u8, "one two three four", "four") == 14);
+    assert(indexOf(u8, "one two three four", "gour") == null);
+    assert(??indexOf(u8, "foo", "foo") == 0);
+    assert(indexOf(u8, "foo", "fool") == null);
+}
+
 /// Reads an integer from memory with size equal to bytes.len.
 /// T specifies the return type, which must be large enough to store
 /// the result.
test/run_tests.cpp
@@ -270,412 +270,6 @@ static TestCase *add_example_compile_libc(const char *root_source_file) {
     return add_example_compile_extra(root_source_file, true);
 }
 
-static void add_compiling_test_cases(void) {
-    add_simple_case_libc("hello world with libc", R"SOURCE(
-const c = @cImport(@cInclude("stdio.h"));
-export fn main(argc: c_int, argv: &&u8) -> c_int {
-    _ = c.puts(c"Hello, world!");
-    return 0;
-}
-    )SOURCE", "Hello, world!" NL);
-
-    {
-        TestCase *tc = add_simple_case("multiple files with private function", R"SOURCE(
-use @import("std").io;
-use @import("foo.zig");
-
-pub fn main() -> %void {
-    privateFunction();
-    %%stdout.printf("OK 2\n");
-}
-
-fn privateFunction() {
-    printText();
-}
-        )SOURCE", "OK 1\nOK 2\n");
-
-        add_source_file(tc, "foo.zig", R"SOURCE(
-use @import("std").io;
-
-// purposefully conflicting function with main.zig
-// but it's private so it should be OK
-fn privateFunction() {
-    %%stdout.printf("OK 1\n");
-}
-
-pub fn printText() {
-    privateFunction();
-}
-        )SOURCE");
-    }
-
-    {
-        TestCase *tc = add_simple_case("import segregation", R"SOURCE(
-use @import("foo.zig");
-use @import("bar.zig");
-
-pub fn main() -> %void {
-    foo_function();
-    bar_function();
-}
-        )SOURCE", "OK\nOK\n");
-
-        add_source_file(tc, "foo.zig", R"SOURCE(
-use @import("std").io;
-pub fn foo_function() {
-    %%stdout.printf("OK\n");
-}
-        )SOURCE");
-
-        add_source_file(tc, "bar.zig", R"SOURCE(
-use @import("other.zig");
-use @import("std").io;
-
-pub fn bar_function() {
-    if (foo_function()) {
-        %%stdout.printf("OK\n");
-    }
-}
-        )SOURCE");
-
-        add_source_file(tc, "other.zig", R"SOURCE(
-pub fn foo_function() -> bool {
-    // this one conflicts with the one from foo
-    return true;
-}
-        )SOURCE");
-    }
-
-    {
-        TestCase *tc = add_simple_case("two files use import each other", R"SOURCE(
-use @import("a.zig");
-
-pub fn main() -> %void {
-    ok();
-}
-        )SOURCE", "OK\n");
-
-        add_source_file(tc, "a.zig", R"SOURCE(
-use @import("b.zig");
-const io = @import("std").io;
-
-pub const a_text = "OK\n";
-
-pub fn ok() {
-    %%io.stdout.printf(b_text);
-}
-        )SOURCE");
-
-        add_source_file(tc, "b.zig", R"SOURCE(
-use @import("a.zig");
-
-pub const b_text = a_text;
-        )SOURCE");
-    }
-
-
-
-    add_simple_case("hello world without libc", R"SOURCE(
-const io = @import("std").io;
-
-pub fn main() -> %void {
-    %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
-}
-    )SOURCE", "Hello, world!\n0012 012 a\n");
-
-
-    add_simple_case_libc("number literals", R"SOURCE(
-const c = @cImport(@cInclude("stdio.h"));
-
-export fn main(argc: c_int, argv: &&u8) -> c_int {
-    _ = c.printf(c"\n");
-
-    _ = c.printf(c"0: %llu\n",
-             u64(0));
-    _ = c.printf(c"320402575052271: %llu\n",
-         u64(320402575052271));
-    _ = c.printf(c"0x01236789abcdef: %llu\n",
-         u64(0x01236789abcdef));
-    _ = c.printf(c"0xffffffffffffffff: %llu\n",
-         u64(0xffffffffffffffff));
-    _ = c.printf(c"0x000000ffffffffffffffff: %llu\n",
-         u64(0x000000ffffffffffffffff));
-    _ = c.printf(c"0o1777777777777777777777: %llu\n",
-         u64(0o1777777777777777777777));
-    _ = c.printf(c"0o0000001777777777777777777777: %llu\n",
-         u64(0o0000001777777777777777777777));
-    _ = c.printf(c"0b1111111111111111111111111111111111111111111111111111111111111111: %llu\n",
-         u64(0b1111111111111111111111111111111111111111111111111111111111111111));
-    _ = c.printf(c"0b0000001111111111111111111111111111111111111111111111111111111111111111: %llu\n",
-         u64(0b0000001111111111111111111111111111111111111111111111111111111111111111));
-
-    _ = c.printf(c"\n");
-
-    _ = c.printf(c"0.0: %a\n",
-         f64(0.0));
-    _ = c.printf(c"0e0: %a\n",
-         f64(0e0));
-    _ = c.printf(c"0.0e0: %a\n",
-         f64(0.0e0));
-    _ = c.printf(c"000000000000000000000000000000000000000000000000000000000.0e0: %a\n",
-         f64(000000000000000000000000000000000000000000000000000000000.0e0));
-    _ = c.printf(c"0.000000000000000000000000000000000000000000000000000000000e0: %a\n",
-         f64(0.000000000000000000000000000000000000000000000000000000000e0));
-    _ = c.printf(c"0.0e000000000000000000000000000000000000000000000000000000000: %a\n",
-         f64(0.0e000000000000000000000000000000000000000000000000000000000));
-    _ = c.printf(c"1.0: %a\n",
-         f64(1.0));
-    _ = c.printf(c"10.0: %a\n",
-         f64(10.0));
-    _ = c.printf(c"10.5: %a\n",
-         f64(10.5));
-    _ = c.printf(c"10.5e5: %a\n",
-         f64(10.5e5));
-    _ = c.printf(c"10.5e+5: %a\n",
-         f64(10.5e+5));
-    _ = c.printf(c"50.0e-2: %a\n",
-         f64(50.0e-2));
-    _ = c.printf(c"50e-2: %a\n",
-         f64(50e-2));
-
-    _ = c.printf(c"\n");
-
-    _ = c.printf(c"0x1.0: %a\n",
-         f64(0x1.0));
-    _ = c.printf(c"0x10.0: %a\n",
-         f64(0x10.0));
-    _ = c.printf(c"0x100.0: %a\n",
-         f64(0x100.0));
-    _ = c.printf(c"0x103.0: %a\n",
-         f64(0x103.0));
-    _ = c.printf(c"0x103.7: %a\n",
-         f64(0x103.7));
-    _ = c.printf(c"0x103.70: %a\n",
-         f64(0x103.70));
-    _ = c.printf(c"0x103.70p4: %a\n",
-         f64(0x103.70p4));
-    _ = c.printf(c"0x103.70p5: %a\n",
-         f64(0x103.70p5));
-    _ = c.printf(c"0x103.70p+5: %a\n",
-         f64(0x103.70p+5));
-    _ = c.printf(c"0x103.70p-5: %a\n",
-         f64(0x103.70p-5));
-
-    _ = c.printf(c"\n");
-
-    _ = c.printf(c"0b10100.00010e0: %a\n",
-         f64(0b10100.00010e0));
-    _ = c.printf(c"0o10700.00010e0: %a\n",
-         f64(0o10700.00010e0));
-
-    return 0;
-}
-    )SOURCE", R"OUTPUT(
-0: 0
-320402575052271: 320402575052271
-0x01236789abcdef: 320402575052271
-0xffffffffffffffff: 18446744073709551615
-0x000000ffffffffffffffff: 18446744073709551615
-0o1777777777777777777777: 18446744073709551615
-0o0000001777777777777777777777: 18446744073709551615
-0b1111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
-0b0000001111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
-
-0.0: 0x0p+0
-0e0: 0x0p+0
-0.0e0: 0x0p+0
-000000000000000000000000000000000000000000000000000000000.0e0: 0x0p+0
-0.000000000000000000000000000000000000000000000000000000000e0: 0x0p+0
-0.0e000000000000000000000000000000000000000000000000000000000: 0x0p+0
-1.0: 0x1p+0
-10.0: 0x1.4p+3
-10.5: 0x1.5p+3
-10.5e5: 0x1.0059p+20
-10.5e+5: 0x1.0059p+20
-50.0e-2: 0x1p-1
-50e-2: 0x1p-1
-
-0x1.0: 0x1p+0
-0x10.0: 0x1p+4
-0x100.0: 0x1p+8
-0x103.0: 0x1.03p+8
-0x103.7: 0x1.037p+8
-0x103.70: 0x1.037p+8
-0x103.70p4: 0x1.037p+12
-0x103.70p5: 0x1.037p+13
-0x103.70p+5: 0x1.037p+13
-0x103.70p-5: 0x1.037p+3
-
-0b10100.00010e0: 0x1.41p+4
-0o10700.00010e0: 0x1.1c0001p+12
-)OUTPUT");
-
-    add_simple_case("order-independent declarations", R"SOURCE(
-const io = @import("std").io;
-const z = io.stdin_fileno;
-const x : @typeOf(y) = 1234;
-const y : u16 = 5678;
-pub fn main() -> %void {
-    var x_local : i32 = print_ok(x);
-}
-fn print_ok(val: @typeOf(x)) -> @typeOf(foo) {
-    %%io.stdout.printf("OK\n");
-    return 0;
-}
-const foo : i32 = 0;
-    )SOURCE", "OK\n");
-
-    add_simple_case_libc("expose function pointer to C land", R"SOURCE(
-const c = @cImport(@cInclude("stdlib.h"));
-
-export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int {
-    const a_int = @ptrcast(&i32, a ?? unreachable);
-    const b_int = @ptrcast(&i32, b ?? unreachable);
-    if (*a_int < *b_int) {
-        -1
-    } else if (*a_int > *b_int) {
-        1
-    } else {
-        c_int(0)
-    }
-}
-
-export fn main() -> c_int {
-    var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 };
-
-    c.qsort(@ptrcast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn);
-
-    for (array) |item, i| {
-        if (item != i) {
-            c.abort();
-        }
-    }
-
-    return 0;
-}
-    )SOURCE", "");
-
-
-
-    add_simple_case_libc("casting between float and integer types", R"SOURCE(
-const c = @cImport(@cInclude("stdio.h"));
-export fn main(argc: c_int, argv: &&u8) -> c_int {
-    const small: f32 = 3.25;
-    const x: f64 = small;
-    const y = i32(x);
-    const z = f64(y);
-    _ = c.printf(c"%.2f\n%d\n%.2f\n%.2f\n", x, y, z, f64(-0.4));
-    return 0;
-}
-    )SOURCE", "3.25\n3\n3.00\n-0.40\n");
-
-
-    add_simple_case("same named methods in incomplete struct", R"SOURCE(
-const io = @import("std").io;
-
-const Foo = struct {
-    field1: Bar,
-
-    fn method(a: &const Foo) -> bool { true }
-};
-
-const Bar = struct {
-    field2: i32,
-
-    fn method(b: &const Bar) -> bool { true }
-};
-
-pub fn main() -> %void {
-    const bar = Bar {.field2 = 13,};
-    const foo = Foo {.field1 = bar,};
-    if (!foo.method()) {
-        %%io.stdout.printf("BAD\n");
-    }
-    if (!bar.method()) {
-        %%io.stdout.printf("BAD\n");
-    }
-    %%io.stdout.printf("OK\n");
-}
-    )SOURCE", "OK\n");
-
-
-    add_simple_case("defer with only fallthrough", R"SOURCE(
-const io = @import("std").io;
-pub fn main() -> %void {
-    %%io.stdout.printf("before\n");
-    defer %%io.stdout.printf("defer1\n");
-    defer %%io.stdout.printf("defer2\n");
-    defer %%io.stdout.printf("defer3\n");
-    %%io.stdout.printf("after\n");
-}
-    )SOURCE", "before\nafter\ndefer3\ndefer2\ndefer1\n");
-
-
-    add_simple_case("defer with return", R"SOURCE(
-const io = @import("std").io;
-const os = @import("std").os;
-pub fn main() -> %void {
-    %%io.stdout.printf("before\n");
-    defer %%io.stdout.printf("defer1\n");
-    defer %%io.stdout.printf("defer2\n");
-    if (os.args.count() == 1) return;
-    defer %%io.stdout.printf("defer3\n");
-    %%io.stdout.printf("after\n");
-}
-    )SOURCE", "before\ndefer2\ndefer1\n");
-
-
-    add_simple_case("%defer and it fails", R"SOURCE(
-const io = @import("std").io;
-pub fn main() -> %void {
-    do_test() %% return;
-}
-fn do_test() -> %void {
-    %%io.stdout.printf("before\n");
-    defer %%io.stdout.printf("defer1\n");
-    %defer %%io.stdout.printf("deferErr\n");
-    %return its_gonna_fail();
-    defer %%io.stdout.printf("defer3\n");
-    %%io.stdout.printf("after\n");
-}
-error IToldYouItWouldFail;
-fn its_gonna_fail() -> %void {
-    return error.IToldYouItWouldFail;
-}
-    )SOURCE", "before\ndeferErr\ndefer1\n");
-
-
-    add_simple_case("%defer and it passes", R"SOURCE(
-const io = @import("std").io;
-pub fn main() -> %void {
-    do_test() %% return;
-}
-fn do_test() -> %void {
-    %%io.stdout.printf("before\n");
-    defer %%io.stdout.printf("defer1\n");
-    %defer %%io.stdout.printf("deferErr\n");
-    %return its_gonna_pass();
-    defer %%io.stdout.printf("defer3\n");
-    %%io.stdout.printf("after\n");
-}
-fn its_gonna_pass() -> %void { }
-    )SOURCE", "before\nafter\ndefer3\ndefer1\n");
-
-
-    {
-        TestCase *tc = add_simple_case("@embedFile", R"SOURCE(
-const foo_txt = @embedFile("foo.txt");
-const io = @import("std").io;
-
-pub fn main() -> %void {
-    %%io.stdout.printf(foo_txt);
-}
-        )SOURCE", "1234\nabcd\n");
-
-        add_source_file(tc, "foo.txt", "1234\nabcd\n");
-    }
-}
-
 ////////////////////////////////////////////////////////////////////////////////////
 
 static void add_build_examples(void) {
@@ -3021,7 +2615,6 @@ int main(int argc, char **argv) {
             }
         }
     }
-    add_compiling_test_cases();
     add_build_examples();
     add_debug_safety_test_cases();
     add_compile_failure_test_cases();
test/run_tests.zig
@@ -0,0 +1,5 @@
+const io = @import("std").io;
+
+pub fn main() -> %void {
+    %%io.stderr.printf("TODO run tests\n");
+}
test/tests.zig
@@ -0,0 +1,587 @@
+const std = @import("std");
+const debug = std.debug;
+const build = std.build;
+const os = std.os;
+const StdIo = os.ChildProcess.StdIo;
+const Term = os.ChildProcess.Term;
+const Buffer0 = std.cstr.Buffer0;
+const io = std.io;
+const mem = std.mem;
+const fmt = std.fmt;
+const List = std.list.List;
+
+error TestFailed;
+
+pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step {
+    const cases = %%b.allocator.create(CompareOutputContext);
+    *cases = CompareOutputContext {
+        .b = b,
+        .compare_output_tests = b.step("test-compare-output", "Run the compare output tests"),
+        .test_index = 0,
+        .test_filter = test_filter,
+    };
+
+    cases.addC("hello world with libc",
+        \\const c = @cImport(@cInclude("stdio.h"));
+        \\export fn main(argc: c_int, argv: &&u8) -> c_int {
+        \\    _ = c.puts(c"Hello, world!");
+        \\    return 0;
+        \\}
+    , "Hello, world!" ++ os.line_sep);
+
+    cases.addCase({
+        var tc = cases.create("multiple files with private function",
+            \\use @import("std").io;
+            \\use @import("foo.zig");
+            \\
+            \\pub fn main() -> %void {
+            \\    privateFunction();
+            \\    %%stdout.printf("OK 2\n");
+            \\}
+            \\
+            \\fn privateFunction() {
+            \\    printText();
+            \\}
+        , "OK 1\nOK 2\n");
+
+        tc.addSourceFile("foo.zig",
+            \\use @import("std").io;
+            \\
+            \\// purposefully conflicting function with main.zig
+            \\// but it's private so it should be OK
+            \\fn privateFunction() {
+            \\    %%stdout.printf("OK 1\n");
+            \\}
+            \\
+            \\pub fn printText() {
+            \\    privateFunction();
+            \\}
+        );
+
+        tc
+    });
+
+    cases.addCase({
+        var tc = cases.create("import segregation",
+            \\use @import("foo.zig");
+            \\use @import("bar.zig");
+            \\
+            \\pub fn main() -> %void {
+            \\    foo_function();
+            \\    bar_function();
+            \\}
+        , "OK\nOK\n");
+
+        tc.addSourceFile("foo.zig",
+            \\use @import("std").io;
+            \\pub fn foo_function() {
+            \\    %%stdout.printf("OK\n");
+            \\}
+        );
+
+        tc.addSourceFile("bar.zig",
+            \\use @import("other.zig");
+            \\use @import("std").io;
+            \\
+            \\pub fn bar_function() {
+            \\    if (foo_function()) {
+            \\        %%stdout.printf("OK\n");
+            \\    }
+            \\}
+        );
+
+        tc.addSourceFile("other.zig",
+            \\pub fn foo_function() -> bool {
+            \\    // this one conflicts with the one from foo
+            \\    return true;
+            \\}
+        );
+
+        tc
+    });
+
+    cases.addCase({
+        var tc = cases.create("two files use import each other",
+            \\use @import("a.zig");
+            \\
+            \\pub fn main() -> %void {
+            \\    ok();
+            \\}
+        , "OK\n");
+
+        tc.addSourceFile("a.zig",
+            \\use @import("b.zig");
+            \\const io = @import("std").io;
+            \\
+            \\pub const a_text = "OK\n";
+            \\
+            \\pub fn ok() {
+            \\    %%io.stdout.printf(b_text);
+            \\}
+        );
+
+        tc.addSourceFile("b.zig",
+            \\use @import("a.zig");
+            \\
+            \\pub const b_text = a_text;
+        );
+
+        tc
+    });
+
+    cases.add("hello world without libc",
+        \\const io = @import("std").io;
+        \\
+        \\pub fn main() -> %void {
+        \\    %%io.stdout.printf("Hello, world!\n{d4} {x3} {c}\n", u32(12), u16(0x12), u8('a'));
+        \\}
+    , "Hello, world!\n0012 012 a\n");
+
+    cases.addC("number literals",
+        \\const c = @cImport(@cInclude("stdio.h"));
+        \\
+        \\export fn main(argc: c_int, argv: &&u8) -> c_int {
+        \\    _ = c.printf(c"0: %llu\n",
+        \\             u64(0));
+        \\    _ = c.printf(c"320402575052271: %llu\n",
+        \\         u64(320402575052271));
+        \\    _ = c.printf(c"0x01236789abcdef: %llu\n",
+        \\         u64(0x01236789abcdef));
+        \\    _ = c.printf(c"0xffffffffffffffff: %llu\n",
+        \\         u64(0xffffffffffffffff));
+        \\    _ = c.printf(c"0x000000ffffffffffffffff: %llu\n",
+        \\         u64(0x000000ffffffffffffffff));
+        \\    _ = c.printf(c"0o1777777777777777777777: %llu\n",
+        \\         u64(0o1777777777777777777777));
+        \\    _ = c.printf(c"0o0000001777777777777777777777: %llu\n",
+        \\         u64(0o0000001777777777777777777777));
+        \\    _ = c.printf(c"0b1111111111111111111111111111111111111111111111111111111111111111: %llu\n",
+        \\         u64(0b1111111111111111111111111111111111111111111111111111111111111111));
+        \\    _ = c.printf(c"0b0000001111111111111111111111111111111111111111111111111111111111111111: %llu\n",
+        \\         u64(0b0000001111111111111111111111111111111111111111111111111111111111111111));
+        \\
+        \\    _ = c.printf(c"\n");
+        \\
+        \\    _ = c.printf(c"0.0: %a\n",
+        \\         f64(0.0));
+        \\    _ = c.printf(c"0e0: %a\n",
+        \\         f64(0e0));
+        \\    _ = c.printf(c"0.0e0: %a\n",
+        \\         f64(0.0e0));
+        \\    _ = c.printf(c"000000000000000000000000000000000000000000000000000000000.0e0: %a\n",
+        \\         f64(000000000000000000000000000000000000000000000000000000000.0e0));
+        \\    _ = c.printf(c"0.000000000000000000000000000000000000000000000000000000000e0: %a\n",
+        \\         f64(0.000000000000000000000000000000000000000000000000000000000e0));
+        \\    _ = c.printf(c"0.0e000000000000000000000000000000000000000000000000000000000: %a\n",
+        \\         f64(0.0e000000000000000000000000000000000000000000000000000000000));
+        \\    _ = c.printf(c"1.0: %a\n",
+        \\         f64(1.0));
+        \\    _ = c.printf(c"10.0: %a\n",
+        \\         f64(10.0));
+        \\    _ = c.printf(c"10.5: %a\n",
+        \\         f64(10.5));
+        \\    _ = c.printf(c"10.5e5: %a\n",
+        \\         f64(10.5e5));
+        \\    _ = c.printf(c"10.5e+5: %a\n",
+        \\         f64(10.5e+5));
+        \\    _ = c.printf(c"50.0e-2: %a\n",
+        \\         f64(50.0e-2));
+        \\    _ = c.printf(c"50e-2: %a\n",
+        \\         f64(50e-2));
+        \\
+        \\    _ = c.printf(c"\n");
+        \\
+        \\    _ = c.printf(c"0x1.0: %a\n",
+        \\         f64(0x1.0));
+        \\    _ = c.printf(c"0x10.0: %a\n",
+        \\         f64(0x10.0));
+        \\    _ = c.printf(c"0x100.0: %a\n",
+        \\         f64(0x100.0));
+        \\    _ = c.printf(c"0x103.0: %a\n",
+        \\         f64(0x103.0));
+        \\    _ = c.printf(c"0x103.7: %a\n",
+        \\         f64(0x103.7));
+        \\    _ = c.printf(c"0x103.70: %a\n",
+        \\         f64(0x103.70));
+        \\    _ = c.printf(c"0x103.70p4: %a\n",
+        \\         f64(0x103.70p4));
+        \\    _ = c.printf(c"0x103.70p5: %a\n",
+        \\         f64(0x103.70p5));
+        \\    _ = c.printf(c"0x103.70p+5: %a\n",
+        \\         f64(0x103.70p+5));
+        \\    _ = c.printf(c"0x103.70p-5: %a\n",
+        \\         f64(0x103.70p-5));
+        \\
+        \\    _ = c.printf(c"\n");
+        \\
+        \\    _ = c.printf(c"0b10100.00010e0: %a\n",
+        \\         f64(0b10100.00010e0));
+        \\    _ = c.printf(c"0o10700.00010e0: %a\n",
+        \\         f64(0o10700.00010e0));
+        \\
+        \\    return 0;
+        \\}
+    ,
+        \\0: 0
+        \\320402575052271: 320402575052271
+        \\0x01236789abcdef: 320402575052271
+        \\0xffffffffffffffff: 18446744073709551615
+        \\0x000000ffffffffffffffff: 18446744073709551615
+        \\0o1777777777777777777777: 18446744073709551615
+        \\0o0000001777777777777777777777: 18446744073709551615
+        \\0b1111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
+        \\0b0000001111111111111111111111111111111111111111111111111111111111111111: 18446744073709551615
+        \\
+        \\0.0: 0x0p+0
+        \\0e0: 0x0p+0
+        \\0.0e0: 0x0p+0
+        \\000000000000000000000000000000000000000000000000000000000.0e0: 0x0p+0
+        \\0.000000000000000000000000000000000000000000000000000000000e0: 0x0p+0
+        \\0.0e000000000000000000000000000000000000000000000000000000000: 0x0p+0
+        \\1.0: 0x1p+0
+        \\10.0: 0x1.4p+3
+        \\10.5: 0x1.5p+3
+        \\10.5e5: 0x1.0059p+20
+        \\10.5e+5: 0x1.0059p+20
+        \\50.0e-2: 0x1p-1
+        \\50e-2: 0x1p-1
+        \\
+        \\0x1.0: 0x1p+0
+        \\0x10.0: 0x1p+4
+        \\0x100.0: 0x1p+8
+        \\0x103.0: 0x1.03p+8
+        \\0x103.7: 0x1.037p+8
+        \\0x103.70: 0x1.037p+8
+        \\0x103.70p4: 0x1.037p+12
+        \\0x103.70p5: 0x1.037p+13
+        \\0x103.70p+5: 0x1.037p+13
+        \\0x103.70p-5: 0x1.037p+3
+        \\
+        \\0b10100.00010e0: 0x1.41p+4
+        \\0o10700.00010e0: 0x1.1c0001p+12
+        \\
+    );
+
+    cases.add("order-independent declarations",
+        \\const io = @import("std").io;
+        \\const z = io.stdin_fileno;
+        \\const x : @typeOf(y) = 1234;
+        \\const y : u16 = 5678;
+        \\pub fn main() -> %void {
+        \\    var x_local : i32 = print_ok(x);
+        \\}
+        \\fn print_ok(val: @typeOf(x)) -> @typeOf(foo) {
+        \\    %%io.stdout.printf("OK\n");
+        \\    return 0;
+        \\}
+        \\const foo : i32 = 0;
+    , "OK\n");
+
+    cases.addC("expose function pointer to C land",
+        \\const c = @cImport(@cInclude("stdlib.h"));
+        \\
+        \\export fn compare_fn(a: ?&const c_void, b: ?&const c_void) -> c_int {
+        \\    const a_int = @ptrcast(&i32, a ?? unreachable);
+        \\    const b_int = @ptrcast(&i32, b ?? unreachable);
+        \\    if (*a_int < *b_int) {
+        \\        -1
+        \\    } else if (*a_int > *b_int) {
+        \\        1
+        \\    } else {
+        \\        c_int(0)
+        \\    }
+        \\}
+        \\
+        \\export fn main() -> c_int {
+        \\    var array = []u32 { 1, 7, 3, 2, 0, 9, 4, 8, 6, 5 };
+        \\
+        \\    c.qsort(@ptrcast(&c_void, &array[0]), c_ulong(array.len), @sizeOf(i32), compare_fn);
+        \\
+        \\    for (array) |item, i| {
+        \\        if (item != i) {
+        \\            c.abort();
+        \\        }
+        \\    }
+        \\
+        \\    return 0;
+        \\}
+    , "");
+
+    cases.addC("casting between float and integer types",
+        \\const c = @cImport(@cInclude("stdio.h"));
+        \\export fn main(argc: c_int, argv: &&u8) -> c_int {
+        \\    const small: f32 = 3.25;
+        \\    const x: f64 = small;
+        \\    const y = i32(x);
+        \\    const z = f64(y);
+        \\    _ = c.printf(c"%.2f\n%d\n%.2f\n%.2f\n", x, y, z, f64(-0.4));
+        \\    return 0;
+        \\}
+    , "3.25\n3\n3.00\n-0.40\n");
+
+    cases.add("same named methods in incomplete struct",
+        \\const io = @import("std").io;
+        \\
+        \\const Foo = struct {
+        \\    field1: Bar,
+        \\
+        \\    fn method(a: &const Foo) -> bool { true }
+        \\};
+        \\
+        \\const Bar = struct {
+        \\    field2: i32,
+        \\
+        \\    fn method(b: &const Bar) -> bool { true }
+        \\};
+        \\
+        \\pub fn main() -> %void {
+        \\    const bar = Bar {.field2 = 13,};
+        \\    const foo = Foo {.field1 = bar,};
+        \\    if (!foo.method()) {
+        \\        %%io.stdout.printf("BAD\n");
+        \\    }
+        \\    if (!bar.method()) {
+        \\        %%io.stdout.printf("BAD\n");
+        \\    }
+        \\    %%io.stdout.printf("OK\n");
+        \\}
+    , "OK\n");
+
+    cases.add("defer with only fallthrough",
+        \\const io = @import("std").io;
+        \\pub fn main() -> %void {
+        \\    %%io.stdout.printf("before\n");
+        \\    defer %%io.stdout.printf("defer1\n");
+        \\    defer %%io.stdout.printf("defer2\n");
+        \\    defer %%io.stdout.printf("defer3\n");
+        \\    %%io.stdout.printf("after\n");
+        \\}
+    , "before\nafter\ndefer3\ndefer2\ndefer1\n");
+
+    cases.add("defer with return",
+        \\const io = @import("std").io;
+        \\const os = @import("std").os;
+        \\pub fn main() -> %void {
+        \\    %%io.stdout.printf("before\n");
+        \\    defer %%io.stdout.printf("defer1\n");
+        \\    defer %%io.stdout.printf("defer2\n");
+        \\    if (os.args.count() == 1) return;
+        \\    defer %%io.stdout.printf("defer3\n");
+        \\    %%io.stdout.printf("after\n");
+        \\}
+    , "before\ndefer2\ndefer1\n");
+
+    cases.add("%defer and it fails",
+        \\const io = @import("std").io;
+        \\pub fn main() -> %void {
+        \\    do_test() %% return;
+        \\}
+        \\fn do_test() -> %void {
+        \\    %%io.stdout.printf("before\n");
+        \\    defer %%io.stdout.printf("defer1\n");
+        \\    %defer %%io.stdout.printf("deferErr\n");
+        \\    %return its_gonna_fail();
+        \\    defer %%io.stdout.printf("defer3\n");
+        \\    %%io.stdout.printf("after\n");
+        \\}
+        \\error IToldYouItWouldFail;
+        \\fn its_gonna_fail() -> %void {
+        \\    return error.IToldYouItWouldFail;
+        \\}
+    , "before\ndeferErr\ndefer1\n");
+
+    cases.add("%defer and it passes",
+        \\const io = @import("std").io;
+        \\pub fn main() -> %void {
+        \\    do_test() %% return;
+        \\}
+        \\fn do_test() -> %void {
+        \\    %%io.stdout.printf("before\n");
+        \\    defer %%io.stdout.printf("defer1\n");
+        \\    %defer %%io.stdout.printf("deferErr\n");
+        \\    %return its_gonna_pass();
+        \\    defer %%io.stdout.printf("defer3\n");
+        \\    %%io.stdout.printf("after\n");
+        \\}
+        \\fn its_gonna_pass() -> %void { }
+    , "before\nafter\ndefer3\ndefer1\n");
+
+    cases.addCase({
+        var tc = cases.create("@embedFile",
+            \\const foo_txt = @embedFile("foo.txt");
+            \\const io = @import("std").io;
+            \\
+            \\pub fn main() -> %void {
+            \\    %%io.stdout.printf(foo_txt);
+            \\}
+        , "1234\nabcd\n");
+
+        tc.addSourceFile("foo.txt", "1234\nabcd\n");
+
+        tc
+    });
+
+    return cases.compare_output_tests;
+}
+
+const CompareOutputContext = struct {
+    b: &build.Builder,
+    compare_output_tests: &build.Step,
+    test_index: usize,
+    test_filter: ?[]const u8,
+
+    const TestCase = struct {
+        name: []const u8,
+        sources: List(SourceFile),
+        expected_output: []const u8,
+        link_libc: 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 create(self: &CompareOutputContext, name: []const u8, source: []const u8,
+        expected_output: []const u8) -> TestCase
+    {
+        var tc = TestCase {
+            .name = name,
+            .sources = List(TestCase.SourceFile).init(self.b.allocator),
+            .expected_output = expected_output,
+            .link_libc = false,
+        };
+        tc.addSourceFile("source.zig", source);
+        return tc;
+    }
+
+    pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
+        var tc = self.create(name, source, expected_output);
+        tc.link_libc = true;
+        self.addCase(tc);
+    }
+
+    pub fn add(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) {
+        const tc = self.create(name, source, expected_output);
+        self.addCase(tc);
+    }
+
+    pub fn addCase(self: &CompareOutputContext, case: &const TestCase) {
+        const b = self.b;
+
+        const root_src = %%os.path.join(b.allocator, "test_artifacts", case.sources.items[0].filename);
+        const exe_path = %%os.path.join(b.allocator, "test_artifacts", "test");
+
+        for ([]bool{false, true}) |release| {
+            const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} ({})",
+                case.name, if (release) "release" else "debug");
+            if (const filter ?= self.test_filter) {
+                if (mem.indexOf(u8, annotated_case_name, filter) == null)
+                    continue;
+            }
+
+            const exe = b.addExecutable("test", root_src);
+            exe.setOutputPath(exe_path);
+            exe.setRelease(release);
+            if (case.link_libc) {
+                exe.linkLibrary("c");
+            }
+
+            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);
+                exe.step.dependOn(&write_src.step);
+            }
+
+            const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name,
+                case.expected_output);
+            run_and_cmp_output.step.dependOn(&exe.step);
+
+            self.compare_output_tests.dependOn(&run_and_cmp_output.step);
+        }
+    }
+};
+
+const RunCompareOutputStep = struct {
+    step: build.Step,
+    context: &CompareOutputContext,
+    exe_path: []const u8,
+    name: []const u8,
+    expected_output: []const u8,
+    test_index: usize,
+
+    pub fn create(context: &CompareOutputContext, exe_path: []const u8,
+        name: []const u8, expected_output: []const u8) -> &RunCompareOutputStep
+    {
+        const allocator = context.b.allocator;
+        const ptr = %%allocator.create(RunCompareOutputStep);
+        *ptr = RunCompareOutputStep {
+            .context = context,
+            .exe_path = exe_path,
+            .name = name,
+            .expected_output = expected_output,
+            .test_index = context.test_index,
+            .step = build.Step.init("RunCompareOutput", allocator, make),
+        };
+        context.test_index += 1;
+        return ptr;
+    }
+
+    fn make(step: &build.Step) -> %void {
+        const self = @fieldParentPtr(RunCompareOutputStep, "step", step);
+        const b = self.context.b;
+
+        const full_exe_path = b.pathFromRoot(self.exe_path);
+
+        %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name);
+
+        var child = os.ChildProcess.spawn(full_exe_path, [][]u8{}, &b.env_map,
+            StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err|
+        {
+            debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
+        };
+
+        const term = child.wait() %% |err| {
+            debug.panic("Unable to spawn {}: {}\n", full_exe_path, @errorName(err));
+        };
+        switch (term) {
+            Term.Clean => |code| {
+                if (code != 0) {
+                    %%io.stderr.printf("Process {} exited with error code {}\n", full_exe_path, code);
+                    return error.TestFailed;
+                }
+            },
+            else => {
+                %%io.stderr.printf("Process {} terminated unexpectedly\n", full_exe_path);
+                return error.TestFailed;
+            },
+        };
+
+        var stdout = %%Buffer0.initEmpty(b.allocator);
+        var stderr = %%Buffer0.initEmpty(b.allocator);
+
+        %%(??child.stdout).readAll(&stdout);
+        %%(??child.stderr).readAll(&stderr);
+
+        if (!mem.eql(u8, self.expected_output, stdout.toSliceConst())) {
+            %%io.stderr.printf(
+                \\
+                \\========= Expected this output: =========
+                \\{}
+                \\================================================
+                \\{}
+                \\
+            , self.expected_output, stdout.toSliceConst());
+            return error.TestFailed;
+        }
+        %%io.stderr.printf("OK\n");
+    }
+};
+
build.zig
@@ -0,0 +1,36 @@
+const Builder = @import("std").build.Builder;
+const tests = @import("test/tests.zig");
+
+pub fn build(b: &Builder) {
+    const test_filter = b.option([]const u8, "test-filter", "Skip tests that do not match filter");
+    const test_step = b.step("test", "Run all the tests");
+
+    const run_tests_exe = b.addExecutable("run_tests", "test/run_tests.zig");
+
+    const run_tests_cmd = b.addCommand(b.out_dir, b.env_map, "./run_tests", [][]const u8{});
+    run_tests_cmd.step.dependOn(&run_tests_exe.step);
+
+    const self_hosted_tests_debug_nolibc = b.addTest("test/self_hosted.zig");
+
+    const self_hosted_tests_release_nolibc = b.addTest("test/self_hosted.zig");
+    self_hosted_tests_release_nolibc.setRelease(true);
+
+    const self_hosted_tests_debug_libc = b.addTest("test/self_hosted.zig");
+    self_hosted_tests_debug_libc.linkLibrary("c");
+
+    const self_hosted_tests_release_libc = b.addTest("test/self_hosted.zig");
+    self_hosted_tests_release_libc.setRelease(true);
+    self_hosted_tests_release_libc.linkLibrary("c");
+
+    const self_hosted_tests = b.step("test-self-hosted", "Run the self-hosted tests");
+    self_hosted_tests.dependOn(&self_hosted_tests_debug_nolibc.step);
+    self_hosted_tests.dependOn(&self_hosted_tests_release_nolibc.step);
+    self_hosted_tests.dependOn(&self_hosted_tests_debug_libc.step);
+    self_hosted_tests.dependOn(&self_hosted_tests_release_libc.step);
+
+    test_step.dependOn(self_hosted_tests);
+    //test_step.dependOn(&run_tests_cmd.step);
+
+    test_step.dependOn(tests.addCompareOutputTests(b, test_filter));
+    //test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
+}