Commit 695c8f756b

Andrew Kelley <andrew@ziglang.org>
2020-01-03 04:45:48
add test harness for "run translated C" tests
1 parent f83411b
src/main.cpp
@@ -43,7 +43,6 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
         "  libc [paths_file]            Display native libc paths file or validate one\n"
         "  run [source] [-- [args]]     create executable and run immediately\n"
         "  translate-c [source]         convert c code to zig code\n"
-        "  translate-c-2 [source]       experimental self-hosted translate-c\n"
         "  targets                      list available compilation targets\n"
         "  test [source]                create and run a test build\n"
         "  version                      print version number and exit\n"
test/src/run_translated_c.zig
@@ -0,0 +1,180 @@
+// This is the implementation of the test harness for running translated
+// C code. For the actual test cases, see test/run_translated_c.zig.
+const std = @import("std");
+const build = std.build;
+const ArrayList = std.ArrayList;
+const fmt = std.fmt;
+const mem = std.mem;
+const fs = std.fs;
+const warn = std.debug.warn;
+
+pub const RunTranslatedCContext = struct {
+    b: *build.Builder,
+    step: *build.Step,
+    test_index: usize,
+    test_filter: ?[]const u8,
+
+    const TestCase = struct {
+        name: []const u8,
+        sources: ArrayList(SourceFile),
+        expected_stdout: []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) void {
+            self.sources.append(SourceFile{
+                .filename = filename,
+                .source = source,
+            }) catch unreachable;
+        }
+    };
+
+    const DoEverythingStep = struct {
+        step: build.Step,
+        context: *RunTranslatedCContext,
+        name: []const u8,
+        case: *const TestCase,
+        test_index: usize,
+
+        pub fn create(
+            context: *RunTranslatedCContext,
+            name: []const u8,
+            case: *const TestCase,
+        ) *DoEverythingStep {
+            const allocator = context.b.allocator;
+            const ptr = allocator.create(DoEverythingStep) catch unreachable;
+            ptr.* = DoEverythingStep{
+                .context = context,
+                .name = name,
+                .case = case,
+                .test_index = context.test_index,
+                .step = build.Step.init("RunTranslatedC", allocator, make),
+            };
+            context.test_index += 1;
+            return ptr;
+        }
+
+        fn make(step: *build.Step) !void {
+            const self = @fieldParentPtr(DoEverythingStep, "step", step);
+            const b = self.context.b;
+
+            warn("Test {}/{} {}...", .{ self.test_index + 1, self.context.test_index, self.name });
+            // translate from c to zig
+            const translated_c_code = blk: {
+                var zig_args = ArrayList([]const u8).init(b.allocator);
+                defer zig_args.deinit();
+
+                const rel_c_filename = try fs.path.join(b.allocator, &[_][]const u8{
+                    b.cache_root,
+                    self.case.sources.toSliceConst()[0].filename,
+                });
+
+                try zig_args.append(b.zig_exe);
+                try zig_args.append("translate-c");
+                try zig_args.append("-lc");
+                try zig_args.append(b.pathFromRoot(rel_c_filename));
+
+                break :blk try b.exec(zig_args.toSliceConst());
+            };
+
+            // write stdout to a file
+
+            const translated_c_path = try fs.path.join(b.allocator,
+                &[_][]const u8{ b.cache_root, "translated_c.zig" });
+            try fs.cwd().writeFile(translated_c_path, translated_c_code);
+
+            // zig run the result
+            const run_stdout = blk: {
+                var zig_args = ArrayList([]const u8).init(b.allocator);
+                defer zig_args.deinit();
+
+                try zig_args.append(b.zig_exe);
+                try zig_args.append("-lc");
+                try zig_args.append("run");
+                try zig_args.append(translated_c_path);
+
+                break :blk try b.exec(zig_args.toSliceConst());
+            };
+            // compare stdout
+            if (!mem.eql(u8, self.case.expected_stdout, run_stdout)) {
+                warn(
+                    \\
+                    \\========= Expected this output: =========
+                    \\{}
+                    \\========= But found: ====================
+                    \\{}
+                    \\
+                , .{ self.case.expected_stdout, run_stdout });
+                return error.TestFailed;
+            }
+
+            warn("OK\n", .{});
+        }
+    };
+
+    pub fn create(
+        self: *RunTranslatedCContext,
+        allow_warnings: bool,
+        filename: []const u8,
+        name: []const u8,
+        source: []const u8,
+        expected_stdout: []const u8,
+    ) *TestCase {
+        const tc = self.b.allocator.create(TestCase) catch unreachable;
+        tc.* = TestCase{
+            .name = name,
+            .sources = ArrayList(TestCase.SourceFile).init(self.b.allocator),
+            .expected_stdout = expected_stdout,
+            .allow_warnings = allow_warnings,
+        };
+
+        tc.addSourceFile(filename, source);
+        return tc;
+    }
+
+    pub fn add(
+        self: *RunTranslatedCContext,
+        name: []const u8,
+        source: []const u8,
+        expected_stdout: []const u8,
+    ) void {
+        const tc = self.create(false, "source.c", name, source, expected_stdout);
+        self.addCase(tc);
+    }
+
+    pub fn addAllowWarnings(
+        self: *RunTranslatedCContext,
+        name: []const u8,
+        source: []const u8,
+        expected_stdout: []const u8,
+    ) void {
+        const tc = self.create(true, "source.c", name, source, expected_stdout);
+        self.addCase(tc);
+    }
+
+    pub fn addCase(self: *RunTranslatedCContext, case: *const TestCase) void {
+        const b = self.b;
+
+        const annotated_case_name = fmt.allocPrint(self.b.allocator, "run-translated-c {}", .{ case.name }) catch unreachable;
+        if (self.test_filter) |filter| {
+            if (mem.indexOf(u8, annotated_case_name, filter) == null) return;
+        }
+
+        const do_everything_step = DoEverythingStep.create(self, annotated_case_name, case);
+        self.step.dependOn(&do_everything_step.step);
+
+        for (case.sources.toSliceConst()) |src_file| {
+            const expanded_src_path = fs.path.join(
+                b.allocator,
+                &[_][]const u8{ b.cache_root, src_file.filename },
+            ) catch unreachable;
+            const write_src = b.addWriteFile(expanded_src_path, src_file.source);
+            do_everything_step.step.dependOn(&write_src.step);
+        }
+    }
+};
+
test/run_translated_c.zig
@@ -0,0 +1,24 @@
+const tests = @import("tests.zig");
+
+pub fn addCases(cases: *tests.RunTranslatedCContext) void {
+    cases.add("hello world",
+        \\#define _NO_CRT_STDIO_INLINE 1
+        \\#include <stdio.h>
+        \\int main(int argc, char **argv) {
+        \\    printf("hello, world!\n");
+        \\    return 0;
+        \\}
+    , "hello, world!\n");
+
+    cases.add("anon struct init",
+        \\#include <stdlib.h>
+        \\struct {int a; int b;} x = {1, 2};
+        \\int main(int argc, char **argv) {
+        \\    x.a += 2;
+        \\    x.b += 1;
+        \\    if (x.a != 3) abort();
+        \\    if (x.b != 3) abort();
+        \\    return 0;
+        \\}
+    , "");
+}
test/tests.zig
@@ -14,6 +14,7 @@ const builtin = @import("builtin");
 const Mode = builtin.Mode;
 const LibExeObjStep = build.LibExeObjStep;
 
+// Cases
 const compare_output = @import("compare_output.zig");
 const standalone = @import("standalone.zig");
 const stack_traces = @import("stack_traces.zig");
@@ -21,8 +22,12 @@ const compile_errors = @import("compile_errors.zig");
 const assemble_and_link = @import("assemble_and_link.zig");
 const runtime_safety = @import("runtime_safety.zig");
 const translate_c = @import("translate_c.zig");
+const run_translated_c = @import("run_translated_c.zig");
 const gen_h = @import("gen_h.zig");
 
+// Implementations
+pub const RunTranslatedCContext = @import("src/run_translated_c.zig").RunTranslatedCContext;
+
 const TestTarget = struct {
     target: Target = .Native,
     mode: builtin.Mode = .Debug,
@@ -383,6 +388,20 @@ pub fn addTranslateCTests(b: *build.Builder, test_filter: ?[]const u8) *build.St
     return cases.step;
 }
 
+pub fn addRunTranslatedCTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step {
+    const cases = b.allocator.create(RunTranslatedCContext) catch unreachable;
+    cases.* = .{
+        .b = b,
+        .step = b.step("test-run-translated-c", "Run the Run-Translated-C tests"),
+        .test_index = 0,
+        .test_filter = test_filter,
+    };
+
+    run_translated_c.addCases(cases);
+
+    return cases.step;
+}
+
 pub fn addGenHTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step {
     const cases = b.allocator.create(GenHContext) catch unreachable;
     cases.* = GenHContext{
build.zig
@@ -137,6 +137,7 @@ pub fn build(b: *Builder) !void {
     test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes));
     test_step.dependOn(tests.addRuntimeSafetyTests(b, test_filter, modes));
     test_step.dependOn(tests.addTranslateCTests(b, test_filter));
+    test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter));
     test_step.dependOn(tests.addGenHTests(b, test_filter));
     test_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes));
     test_step.dependOn(docs_step);