diff --git a/build.zig b/build.zig index b0338800b5..5b82a4f9c5 100644 --- a/build.zig +++ b/build.zig @@ -38,4 +38,5 @@ pub fn build(b: &Builder) { test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); + test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); } diff --git a/src/link.cpp b/src/link.cpp index edf5243b40..5c73c2134e 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -770,6 +770,14 @@ void codegen_link(CodeGen *g, const char *out_file) { if (g->want_h_file) { codegen_generate_h_file(g); } + if (override_out_file) { + assert(g->link_objects.length == 1); + Buf *o_file_path = g->link_objects.at(0); + int err; + if ((err = os_rename(o_file_path, &lj.out_file))) { + zig_panic("unable to rename object file into final output: %s", err_str(err)); + } + } if (g->verbose) { fprintf(stderr, "OK\n"); } diff --git a/src/os.cpp b/src/os.cpp index de0df92714..e8ee2a155b 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -683,3 +683,10 @@ int os_delete_file(Buf *path) { void os_init(void) { srand((unsigned)time(NULL)); } + +int os_rename(Buf *src_path, Buf *dest_path) { + if (rename(buf_ptr(src_path), buf_ptr(dest_path)) == -1) { + return ErrorFileSystem; + } + return 0; +} diff --git a/src/os.hpp b/src/os.hpp index bec2766893..1d29362d59 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -55,6 +55,8 @@ int os_delete_file(Buf *path); int os_file_exists(Buf *full_path, bool *result); +int os_rename(Buf *src_path, Buf *dest_path); + #if defined(__APPLE__) #define ZIG_OS_DARWIN #elif defined(_WIN32) diff --git a/std/build.zig b/std/build.zig index 11c0383291..c58a72eec3 100644 --- a/std/build.zig +++ b/std/build.zig @@ -17,6 +17,7 @@ error UncleanExit; error InvalidStepName; error DependencyLoopDetected; error NoCompilerFound; +error NeedAnObject; pub const Builder = struct { uninstall_tls: TopLevelStep, @@ -131,6 +132,30 @@ pub const Builder = struct { return test_step; } + pub fn addAssemble(self: &Builder, name: []const u8, src: []const u8) -> &AsmStep { + const asm_step = %%self.allocator.create(AsmStep); + *asm_step = AsmStep.init(self, name, src); + return asm_step; + } + + pub fn addLinkExecutable(self: &Builder, name: []const u8) -> &LinkStep { + const exe = %%self.allocator.create(LinkStep); + *exe = LinkStep.initExecutable(self, name); + return exe; + } + + pub fn addLinkStaticLibrary(self: &Builder, name: []const u8) -> &LinkStep { + const exe = %%self.allocator.create(LinkStep); + *exe = LinkStep.initStaticLibrary(self, name); + return exe; + } + + pub fn addLinkSharedLibrary(self: &Builder, name: []const u8, ver: &const Version) -> &LinkStep { + const exe = %%self.allocator.create(LinkStep); + *exe = LinkStep.initSharedLibrary(self, name, ver); + return exe; + } + pub fn addCStaticLibrary(self: &Builder, name: []const u8) -> &CLibrary { const lib = %%self.allocator.create(CLibrary); *lib = CLibrary.initStatic(self, name); @@ -546,6 +571,17 @@ const Target = enum { else => ".o", }; } + + pub fn exeFileExt(self: &const Target) -> []const u8 { + const target_os = switch (*self) { + Target.Native => @compileVar("os"), + Target.Cross => |t| t.os, + }; + return switch (target_os) { + Os.windows => ".exe", + else => "", + }; + } }; const LinkerScript = enum { @@ -704,6 +740,310 @@ pub const Exe = struct { } }; +pub const AsmStep = struct { + step: Step, + builder: &Builder, + name: []const u8, + target: Target, + verbose: bool, + release: bool, + output_path: ?[]const u8, + src_path: []const u8, + + pub fn init(builder: &Builder, name: []const u8, src_path: []const u8) -> AsmStep { + var self = AsmStep { + .step = Step.init(name, builder.allocator, make), + .builder = builder, + .name = name, + .target = Target.Native, + .verbose = false, + .release = false, + .output_path = null, + .src_path = src_path, + }; + return self; + } + + pub fn setTarget(self: &AsmStep, target_arch: Arch, target_os: Os, target_environ: Environ) { + self.target = Target.Cross { + CrossTarget { + .arch = target_arch, + .os = target_os, + .environ = target_environ, + } + }; + } + + pub fn setVerbose(self: &AsmStep, value: bool) { + self.verbose = value; + } + + pub fn setRelease(self: &AsmStep, value: bool) { + self.release = value; + } + + pub fn setOutputPath(self: &AsmStep, value: []const u8) { + self.output_path = value; + } + + fn make(step: &Step) -> %void { + const self = @fieldParentPtr(AsmStep, "step", step); + const builder = self.builder; + + var zig_args = List([]const u8).init(builder.allocator); + defer zig_args.deinit(); + + %%zig_args.append("asm"); + %%zig_args.append(builder.pathFromRoot(self.src_path)); + + if (self.verbose) { + %%zig_args.append("--verbose"); + } + + if (self.release) { + %%zig_args.append("--release"); + } + + if (const output_path ?= self.output_path) { + %%zig_args.append("--output"); + %%zig_args.append(builder.pathFromRoot(output_path)); + } + + %%zig_args.append("--name"); + %%zig_args.append(self.name); + + switch (self.target) { + Target.Native => {}, + Target.Cross => |cross_target| { + %%zig_args.append("--target-arch"); + %%zig_args.append(@enumTagName(cross_target.arch)); + + %%zig_args.append("--target-os"); + %%zig_args.append(@enumTagName(cross_target.os)); + + %%zig_args.append("--target-environ"); + %%zig_args.append(@enumTagName(cross_target.environ)); + }, + } + + builder.spawnChild(builder.zig_exe, zig_args.toSliceConst()); + } +}; + +pub const LinkStep = struct { + step: Step, + builder: &Builder, + name: []const u8, + target: Target, + linker_script: LinkerScript, + link_libs: BufSet, + verbose: bool, + release: bool, + output_path: ?[]const u8, + object_files: List([]const u8), + static: bool, + out_filename: []const u8, + out_type: OutType, + version: Version, + major_only_filename: []const u8, + name_only_filename: []const u8, + + const OutType = enum { + Exe, + Lib, + }; + + pub fn initExecutable(builder: &Builder, name: []const u8) -> LinkStep { + return init(builder, name, OutType.Exe, builder.version(0, 0, 0), false) + } + + pub fn initSharedLibrary(builder: &Builder, name: []const u8, version: &const Version) -> LinkStep { + return init(builder, name, OutType.Lib, version, false) + } + + pub fn initStaticLibrary(builder: &Builder, name: []const u8) -> LinkStep { + return init(builder, name, OutType.Lib, builder.version(0, 0, 0), true) + } + + fn init(builder: &Builder, name: []const u8, out_type: OutType, version: &const Version, static: bool) -> LinkStep { + var self = LinkStep { + .builder = builder, + .verbose = false, + .release = false, + .name = name, + .target = Target.Native, + .linker_script = LinkerScript.None, + .link_libs = BufSet.init(builder.allocator), + .step = Step.init(name, builder.allocator, make), + .output_path = null, + .object_files = List([]const u8).init(builder.allocator), + .out_type = out_type, + .version = *version, + .static = static, + .out_filename = undefined, + .major_only_filename = undefined, + .name_only_filename = undefined, + }; + self.computeOutFileName(); + return self; + } + + fn computeOutFileName(self: &LinkStep) { + switch (self.out_type) { + OutType.Exe => { + self.out_filename = %%fmt.allocPrint(self.builder.allocator, "{}{}", + self.name, self.target.exeFileExt()); + }, + OutType.Lib => { + if (self.static) { + self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.a", self.name); + } else { + self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.so.{d}.{d}.{d}", + self.name, self.version.major, self.version.minor, self.version.patch); + self.major_only_filename = %%fmt.allocPrint(self.builder.allocator, + "lib{}.so.{d}", self.name, self.version.major); + self.name_only_filename = %%fmt.allocPrint(self.builder.allocator, + "lib{}.so", self.name); + } + }, + } + } + + pub fn addObjectFile(self: &LinkStep, file: []const u8) { + %%self.object_files.append(file); + } + + pub fn setTarget(self: &LinkStep, target_arch: Arch, target_os: Os, target_environ: Environ) { + self.target = Target.Cross { + CrossTarget { + .arch = target_arch, + .os = target_os, + .environ = target_environ, + } + }; + self.computeOutFileName(); + } + + /// LinkStep keeps a reference to script for its lifetime or until this function + /// is called again. + pub fn setLinkerScriptContents(self: &LinkStep, script: []const u8) { + self.linker_script = LinkerScript.Embed { script }; + } + + pub fn setLinkerScriptPath(self: &LinkStep, path: []const u8) { + self.linker_script = LinkerScript.Path { path }; + } + + pub fn linkLibrary(self: &LinkStep, name: []const u8) { + %%self.link_libs.put(name); + } + + pub fn setVerbose(self: &LinkStep, value: bool) { + self.verbose = value; + } + + pub fn setRelease(self: &LinkStep, value: bool) { + self.release = value; + } + + pub fn setOutputPath(self: &LinkStep, value: []const u8) { + self.output_path = value; + } + + fn make(step: &Step) -> %void { + const self = @fieldParentPtr(LinkStep, "step", step); + const builder = self.builder; + + if (self.object_files.len == 0) { + %%io.stderr.printf("{}: linker needs 1 or more objects to link\n", step.name); + return error.NeedAnObject; + } + + var zig_args = List([]const u8).init(builder.allocator); + defer zig_args.deinit(); + + const cmd = switch (self.out_type) { + OutType.Exe => "link_exe", + OutType.Lib => "link_lib", + }; + %%zig_args.append(cmd); + + for (self.object_files.toSliceConst()) |object_file| { + %%zig_args.append(builder.pathFromRoot(object_file)); + } + + if (self.verbose) { + %%zig_args.append("--verbose"); + } + + if (self.release) { + %%zig_args.append("--release"); + } + + if (self.static) { + %%zig_args.append("--static"); + } + + if (const output_path ?= self.output_path) { + %%zig_args.append("--output"); + %%zig_args.append(builder.pathFromRoot(output_path)); + } + + %%zig_args.append("--name"); + %%zig_args.append(self.name); + + switch (self.target) { + Target.Native => {}, + Target.Cross => |cross_target| { + %%zig_args.append("--target-arch"); + %%zig_args.append(@enumTagName(cross_target.arch)); + + %%zig_args.append("--target-os"); + %%zig_args.append(@enumTagName(cross_target.os)); + + %%zig_args.append("--target-environ"); + %%zig_args.append(@enumTagName(cross_target.environ)); + }, + } + + switch (self.linker_script) { + LinkerScript.None => {}, + LinkerScript.Embed => |script| { + 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)); + %%zig_args.append("--linker-script"); + %%zig_args.append(tmp_file_name); + }, + LinkerScript.Path => |path| { + %%zig_args.append("--linker-script"); + %%zig_args.append(path); + }, + } + + { + var it = self.link_libs.iterator(); + while (true) { + const entry = it.next() ?? break; + %%zig_args.append("--library"); + %%zig_args.append(entry.key); + } + } + + 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, @@ -803,7 +1143,7 @@ pub const CLibrary = struct { } pub fn initStatic(builder: &Builder, name: []const u8) -> CLibrary { - return init(builder, name, undefined, true); + return init(builder, name, builder.version(0, 0, 0), true); } fn init(builder: &Builder, name: []const u8, version: &const Version, static: bool) -> CLibrary { @@ -931,7 +1271,7 @@ pub const CLibrary = struct { %%cc_args.append(self.out_filename); for (self.object_files.toSliceConst()) |object_file| { - %%cc_args.append(object_file); + %%cc_args.append(builder.pathFromRoot(object_file)); } builder.spawnChild(cc, cc_args.toSliceConst()); diff --git a/test/assemble_and_link.zig b/test/assemble_and_link.zig new file mode 100644 index 0000000000..89b0195fe6 --- /dev/null +++ b/test/assemble_and_link.zig @@ -0,0 +1,26 @@ +const tests = @import("tests.zig"); + +pub fn addCases(cases: &tests.CompareOutputContext) { + if (@compileVar("os") == Os.linux and @compileVar("arch") == Arch.x86_64) { + cases.addAsm("hello world linux x86_64", + \\.text + \\.globl _start + \\ + \\_start: + \\ mov rax, 1 + \\ mov rdi, 1 + \\ lea rsi, msg + \\ mov rdx, 14 + \\ syscall + \\ + \\ mov rax, 60 + \\ mov rdi, 0 + \\ syscall + \\ + \\.data + \\ + \\msg: + \\ .ascii "Hello, world!\n" + , "Hello, world!\n"); + } +} diff --git a/test/build_examples.zig b/test/build_examples.zig index 174dc14bed..72bed9d296 100644 --- a/test/build_examples.zig +++ b/test/build_examples.zig @@ -1,60 +1,8 @@ -const std = @import("std"); -const build = std.build; -const mem = std.mem; -const fmt = std.fmt; - -pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { - const cases = %%b.allocator.create(BuildExamplesContext); - *cases = BuildExamplesContext { - .b = b, - .step = b.step("test-build-examples", "Build the examples"), - .test_index = 0, - .test_filter = test_filter, - }; +const tests = @import("tests.zig"); +pub fn addCases(cases: &tests.BuildExamplesContext) { cases.add("example/hello_world/hello.zig"); cases.addC("example/hello_world/hello_libc.zig"); cases.add("example/cat/main.zig"); cases.add("example/guess_number/main.zig"); - - return cases.step; } - -const BuildExamplesContext = struct { - b: &build.Builder, - step: &build.Step, - test_index: usize, - test_filter: ?[]const u8, - - pub fn addC(self: &BuildExamplesContext, root_src: []const u8) { - self.addAllArgs(root_src, true); - } - - pub fn add(self: &BuildExamplesContext, root_src: []const u8) { - self.addAllArgs(root_src, false); - } - - pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) { - const b = self.b; - - for ([]bool{false, true}) |release| { - const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})", - root_src, 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.setRelease(release); - if (link_libc) { - exe.linkLibrary("c"); - } - - const log_step = b.addLog("PASS {}\n", annotated_case_name); - log_step.step.dependOn(&exe.step); - - self.step.dependOn(&log_step.step); - } - } -}; diff --git a/test/compare_output.zig b/test/compare_output.zig index f971d7eef1..33ec04995d 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -1,26 +1,7 @@ -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, - }; +const os = @import("std").os; +const tests = @import("tests.zig"); +pub fn addCases(cases: &tests.CompareOutputContext) { cases.addC("hello world with libc", \\const c = @cImport(@cInclude("stdio.h")); \\export fn main(argc: c_int, argv: &&u8) -> c_int { @@ -420,168 +401,4 @@ pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &bu 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"); - } -}; - diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 199d60b184..16431c76f1 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,26 +1,6 @@ -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 addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { - const cases = %%b.allocator.create(CompileErrorContext); - *cases = CompileErrorContext { - .b = b, - .step = b.step("test-compile-errors", "Run the compile error tests"), - .test_index = 0, - .test_filter = test_filter, - }; +const tests = @import("tests.zig"); +pub fn addCases(cases: &tests.CompileErrorContext) { cases.add("implicit semicolon - block statement", \\export fn entry() { \\ {} @@ -1594,219 +1574,4 @@ pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &bui , "error: 'main' is private", ".tmp_source.zig:1:1: note: declared here"); - - - - - return cases.step; } - -const CompileErrorContext = struct { - b: &build.Builder, - step: &build.Step, - test_index: usize, - test_filter: ?[]const u8, - - const TestCase = struct { - name: []const u8, - sources: List(SourceFile), - expected_errors: List([]const u8), - link_libc: bool, - is_exe: 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_errors.append(text); - } - }; - - const CompileCmpOutputStep = struct { - step: build.Step, - context: &CompileErrorContext, - name: []const u8, - test_index: usize, - case: &const TestCase, - release: bool, - - pub fn create(context: &CompileErrorContext, name: []const u8, - case: &const TestCase, release: bool) -> &CompileCmpOutputStep - { - const allocator = context.b.allocator; - const ptr = %%allocator.create(CompileCmpOutputStep); - *ptr = CompileCmpOutputStep { - .step = build.Step.init("CompileCmpOutput", allocator, make), - .context = context, - .name = name, - .test_index = context.test_index, - .case = case, - .release = release, - }; - context.test_index += 1; - return ptr; - } - - fn make(step: &build.Step) -> %void { - const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); - const b = self.context.b; - - const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename); - const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o"); - - var zig_args = List([]const u8).init(b.allocator); - %%zig_args.append(if (self.case.is_exe) "build_exe" else "build_obj"); - %%zig_args.append(b.pathFromRoot(root_src)); - - %%zig_args.append("--name"); - %%zig_args.append("test"); - - %%zig_args.append("--output"); - %%zig_args.append(b.pathFromRoot(obj_path)); - - if (self.release) { - %%zig_args.append("--release"); - } - - %%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 incorrectly succeeded\n"); - return error.TestFailed; - } - }, - else => { - %%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe); - 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 (stdout.len != 0) { - %%io.stderr.printf( - \\ - \\Expected empty stdout, instead found: - \\================================================ - \\{} - \\================================================ - \\ - , stdout); - return error.TestFailed; - } - - for (self.case.expected_errors.toSliceConst()) |expected_error| { - if (mem.indexOf(u8, stderr, expected_error) == null) { - %%io.stderr.printf( - \\ - \\========= Expected this compile error: ========= - \\{} - \\================================================ - \\{} - \\ - , expected_error, stderr); - 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: &CompileErrorContext, 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_errors = List([]const u8).init(self.b.allocator), - .link_libc = false, - .is_exe = false, - }; - tc.addSourceFile(".tmp_source.zig", 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 addC(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { - var tc = self.create(name, source, expected_lines); - tc.link_libc = true; - self.addCase(tc); - } - - pub fn addExe(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { - var tc = self.create(name, source, expected_lines); - tc.is_exe = true; - self.addCase(tc); - } - - pub fn add(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { - const tc = self.create(name, source, expected_lines); - self.addCase(tc); - } - - pub fn addCase(self: &CompileErrorContext, case: &const TestCase) { - const b = self.b; - - 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 compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, release); - self.step.dependOn(&compile_and_cmp_errors.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); - compile_and_cmp_errors.step.dependOn(&write_src.step); - } - } - } -}; diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 2f960f2440..a2ff16fabf 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -58,37 +58,6 @@ static const char *zig_exe = "./zig"; #define NL "\n" #endif -static TestCase *add_asm_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->special = TestSpecialLinkStep; - - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = ".tmp_source.s"; - test_case->source_files.at(0).source_code = source; - - test_case->compiler_args.append("asm"); - test_case->compiler_args.append(".tmp_source.s"); - test_case->compiler_args.append("--name"); - test_case->compiler_args.append("test"); - test_case->compiler_args.append("--color"); - test_case->compiler_args.append("on"); - - test_case->linker_args.append("link_exe"); - test_case->linker_args.append("test.o"); - test_case->linker_args.append("--name"); - test_case->linker_args.append("test"); - test_case->linker_args.append("--output"); - test_case->linker_args.append(tmp_exe_path); - test_case->linker_args.append("--color"); - test_case->linker_args.append("on"); - - test_cases.append(test_case); - - return test_case; -} - static void add_debug_safety_case(const char *case_name, const char *source) { TestCase *test_case = allocate(1); test_case->is_debug_safety = true; @@ -566,33 +535,6 @@ struct comptime { R"(pub const FOO_CHAR = 63;)"); } -static void add_asm_tests(void) { -#if defined(ZIG_OS_LINUX) && defined(ZIG_ARCH_X86_64) - add_asm_case("assemble and link hello world linux x86_64", R"SOURCE( -.text -.globl _start - -_start: - mov rax, 1 - mov rdi, 1 - lea rsi, msg - mov rdx, 14 - syscall - - mov rax, 60 - mov rdi, 0 - syscall - -.data - -msg: - .ascii "Hello, world!\n" - )SOURCE", "Hello, world!\n"); - -#endif -} - - 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) { @@ -800,7 +742,6 @@ int main(int argc, char **argv) { } add_debug_safety_test_cases(); add_parseh_test_cases(); - add_asm_tests(); run_all_tests(grep_text); cleanup(); } diff --git a/test/tests.zig b/test/tests.zig index 7eccf24232..d14d0a1d13 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,3 +1,528 @@ -pub const addCompareOutputTests = @import("compare_output.zig").addCompareOutputTests; -pub const addBuildExampleTests = @import("build_examples.zig").addBuildExampleTests; -pub const addCompileErrorTests = @import("compile_errors.zig").addCompileErrorTests; +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 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 fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(CompareOutputContext); + *cases = CompareOutputContext { + .b = b, + .step = b.step("test-compare-output", "Run the compare output tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + compare_output.addCases(cases); + + return cases.step; +} + +pub fn addCompileErrorTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(CompileErrorContext); + *cases = CompileErrorContext { + .b = b, + .step = b.step("test-compile-errors", "Run the compile error tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + compile_errors.addCases(cases); + + return cases.step; +} + +pub fn addBuildExampleTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(BuildExamplesContext); + *cases = BuildExamplesContext { + .b = b, + .step = b.step("test-build-examples", "Build the examples"), + .test_index = 0, + .test_filter = test_filter, + }; + + build_examples.addCases(cases); + + return cases.step; +} + +pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(CompareOutputContext); + *cases = CompareOutputContext { + .b = b, + .step = b.step("test-asm-link", "Run the assemble and link tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + assemble_and_link.addCases(cases); + + return cases.step; +} + +pub const CompareOutputContext = struct { + b: &build.Builder, + step: &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, + is_asm: 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, + }); + } + }; + + 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"); + } + }; + + pub fn createExtra(self: &CompareOutputContext, name: []const u8, source: []const u8, + expected_output: []const u8, is_asm: bool) -> TestCase + { + var tc = TestCase { + .name = name, + .sources = List(TestCase.SourceFile).init(self.b.allocator), + .expected_output = expected_output, + .link_libc = false, + .is_asm = is_asm, + }; + const root_src_name = if (is_asm) "source.s" else "source.zig"; + tc.addSourceFile(root_src_name, source); + return tc; + } + + pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8, + expected_output: []const u8) -> TestCase + { + return createExtra(self, name, source, expected_output, false); + } + + 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 addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) { + const tc = self.createExtra(name, source, expected_output, true); + 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"); + + if (case.is_asm) { + const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o"); + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "assemble-and-link {}", case.name); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; + } + + const obj = b.addAssemble("test", root_src); + obj.setOutputPath(obj_path); + + 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); + obj.step.dependOn(&write_src.step); + } + + const exe = b.addLinkExecutable("test"); + exe.step.dependOn(&obj.step); + exe.addObjectFile(obj_path); + exe.setOutputPath(exe_path); + + 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.step.dependOn(&run_and_cmp_output.step); + } else { + 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.step.dependOn(&run_and_cmp_output.step); + } + }; + + } +}; + +pub const CompileErrorContext = struct { + b: &build.Builder, + step: &build.Step, + test_index: usize, + test_filter: ?[]const u8, + + const TestCase = struct { + name: []const u8, + sources: List(SourceFile), + expected_errors: List([]const u8), + link_libc: bool, + is_exe: 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_errors.append(text); + } + }; + + const CompileCmpOutputStep = struct { + step: build.Step, + context: &CompileErrorContext, + name: []const u8, + test_index: usize, + case: &const TestCase, + release: bool, + + pub fn create(context: &CompileErrorContext, name: []const u8, + case: &const TestCase, release: bool) -> &CompileCmpOutputStep + { + const allocator = context.b.allocator; + const ptr = %%allocator.create(CompileCmpOutputStep); + *ptr = CompileCmpOutputStep { + .step = build.Step.init("CompileCmpOutput", allocator, make), + .context = context, + .name = name, + .test_index = context.test_index, + .case = case, + .release = release, + }; + context.test_index += 1; + return ptr; + } + + fn make(step: &build.Step) -> %void { + const self = @fieldParentPtr(CompileCmpOutputStep, "step", step); + const b = self.context.b; + + const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename); + const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o"); + + var zig_args = List([]const u8).init(b.allocator); + %%zig_args.append(if (self.case.is_exe) "build_exe" else "build_obj"); + %%zig_args.append(b.pathFromRoot(root_src)); + + %%zig_args.append("--name"); + %%zig_args.append("test"); + + %%zig_args.append("--output"); + %%zig_args.append(b.pathFromRoot(obj_path)); + + if (self.release) { + %%zig_args.append("--release"); + } + + %%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 incorrectly succeeded\n"); + return error.TestFailed; + } + }, + else => { + %%io.stderr.printf("Process {} terminated unexpectedly\n", b.zig_exe); + 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 (stdout.len != 0) { + %%io.stderr.printf( + \\ + \\Expected empty stdout, instead found: + \\================================================ + \\{} + \\================================================ + \\ + , stdout); + return error.TestFailed; + } + + for (self.case.expected_errors.toSliceConst()) |expected_error| { + if (mem.indexOf(u8, stderr, expected_error) == null) { + %%io.stderr.printf( + \\ + \\========= Expected this compile error: ========= + \\{} + \\================================================ + \\{} + \\ + , expected_error, stderr); + 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: &CompileErrorContext, 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_errors = List([]const u8).init(self.b.allocator), + .link_libc = false, + .is_exe = false, + }; + tc.addSourceFile(".tmp_source.zig", 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 addC(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { + var tc = self.create(name, source, expected_lines); + tc.link_libc = true; + self.addCase(tc); + } + + pub fn addExe(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { + var tc = self.create(name, source, expected_lines); + tc.is_exe = true; + self.addCase(tc); + } + + pub fn add(self: &CompileErrorContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create(name, source, expected_lines); + self.addCase(tc); + } + + pub fn addCase(self: &CompileErrorContext, case: &const TestCase) { + const b = self.b; + + for ([]bool{false, true}) |release| { + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "compile-error {} ({})", + case.name, if (release) "release" else "debug"); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + continue; + } + + const compile_and_cmp_errors = CompileCmpOutputStep.create(self, annotated_case_name, case, release); + self.step.dependOn(&compile_and_cmp_errors.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); + compile_and_cmp_errors.step.dependOn(&write_src.step); + } + } + } +}; + +pub const BuildExamplesContext = struct { + b: &build.Builder, + step: &build.Step, + test_index: usize, + test_filter: ?[]const u8, + + pub fn addC(self: &BuildExamplesContext, root_src: []const u8) { + self.addAllArgs(root_src, true); + } + + pub fn add(self: &BuildExamplesContext, root_src: []const u8) { + self.addAllArgs(root_src, false); + } + + pub fn addAllArgs(self: &BuildExamplesContext, root_src: []const u8, link_libc: bool) { + const b = self.b; + + for ([]bool{false, true}) |release| { + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "build {} ({})", + root_src, 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.setRelease(release); + if (link_libc) { + exe.linkLibrary("c"); + } + + const log_step = b.addLog("PASS {}\n", annotated_case_name); + log_step.step.dependOn(&exe.step); + + self.step.dependOn(&log_step.step); + } + } +};