From 37b9a2e6a4aa59494406d38495768778502fbcda Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 01:13:15 -0400 Subject: [PATCH 01/10] convert compare-output tests to use zig build system --- build.zig | 36 ++ src/main.cpp | 3 + std/build.zig | 253 ++++++++++-- std/mem.zig | 28 ++ std/os/index.zig | 65 ++- std/os/linux.zig | 4 + std/os/path.zig | 59 ++- std/special/build_file_template.zig | 4 +- std/special/build_runner.zig | 53 ++- test/run_tests.cpp | 407 ------------------- test/run_tests.zig | 5 + test/tests.zig | 587 ++++++++++++++++++++++++++++ 12 files changed, 1023 insertions(+), 481 deletions(-) create mode 100644 build.zig create mode 100644 test/run_tests.zig create mode 100644 test/tests.zig diff --git a/build.zig b/build.zig new file mode 100644 index 000000000..24653ab15 --- /dev/null +++ b/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)); +} diff --git a/src/main.cpp b/src/main.cpp index 32b22e8bd..058fa7a88 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -168,6 +168,7 @@ int main(int argc, char **argv) { ZigList 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)); diff --git a/std/build.zig b/std/build.zig index ca3cb6554..11c038329 100644 --- a/std/build.zig +++ b/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| { - %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 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| { + %%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 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), diff --git a/std/mem.zig b/std/mem.zig index 99bfb0ade..66e119b50 100644 --- a/std/mem.zig +++ b/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. diff --git a/std/os/index.zig b/std/os/index.zig index c154f59ee..7da623d3c 100644 --- a/std/os/index.zig +++ b/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 + }; +} diff --git a/std/os/linux.zig b/std/os/linux.zig index d79b8f045..7c841fafa 100644 --- a/std/os/linux.zig +++ b/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 { diff --git a/std/os/path.zig b/std/os/path.zig index e8b650567..66c07ad6d 100644 --- a/std/os/path.zig +++ b/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, "."); } diff --git a/std/special/build_file_template.zig b/std/special/build_file_template.zig index ea91ebb4c..dfb2efeec 100644 --- a/std/special/build_file_template.zig +++ b/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); } diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 21ab1baaf..7e9b31b24 100644 --- a/std/special/build_runner.zig +++ b/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| { diff --git a/test/run_tests.cpp b/test/run_tests.cpp index aad51dbe1..1be5f69ba 100644 --- a/test/run_tests.cpp +++ b/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(); diff --git a/test/run_tests.zig b/test/run_tests.zig new file mode 100644 index 000000000..5cdaa94fe --- /dev/null +++ b/test/run_tests.zig @@ -0,0 +1,5 @@ +const io = @import("std").io; + +pub fn main() -> %void { + %%io.stderr.printf("TODO run tests\n"); +} diff --git a/test/tests.zig b/test/tests.zig new file mode 100644 index 000000000..f971d7eef --- /dev/null +++ b/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"); + } +}; + From 10525b869d1a50eef65d8ec2717dda61c937e154 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 01:45:46 -0400 Subject: [PATCH 02/10] convert build examples tests to zig build system --- build.zig | 30 +- test/build_examples.zig | 60 ++++ test/compare_output.zig | 587 +++++++++++++++++++++++++++++++++++++++ test/run_tests.cpp | 8 - test/tests.zig | 589 +--------------------------------------- 5 files changed, 662 insertions(+), 612 deletions(-) create mode 100644 test/build_examples.zig create mode 100644 test/compare_output.zig diff --git a/build.zig b/build.zig index 24653ab15..a8b167bef 100644 --- a/build.zig +++ b/build.zig @@ -10,27 +10,23 @@ pub fn build(b: &Builder) { 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); + for ([]bool{false, true}) |release| { + for ([]bool{false, true}) |link_libc| { + const these_tests = b.addTest("test/self_hosted.zig"); + // TODO add prefix to test names + // TODO pass test_filter to these_tests + these_tests.setRelease(release); + if (link_libc) { + these_tests.linkLibrary("c"); + } + self_hosted_tests.dependOn(&these_tests.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)); + test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); } diff --git a/test/build_examples.zig b/test/build_examples.zig new file mode 100644 index 000000000..174dc14be --- /dev/null +++ b/test/build_examples.zig @@ -0,0 +1,60 @@ +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, + }; + + 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 new file mode 100644 index 000000000..f971d7eef --- /dev/null +++ b/test/compare_output.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"); + } +}; + diff --git a/test/run_tests.cpp b/test/run_tests.cpp index 1be5f69ba..ef89ee81b 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -272,13 +272,6 @@ static TestCase *add_example_compile_libc(const char *root_source_file) { //////////////////////////////////////////////////////////////////////////////////// -static void add_build_examples(void) { - add_example_compile("example/hello_world/hello.zig"); - add_example_compile_libc("example/hello_world/hello_libc.zig"); - add_example_compile("example/cat/main.zig"); - add_example_compile("example/guess_number/main.zig"); -} - //////////////////////////////////////////////////////////////////////////////////// @@ -2615,7 +2608,6 @@ int main(int argc, char **argv) { } } } - add_build_examples(); add_debug_safety_test_cases(); add_compile_failure_test_cases(); add_parse_error_tests(); diff --git a/test/tests.zig b/test/tests.zig index f971d7eef..d398133db 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,587 +1,2 @@ -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"); - } -}; - +pub const addCompareOutputTests = @import("compare_output.zig").addCompareOutputTests; +pub const addBuildExampleTests = @import("build_examples.zig").addBuildExampleTests; From d0a17b6937cd0128f4a4fc37ec0f2bd99266034e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 04:12:22 -0400 Subject: [PATCH 03/10] convert std lib tests to zig build system --- build.zig | 18 +- test/compile_errors.zig | 1812 ++++++++++++++++++++++++++++++++++++++ test/run_tests.cpp | 1814 --------------------------------------- test/tests.zig | 1 + 4 files changed, 1830 insertions(+), 1815 deletions(-) create mode 100644 test/compile_errors.zig diff --git a/build.zig b/build.zig index a8b167bef..8e8b12358 100644 --- a/build.zig +++ b/build.zig @@ -11,6 +11,7 @@ pub fn build(b: &Builder) { run_tests_cmd.step.dependOn(&run_tests_exe.step); const self_hosted_tests = b.step("test-self-hosted", "Run the self-hosted tests"); + test_step.dependOn(self_hosted_tests); for ([]bool{false, true}) |release| { for ([]bool{false, true}) |link_libc| { const these_tests = b.addTest("test/self_hosted.zig"); @@ -24,9 +25,24 @@ pub fn build(b: &Builder) { } } - test_step.dependOn(self_hosted_tests); + const std_lib_tests = b.step("test-std", "Run the standard library tests"); + test_step.dependOn(std_lib_tests); + for ([]bool{false, true}) |release| { + for ([]bool{false, true}) |link_libc| { + const these_tests = b.addTest("std/index.zig"); + // TODO add prefix to test names + // TODO pass test_filter to these_tests + these_tests.setRelease(release); + if (link_libc) { + these_tests.linkLibrary("c"); + } + std_lib_tests.dependOn(&these_tests.step); + } + } + //test_step.dependOn(&run_tests_cmd.step); test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); + test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig new file mode 100644 index 000000000..199d60b18 --- /dev/null +++ b/test/compile_errors.zig @@ -0,0 +1,1812 @@ +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, + }; + + cases.add("implicit semicolon - block statement", + \\export fn entry() { + \\ {} + \\ var good = {}; + \\ ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - block expr", + \\export fn entry() { + \\ _ = {}; + \\ var good = {}; + \\ _ = {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - comptime statement", + \\export fn entry() { + \\ comptime {} + \\ var good = {}; + \\ comptime ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - comptime expression", + \\export fn entry() { + \\ _ = comptime {}; + \\ var good = {}; + \\ _ = comptime {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - defer", + \\export fn entry() { + \\ defer {} + \\ var good = {}; + \\ defer ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: expected token ';', found 'var'"); + + cases.add("implicit semicolon - if statement", + \\export fn entry() { + \\ if(true) {} + \\ var good = {}; + \\ if(true) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if expression", + \\export fn entry() { + \\ _ = if(true) {}; + \\ var good = {}; + \\ _ = if(true) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else statement", + \\export fn entry() { + \\ if(true) {} else {} + \\ var good = {}; + \\ if(true) ({}) else ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else expression", + \\export fn entry() { + \\ _ = if(true) {} else {}; + \\ var good = {}; + \\ _ = if(true) {} else {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else-if statement", + \\export fn entry() { + \\ if(true) {} else if(true) {} + \\ var good = {}; + \\ if(true) ({}) else if(true) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else-if expression", + \\export fn entry() { + \\ _ = if(true) {} else if(true) {}; + \\ var good = {}; + \\ _ = if(true) {} else if(true) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else-if-else statement", + \\export fn entry() { + \\ if(true) {} else if(true) {} else {} + \\ var good = {}; + \\ if(true) ({}) else if(true) ({}) else ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if-else-if-else expression", + \\export fn entry() { + \\ _ = if(true) {} else if(true) {} else {}; + \\ var good = {}; + \\ _ = if(true) {} else if(true) {} else {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var) statement", + \\export fn entry() { + \\ if(_=foo()) {} + \\ var good = {}; + \\ if(_=foo()) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var) expression", + \\export fn entry() { + \\ _ = if(_=foo()) {}; + \\ var good = {}; + \\ _ = if(_=foo()) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else statement", + \\export fn entry() { + \\ if(_=foo()) {} else {} + \\ var good = {}; + \\ if(_=foo()) ({}) else ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else expression", + \\export fn entry() { + \\ _ = if(_=foo()) {} else {}; + \\ var good = {}; + \\ _ = if(_=foo()) {} else {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else-if(var) statement", + \\export fn entry() { + \\ if(_=foo()) {} else if(_=foo()) {} + \\ var good = {}; + \\ if(_=foo()) ({}) else if(_=foo()) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else-if(var) expression", + \\export fn entry() { + \\ _ = if(_=foo()) {} else if(_=foo()) {}; + \\ var good = {}; + \\ _ = if(_=foo()) {} else if(_=foo()) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else-if(var)-else statement", + \\export fn entry() { + \\ if(_=foo()) {} else if(_=foo()) {} else {} + \\ var good = {}; + \\ if(_=foo()) ({}) else if(_=foo()) ({}) else ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - if(var)-else-if(var)-else expression", + \\export fn entry() { + \\ _ = if(_=foo()) {} else if(_=foo()) {} else {}; + \\ var good = {}; + \\ _ = if(_=foo()) {} else if(_=foo()) {} else {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - try statement", + \\export fn entry() { + \\ try (_ = foo()) {} + \\ var good = {}; + \\ try (_ = foo()) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - try expression", + \\export fn entry() { + \\ _ = try (_ = foo()) {}; + \\ var good = {}; + \\ _ = try (_ = foo()) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - while statement", + \\export fn entry() { + \\ while(true) {} + \\ var good = {}; + \\ while(true) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - while expression", + \\export fn entry() { + \\ _ = while(true) {}; + \\ var good = {}; + \\ _ = while(true) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - while-continue statement", + \\export fn entry() { + \\ while(true;{}) {} + \\ var good = {}; + \\ while(true;{}) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - while-continue expression", + \\export fn entry() { + \\ _ = while(true;{}) {}; + \\ var good = {}; + \\ _ = while(true;{}) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - for statement", + \\export fn entry() { + \\ for(foo()) {} + \\ var good = {}; + \\ for(foo()) ({}) + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("implicit semicolon - for expression", + \\export fn entry() { + \\ _ = for(foo()) {}; + \\ var good = {}; + \\ _ = for(foo()) {} + \\ var bad = {}; + \\} + , ".tmp_source.zig:5:5: error: invalid token: 'var'"); + + cases.add("multiple function definitions", + \\fn a() {} + \\fn a() {} + \\export fn entry() { a(); } + , ".tmp_source.zig:2:1: error: redefinition of 'a'"); + + cases.add("unreachable with return", + \\fn a() -> noreturn {return;} + \\export fn entry() { a(); } + , ".tmp_source.zig:1:21: error: expected type 'noreturn', found 'void'"); + + cases.add("control reaches end of non-void function", + \\fn a() -> i32 {} + \\export fn entry() { _ = a(); } + , ".tmp_source.zig:1:15: error: expected type 'i32', found 'void'"); + + cases.add("undefined function call", + \\export fn a() { + \\ b(); + \\} + , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); + + cases.add("wrong number of arguments", + \\export fn a() { + \\ b(1); + \\} + \\fn b(a: i32, b: i32, c: i32) { } + , ".tmp_source.zig:2:6: error: expected 3 arguments, found 1"); + + cases.add("invalid type", + \\fn a() -> bogus {} + \\export fn entry() { _ = a(); } + , ".tmp_source.zig:1:11: error: use of undeclared identifier 'bogus'"); + + cases.add("pointer to unreachable", + \\fn a() -> &noreturn {} + \\export fn entry() { _ = a(); } + , ".tmp_source.zig:1:12: error: pointer to unreachable not allowed"); + + cases.add("unreachable code", + \\export fn a() { + \\ return; + \\ b(); + \\} + \\ + \\fn b() {} + , ".tmp_source.zig:3:6: error: unreachable code"); + + cases.add("bad import", + \\const bogus = @import("bogus-does-not-exist.zig"); + \\export fn entry() { bogus.bogo(); } + , ".tmp_source.zig:1:15: error: unable to find 'bogus-does-not-exist.zig'"); + + cases.add("undeclared identifier", + \\export fn a() { + \\ b + + \\ c + \\} + , + ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'", + ".tmp_source.zig:3:5: error: use of undeclared identifier 'c'"); + + cases.add("parameter redeclaration", + \\fn f(a : i32, a : i32) { + \\} + \\export fn entry() { f(1, 2); } + , ".tmp_source.zig:1:15: error: redeclaration of variable 'a'"); + + cases.add("local variable redeclaration", + \\export fn f() { + \\ const a : i32 = 0; + \\ const a = 0; + \\} + , ".tmp_source.zig:3:5: error: redeclaration of variable 'a'"); + + cases.add("local variable redeclares parameter", + \\fn f(a : i32) { + \\ const a = 0; + \\} + \\export fn entry() { f(1); } + , ".tmp_source.zig:2:5: error: redeclaration of variable 'a'"); + + cases.add("variable has wrong type", + \\export fn f() -> i32 { + \\ const a = c"a"; + \\ a + \\} + , ".tmp_source.zig:3:5: error: expected type 'i32', found '&const u8'"); + + cases.add("if condition is bool, not int", + \\export fn f() { + \\ if (0) {} + \\} + , ".tmp_source.zig:2:9: error: integer value 0 cannot be implicitly casted to type 'bool'"); + + cases.add("assign unreachable", + \\export fn f() { + \\ const a = return; + \\} + , ".tmp_source.zig:2:5: error: unreachable code"); + + cases.add("unreachable variable", + \\export fn f() { + \\ const a: noreturn = {}; + \\} + , ".tmp_source.zig:2:14: error: variable of type 'noreturn' not allowed"); + + cases.add("unreachable parameter", + \\fn f(a: noreturn) {} + \\export fn entry() { f(); } + , ".tmp_source.zig:1:9: error: parameter of type 'noreturn' not allowed"); + + cases.add("bad assignment target", + \\export fn f() { + \\ 3 = 3; + \\} + , ".tmp_source.zig:2:7: error: cannot assign to constant"); + + cases.add("assign to constant variable", + \\export fn f() { + \\ const a = 3; + \\ a = 4; + \\} + , ".tmp_source.zig:3:7: error: cannot assign to constant"); + + cases.add("use of undeclared identifier", + \\export fn f() { + \\ b = 3; + \\} + , ".tmp_source.zig:2:5: error: use of undeclared identifier 'b'"); + + cases.add("const is a statement, not an expression", + \\export fn f() { + \\ (const a = 0); + \\} + , ".tmp_source.zig:2:6: error: invalid token: 'const'"); + + cases.add("array access of undeclared identifier", + \\export fn f() { + \\ i[i] = i[i]; + \\} + , ".tmp_source.zig:2:5: error: use of undeclared identifier 'i'", + ".tmp_source.zig:2:12: error: use of undeclared identifier 'i'"); + + cases.add("array access of non array", + \\export fn f() { + \\ var bad : bool = undefined; + \\ bad[bad] = bad[bad]; + \\} + , ".tmp_source.zig:3:8: error: array access of non-array type 'bool'", + ".tmp_source.zig:3:19: error: array access of non-array type 'bool'"); + + cases.add("array access with non integer index", + \\export fn f() { + \\ var array = "aoeu"; + \\ var bad = false; + \\ array[bad] = array[bad]; + \\} + , ".tmp_source.zig:4:11: error: expected type 'usize', found 'bool'", + ".tmp_source.zig:4:24: error: expected type 'usize', found 'bool'"); + + cases.add("write to const global variable", + \\const x : i32 = 99; + \\fn f() { + \\ x = 1; + \\} + \\export fn entry() { f(); } + , ".tmp_source.zig:3:7: error: cannot assign to constant"); + + + cases.add("missing else clause", + \\fn f(b: bool) { + \\ const x : i32 = if (b) { 1 }; + \\ const y = if (b) { i32(1) }; + \\} + \\export fn entry() { f(true); } + , ".tmp_source.zig:2:30: error: integer value 1 cannot be implicitly casted to type 'void'", + ".tmp_source.zig:3:15: error: incompatible types: 'i32' and 'void'"); + + cases.add("direct struct loop", + \\const A = struct { a : A, }; + \\export fn entry() -> usize { @sizeOf(A) } + , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); + + cases.add("indirect struct loop", + \\const A = struct { b : B, }; + \\const B = struct { c : C, }; + \\const C = struct { a : A, }; + \\export fn entry() -> usize { @sizeOf(A) } + , ".tmp_source.zig:1:11: error: struct 'A' contains itself"); + + cases.add("invalid struct field", + \\const A = struct { x : i32, }; + \\export fn f() { + \\ var a : A = undefined; + \\ a.foo = 1; + \\ const y = a.bar; + \\} + , + ".tmp_source.zig:4:6: error: no member named 'foo' in 'A'", + ".tmp_source.zig:5:16: error: no member named 'bar' in 'A'"); + + cases.add("redefinition of struct", + \\const A = struct { x : i32, }; + \\const A = struct { y : i32, }; + , ".tmp_source.zig:2:1: error: redefinition of 'A'"); + + cases.add("redefinition of enums", + \\const A = enum {}; + \\const A = enum {}; + , ".tmp_source.zig:2:1: error: redefinition of 'A'"); + + cases.add("redefinition of global variables", + \\var a : i32 = 1; + \\var a : i32 = 2; + , + ".tmp_source.zig:2:1: error: redefinition of 'a'", + ".tmp_source.zig:1:1: note: previous definition is here"); + + cases.add("byvalue struct parameter in exported function", + \\const A = struct { x : i32, }; + \\export fn f(a : A) {} + , ".tmp_source.zig:2:13: error: byvalue types not yet supported on extern function parameters"); + + cases.add("byvalue struct return value in exported function", + \\const A = struct { x: i32, }; + \\export fn f() -> A { + \\ A {.x = 1234 } + \\} + , ".tmp_source.zig:2:18: error: byvalue types not yet supported on extern function return values"); + + cases.add("duplicate field in struct value expression", + \\const A = struct { + \\ x : i32, + \\ y : i32, + \\ z : i32, + \\}; + \\export fn f() { + \\ const a = A { + \\ .z = 1, + \\ .y = 2, + \\ .x = 3, + \\ .z = 4, + \\ }; + \\} + , ".tmp_source.zig:11:9: error: duplicate field"); + + cases.add("missing field in struct value expression", + \\const A = struct { + \\ x : i32, + \\ y : i32, + \\ z : i32, + \\}; + \\export fn f() { + \\ // we want the error on the '{' not the 'A' because + \\ // the A could be a complicated expression + \\ const a = A { + \\ .z = 4, + \\ .y = 2, + \\ }; + \\} + , ".tmp_source.zig:9:17: error: missing field: 'x'"); + + cases.add("invalid field in struct value expression", + \\const A = struct { + \\ x : i32, + \\ y : i32, + \\ z : i32, + \\}; + \\export fn f() { + \\ const a = A { + \\ .z = 4, + \\ .y = 2, + \\ .foo = 42, + \\ }; + \\} + , ".tmp_source.zig:10:9: error: no member named 'foo' in 'A'"); + + cases.add("invalid break expression", + \\export fn f() { + \\ break; + \\} + , ".tmp_source.zig:2:5: error: 'break' expression outside loop"); + + cases.add("invalid continue expression", + \\export fn f() { + \\ continue; + \\} + , ".tmp_source.zig:2:5: error: 'continue' expression outside loop"); + + cases.add("invalid maybe type", + \\export fn f() { + \\ if (const x ?= true) { } + \\} + , ".tmp_source.zig:2:20: error: expected nullable type, found 'bool'"); + + cases.add("cast unreachable", + \\fn f() -> i32 { + \\ i32(return 1) + \\} + \\export fn entry() { _ = f(); } + , ".tmp_source.zig:2:8: error: unreachable code"); + + cases.add("invalid builtin fn", + \\fn f() -> @bogus(foo) { + \\} + \\export fn entry() { _ = f(); } + , ".tmp_source.zig:1:11: error: invalid builtin function: 'bogus'"); + + cases.add("top level decl dependency loop", + \\const a : @typeOf(b) = 0; + \\const b : @typeOf(a) = 0; + \\export fn entry() { + \\ const c = a + b; + \\} + , ".tmp_source.zig:1:1: error: 'a' depends on itself"); + + cases.add("noalias on non pointer param", + \\fn f(noalias x: i32) {} + \\export fn entry() { f(1234); } + , ".tmp_source.zig:1:6: error: noalias on non-pointer parameter"); + + cases.add("struct init syntax for array", + \\const foo = []u16{.x = 1024,}; + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:1:18: error: type '[]u16' does not support struct initialization syntax"); + + cases.add("type variables must be constant", + \\var foo = u8; + \\export fn entry() -> foo { + \\ return 1; + \\} + , ".tmp_source.zig:1:1: error: variable of type 'type' must be constant"); + + + cases.add("variables shadowing types", + \\const Foo = struct {}; + \\const Bar = struct {}; + \\ + \\fn f(Foo: i32) { + \\ var Bar : i32 = undefined; + \\} + \\ + \\export fn entry() { + \\ f(1234); + \\} + , + ".tmp_source.zig:4:6: error: redefinition of 'Foo'", + ".tmp_source.zig:1:1: note: previous definition is here", + ".tmp_source.zig:5:5: error: redefinition of 'Bar'", + ".tmp_source.zig:2:1: note: previous definition is here"); + + cases.add("multiple else prongs in a switch", + \\fn f(x: u32) { + \\ const value: bool = switch (x) { + \\ 1234 => false, + \\ else => true, + \\ else => true, + \\ }; + \\} + \\export fn entry() { + \\ f(1234); + \\} + , ".tmp_source.zig:5:9: error: multiple else prongs in switch expression"); + + cases.add("global variable initializer must be constant expression", + \\extern fn foo() -> i32; + \\const x = foo(); + \\export fn entry() -> i32 { x } + , ".tmp_source.zig:2:11: error: unable to evaluate constant expression"); + + cases.add("array concatenation with wrong type", + \\const src = "aoeu"; + \\const derp = usize(1234); + \\const a = derp ++ "foo"; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + , ".tmp_source.zig:3:11: error: expected array or C string literal, found 'usize'"); + + cases.add("non compile time array concatenation", + \\fn f() -> []u8 { + \\ s ++ "foo" + \\} + \\var s: [10]u8 = undefined; + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:2:5: error: unable to evaluate constant expression"); + + cases.add("@cImport with bogus include", + \\const c = @cImport(@cInclude("bogus.h")); + \\export fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } + , ".tmp_source.zig:1:11: error: C import failed", + ".h:1:10: note: 'bogus.h' file not found"); + + cases.add("address of number literal", + \\const x = 3; + \\const y = &x; + \\fn foo() -> &const i32 { y } + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:3:26: error: expected type '&const i32', found '&const (integer literal)'"); + + cases.add("integer overflow error", + \\const x : u8 = 300; + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + , ".tmp_source.zig:1:16: error: integer value 300 cannot be implicitly casted to type 'u8'"); + + cases.add("incompatible number literals", + \\const x = 2 == 2.0; + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + , ".tmp_source.zig:1:11: error: integer value 2 cannot be implicitly casted to type '(float literal)'"); + + cases.add("missing function call param", + \\const Foo = struct { + \\ a: i32, + \\ b: i32, + \\ + \\ fn member_a(foo: &const Foo) -> i32 { + \\ return foo.a; + \\ } + \\ fn member_b(foo: &const Foo) -> i32 { + \\ return foo.b; + \\ } + \\}; + \\ + \\const member_fn_type = @typeOf(Foo.member_a); + \\const members = []member_fn_type { + \\ Foo.member_a, + \\ Foo.member_b, + \\}; + \\ + \\fn f(foo: &const Foo, index: usize) { + \\ const result = members[index](); + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:20:34: error: expected 1 arguments, found 0"); + + cases.add("missing function name and param name", + \\fn () {} + \\fn f(i32) {} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , + ".tmp_source.zig:1:1: error: missing function name", + ".tmp_source.zig:2:6: error: missing parameter name"); + + cases.add("wrong function type", + \\const fns = []fn(){ a, b, c }; + \\fn a() -> i32 {0} + \\fn b() -> i32 {1} + \\fn c() -> i32 {2} + \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } + , ".tmp_source.zig:1:21: error: expected type 'fn()', found 'fn() -> i32'"); + + cases.add("extern function pointer mismatch", + \\const fns = [](fn(i32)->i32){ a, b, c }; + \\pub fn a(x: i32) -> i32 {x + 0} + \\pub fn b(x: i32) -> i32 {x + 1} + \\export fn c(x: i32) -> i32 {x + 2} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(fns)) } + , ".tmp_source.zig:1:37: error: expected type 'fn(i32) -> i32', found 'extern fn(i32) -> i32'"); + + + cases.add("implicit cast from f64 to f32", + \\const x : f64 = 1.0; + \\const y : f32 = x; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , ".tmp_source.zig:2:17: error: expected type 'f32', found 'f64'"); + + + cases.add("colliding invalid top level functions", + \\fn func() -> bogus {} + \\fn func() -> bogus {} + \\export fn entry() -> usize { @sizeOf(@typeOf(func)) } + , + ".tmp_source.zig:2:1: error: redefinition of 'func'", + ".tmp_source.zig:1:14: error: use of undeclared identifier 'bogus'"); + + + cases.add("bogus compile var", + \\const x = @compileVar("bogus"); + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + , ".tmp_source.zig:1:23: error: unrecognized compile variable: 'bogus'"); + + + cases.add("non constant expression in array size outside function", + \\const Foo = struct { + \\ y: [get()]u8, + \\}; + \\var global_var: usize = 1; + \\fn get() -> usize { global_var } + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(Foo)) } + , + ".tmp_source.zig:5:21: error: unable to evaluate constant expression", + ".tmp_source.zig:2:12: note: called from here", + ".tmp_source.zig:2:8: note: called from here"); + + + cases.add("addition with non numbers", + \\const Foo = struct { + \\ field: i32, + \\}; + \\const x = Foo {.field = 1} + Foo {.field = 2}; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + , ".tmp_source.zig:4:28: error: invalid operands to binary expression: 'Foo' and 'Foo'"); + + + cases.add("division by zero", + \\const lit_int_x = 1 / 0; + \\const lit_float_x = 1.0 / 0.0; + \\const int_x = i32(1) / i32(0); + \\const float_x = f32(1.0) / f32(0.0); + \\ + \\export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } + \\export fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } + \\export fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } + \\export fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } + , + ".tmp_source.zig:1:21: error: division by zero is undefined", + ".tmp_source.zig:2:25: error: division by zero is undefined", + ".tmp_source.zig:3:22: error: division by zero is undefined", + ".tmp_source.zig:4:26: error: division by zero is undefined"); + + + cases.add("missing switch prong", + \\const Number = enum { + \\ One, + \\ Two, + \\ Three, + \\ Four, + \\}; + \\fn f(n: Number) -> i32 { + \\ switch (n) { + \\ Number.One => 1, + \\ Number.Two => 2, + \\ Number.Three => i32(3), + \\ } + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:8:5: error: enumeration value 'Number.Four' not handled in switch"); + + cases.add("normal string with newline", + \\const foo = "a + \\b"; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:1:13: error: newline not allowed in string literal"); + + cases.add("invalid comparison for function pointers", + \\fn foo() {} + \\const invalid = foo > foo; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(invalid)) } + , ".tmp_source.zig:2:21: error: operator not allowed for type 'fn()'"); + + cases.add("generic function instance with non-constant expression", + \\fn foo(comptime x: i32, y: i32) -> i32 { return x + y; } + \\fn test1(a: i32, b: i32) -> i32 { + \\ return foo(a, b); + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(test1)) } + , ".tmp_source.zig:3:16: error: unable to evaluate constant expression"); + + cases.add("goto jumping into block", + \\export fn f() { + \\ { + \\a_label: + \\ } + \\ goto a_label; + \\} + , ".tmp_source.zig:5:5: error: no label in scope named 'a_label'"); + + cases.add("goto jumping past a defer", + \\fn f(b: bool) { + \\ if (b) goto label; + \\ defer derp(); + \\label: + \\} + \\fn derp(){} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:2:12: error: no label in scope named 'label'"); + + cases.add("assign null to non-nullable pointer", + \\const a: &u8 = null; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + , ".tmp_source.zig:1:16: error: expected type '&u8', found '(null)'"); + + cases.add("indexing an array of size zero", + \\const array = []u8{}; + \\export fn foo() { + \\ const pointer = &array[0]; + \\} + , ".tmp_source.zig:3:27: error: index 0 outside array of size 0"); + + cases.add("compile time division by zero", + \\const y = foo(0); + \\fn foo(x: i32) -> i32 { + \\ 1 / x + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , + ".tmp_source.zig:3:7: error: division by zero is undefined", + ".tmp_source.zig:1:14: note: called from here"); + + cases.add("branch on undefined value", + \\const x = if (undefined) true else false; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(x)) } + , ".tmp_source.zig:1:15: error: use of undefined value"); + + + cases.add("endless loop in function evaluation", + \\const seventh_fib_number = fibbonaci(7); + \\fn fibbonaci(x: i32) -> i32 { + \\ return fibbonaci(x - 1) + fibbonaci(x - 2); + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } + , + ".tmp_source.zig:3:21: error: evaluation exceeded 1000 backwards branches", + ".tmp_source.zig:3:21: note: called from here"); + + cases.add("@embedFile with bogus file", + \\const resource = @embedFile("bogus.txt"); + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(resource)) } + , ".tmp_source.zig:1:29: error: unable to find '", "/bogus.txt'"); + + cases.add("non-const expression in struct literal outside function", + \\const Foo = struct { + \\ x: i32, + \\}; + \\const a = Foo {.x = get_it()}; + \\extern fn get_it() -> i32; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + , ".tmp_source.zig:4:21: error: unable to evaluate constant expression"); + + cases.add("non-const expression function call with struct return value outside function", + \\const Foo = struct { + \\ x: i32, + \\}; + \\const a = get_it(); + \\fn get_it() -> Foo { + \\ global_side_effect = true; + \\ Foo {.x = 13} + \\} + \\var global_side_effect = false; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + , + ".tmp_source.zig:6:24: error: unable to evaluate constant expression", + ".tmp_source.zig:4:17: note: called from here"); + + cases.add("undeclared identifier error should mark fn as impure", + \\export fn foo() { + \\ test_a_thing(); + \\} + \\fn test_a_thing() { + \\ bad_fn_call(); + \\} + , ".tmp_source.zig:5:5: error: use of undeclared identifier 'bad_fn_call'"); + + cases.add("illegal comparison of types", + \\fn bad_eql_1(a: []u8, b: []u8) -> bool { + \\ a == b + \\} + \\const EnumWithData = enum { + \\ One, + \\ Two: i32, + \\}; + \\fn bad_eql_2(a: &const EnumWithData, b: &const EnumWithData) -> bool { + \\ *a == *b + \\} + \\ + \\export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } + \\export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } + , + ".tmp_source.zig:2:7: error: operator not allowed for type '[]u8'", + ".tmp_source.zig:9:8: error: operator not allowed for type 'EnumWithData'"); + + cases.add("non-const switch number literal", + \\export fn foo() { + \\ const x = switch (bar()) { + \\ 1, 2 => 1, + \\ 3, 4 => 2, + \\ else => 3, + \\ }; + \\} + \\fn bar() -> i32 { + \\ 2 + \\} + , ".tmp_source.zig:2:15: error: unable to infer expression type"); + + cases.add("atomic orderings of cmpxchg - failure stricter than success", + \\export fn f() { + \\ var x: i32 = 1234; + \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Monotonic, AtomicOrder.SeqCst)) {} + \\} + , ".tmp_source.zig:3:72: error: failure atomic ordering must be no stricter than success"); + + cases.add("atomic orderings of cmpxchg - success Monotonic or stricter", + \\export fn f() { + \\ var x: i32 = 1234; + \\ while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Unordered, AtomicOrder.Unordered)) {} + \\} + , ".tmp_source.zig:3:49: error: success atomic ordering must be Monotonic or stricter"); + + cases.add("negation overflow in function evaluation", + \\const y = neg(-128); + \\fn neg(x: i8) -> i8 { + \\ -x + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , + ".tmp_source.zig:3:5: error: negation caused overflow", + ".tmp_source.zig:1:14: note: called from here"); + + cases.add("add overflow in function evaluation", + \\const y = add(65530, 10); + \\fn add(a: u16, b: u16) -> u16 { + \\ a + b + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , + ".tmp_source.zig:3:7: error: operation caused overflow", + ".tmp_source.zig:1:14: note: called from here"); + + + cases.add("sub overflow in function evaluation", + \\const y = sub(10, 20); + \\fn sub(a: u16, b: u16) -> u16 { + \\ a - b + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , + ".tmp_source.zig:3:7: error: operation caused overflow", + ".tmp_source.zig:1:14: note: called from here"); + + cases.add("mul overflow in function evaluation", + \\const y = mul(300, 6000); + \\fn mul(a: u16, b: u16) -> u16 { + \\ a * b + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(y)) } + , + ".tmp_source.zig:3:7: error: operation caused overflow", + ".tmp_source.zig:1:14: note: called from here"); + + cases.add("truncate sign mismatch", + \\fn f() -> i8 { + \\ const x: u32 = 10; + \\ @truncate(i8, x) + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:3:19: error: expected signed integer type, found 'u32'"); + + cases.add("%return in function with non error return type", + \\export fn f() { + \\ %return something(); + \\} + \\fn something() -> %void { } + , + ".tmp_source.zig:2:5: error: expected type 'void', found 'error'"); + + cases.add("wrong return type for main", + \\pub fn main() { } + , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); + + cases.add("double ?? on main return value", + \\pub fn main() -> ??void { + \\} + , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); + + cases.add("invalid pointer for var type", + \\extern fn ext() -> usize; + \\var bytes: [ext()]u8 = undefined; + \\export fn f() { + \\ for (bytes) |*b, i| { + \\ *b = u8(i); + \\ } + \\} + , ".tmp_source.zig:2:13: error: unable to evaluate constant expression"); + + cases.add("export function with comptime parameter", + \\export fn foo(comptime x: i32, y: i32) -> i32{ + \\ x + y + \\} + , ".tmp_source.zig:1:15: error: comptime parameter not allowed in extern function"); + + cases.add("extern function with comptime parameter", + \\extern fn foo(comptime x: i32, y: i32) -> i32; + \\fn f() -> i32 { + \\ foo(1, 2) + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:1:15: error: comptime parameter not allowed in extern function"); + + cases.add("convert fixed size array to slice with invalid size", + \\export fn f() { + \\ var array: [5]u8 = undefined; + \\ var foo = ([]const u32)(array)[0]; + \\} + , ".tmp_source.zig:3:28: error: unable to convert [5]u8 to []const u32: size mismatch"); + + cases.add("non-pure function returns type", + \\var a: u32 = 0; + \\pub fn List(comptime T: type) -> type { + \\ a += 1; + \\ SmallList(T, 8) + \\} + \\ + \\pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) -> type { + \\ struct { + \\ items: []T, + \\ length: usize, + \\ prealloc_items: [STATIC_SIZE]T, + \\ } + \\} + \\ + \\export fn function_with_return_type_type() { + \\ var list: List(i32) = undefined; + \\ list.length = 10; + \\} + , ".tmp_source.zig:3:7: error: unable to evaluate constant expression", + ".tmp_source.zig:16:19: note: called from here"); + + cases.add("bogus method call on slice", + \\var self = "aoeu"; + \\fn f(m: []const u8) { + \\ m.copy(u8, self[0...], m); + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:3:6: error: no member named 'copy' in '[]const u8'"); + + cases.add("wrong number of arguments for method fn call", + \\const Foo = struct { + \\ fn method(self: &const Foo, a: i32) {} + \\}; + \\fn f(foo: &const Foo) { + \\ + \\ foo.method(1, 2); + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(f)) } + , ".tmp_source.zig:6:15: error: expected 2 arguments, found 3"); + + cases.add("assign through constant pointer", + \\export fn f() { + \\ var cstr = c"Hat"; + \\ cstr[0] = 'W'; + \\} + , ".tmp_source.zig:3:11: error: cannot assign to constant"); + + cases.add("assign through constant slice", + \\export fn f() { + \\ var cstr: []const u8 = "Hat"; + \\ cstr[0] = 'W'; + \\} + , ".tmp_source.zig:3:11: error: cannot assign to constant"); + + cases.add("main function with bogus args type", + \\pub fn main(args: [][]bogus) -> %void {} + , ".tmp_source.zig:1:23: error: use of undeclared identifier 'bogus'"); + + cases.add("for loop missing element param", + \\fn foo(blah: []u8) { + \\ for (blah) { } + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:2:5: error: for loop expression missing element parameter"); + + cases.add("misspelled type with pointer only reference", + \\const JasonHM = u8; + \\const JasonList = &JsonNode; + \\ + \\const JsonOA = enum { + \\ JSONArray: JsonList, + \\ JSONObject: JasonHM, + \\}; + \\ + \\const JsonType = enum { + \\ JSONNull: void, + \\ JSONInteger: isize, + \\ JSONDouble: f64, + \\ JSONBool: bool, + \\ JSONString: []u8, + \\ JSONArray, + \\ JSONObject, + \\}; + \\ + \\pub const JsonNode = struct { + \\ kind: JsonType, + \\ jobject: ?JsonOA, + \\}; + \\ + \\fn foo() { + \\ var jll: JasonList = undefined; + \\ jll.init(1234); + \\ var jd = JsonNode {.kind = JsonType.JSONArray , .jobject = JsonOA.JSONArray {jll} }; + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:5:16: error: use of undeclared identifier 'JsonList'"); + + cases.add("method call with first arg type primitive", + \\const Foo = struct { + \\ x: i32, + \\ + \\ fn init(x: i32) -> Foo { + \\ Foo { + \\ .x = x, + \\ } + \\ } + \\}; + \\ + \\export fn f() { + \\ const derp = Foo.init(3); + \\ + \\ derp.init(); + \\} + , ".tmp_source.zig:14:5: error: expected type 'i32', found '&const Foo'"); + + cases.add("method call with first arg type wrong container", + \\pub const List = struct { + \\ len: usize, + \\ allocator: &Allocator, + \\ + \\ pub fn init(allocator: &Allocator) -> List { + \\ List { + \\ .len = 0, + \\ .allocator = allocator, + \\ } + \\ } + \\}; + \\ + \\pub var global_allocator = Allocator { + \\ .field = 1234, + \\}; + \\ + \\pub const Allocator = struct { + \\ field: i32, + \\}; + \\ + \\export fn foo() { + \\ var x = List.init(&global_allocator); + \\ x.init(); + \\} + , ".tmp_source.zig:23:5: error: expected type '&Allocator', found '&List'"); + + cases.add("binary not on number literal", + \\const TINY_QUANTUM_SHIFT = 4; + \\const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT; + \\var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1); + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } + , ".tmp_source.zig:3:60: error: unable to perform binary not operation on type '(integer literal)'"); + + cases.addCase({ + const tc = cases.create("multiple files with private function error", + \\const foo = @import("foo.zig"); + \\ + \\export fn callPrivFunction() { + \\ foo.privateFunction(); + \\} + , + ".tmp_source.zig:4:8: error: 'privateFunction' is private", + "foo.zig:1:1: note: declared here"); + + tc.addSourceFile("foo.zig", + \\fn privateFunction() { } + ); + + tc + }); + + cases.add("container init with non-type", + \\const zero: i32 = 0; + \\const a = zero{1}; + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(a)) } + , ".tmp_source.zig:2:11: error: expected type, found 'i32'"); + + cases.add("assign to constant field", + \\const Foo = struct { + \\ field: i32, + \\}; + \\export fn derp() { + \\ const f = Foo {.field = 1234,}; + \\ f.field = 0; + \\} + , ".tmp_source.zig:6:13: error: cannot assign to constant"); + + cases.add("return from defer expression", + \\pub fn testTrickyDefer() -> %void { + \\ defer canFail() %% {}; + \\ + \\ defer %return canFail(); + \\ + \\ const a = maybeInt() ?? return; + \\} + \\ + \\fn canFail() -> %void { } + \\ + \\pub fn maybeInt() -> ?i32 { + \\ return 0; + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } + , ".tmp_source.zig:4:11: error: cannot return from defer expression"); + + cases.add("attempt to access var args out of bounds", + \\fn add(args: ...) -> i32 { + \\ args[0] + args[1] + \\} + \\ + \\fn foo() -> i32 { + \\ add(i32(1234)) + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , + ".tmp_source.zig:2:19: error: index 1 outside argument list of size 1", + ".tmp_source.zig:6:8: note: called from here"); + + cases.add("pass integer literal to var args", + \\fn add(args: ...) -> i32 { + \\ var sum = i32(0); + \\ {comptime var i: usize = 0; inline while (i < args.len; i += 1) { + \\ sum += args[i]; + \\ }} + \\ return sum; + \\} + \\ + \\fn bar() -> i32 { + \\ add(1, 2, 3, 4) + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(bar)) } + , ".tmp_source.zig:10:9: error: parameter of type '(integer literal)' requires comptime"); + + cases.add("assign too big number to u16", + \\export fn foo() { + \\ var vga_mem: u16 = 0xB8000; + \\} + , ".tmp_source.zig:2:24: error: integer value 753664 cannot be implicitly casted to type 'u16'"); + + cases.add("set global variable alignment to non power of 2", + \\const some_data: [100]u8 = { + \\ @setGlobalAlign(some_data, 3); + \\ undefined + \\}; + \\export fn entry() -> usize { @sizeOf(@typeOf(some_data)) } + , ".tmp_source.zig:2:32: error: alignment value must be power of 2"); + + cases.add("compile log", + \\export fn foo() { + \\ comptime bar(12, "hi"); + \\} + \\fn bar(a: i32, b: []const u8) { + \\ @compileLog("begin"); + \\ @compileLog("a", a, "b", b); + \\ @compileLog("end"); + \\} + , + ".tmp_source.zig:5:5: error: found compile log statement", + ".tmp_source.zig:2:17: note: called from here", + ".tmp_source.zig:6:5: error: found compile log statement", + ".tmp_source.zig:2:17: note: called from here", + ".tmp_source.zig:7:5: error: found compile log statement", + ".tmp_source.zig:2:17: note: called from here"); + + cases.add("casting bit offset pointer to regular pointer", + \\const u2 = @IntType(false, 2); + \\const u3 = @IntType(false, 3); + \\ + \\const BitField = packed struct { + \\ a: u3, + \\ b: u3, + \\ c: u2, + \\}; + \\ + \\fn foo(bit_field: &const BitField) -> u3 { + \\ return bar(&bit_field.b); + \\} + \\ + \\fn bar(x: &const u3) -> u3 { + \\ return *x; + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:11:26: error: expected type '&const u3', found '&:3:6 const u3'"); + + cases.add("referring to a struct that is invalid", + \\const UsbDeviceRequest = struct { + \\ Type: u8, + \\}; + \\ + \\export fn foo() { + \\ comptime assert(@sizeOf(UsbDeviceRequest) == 0x8); + \\} + \\ + \\fn assert(ok: bool) { + \\ if (!ok) unreachable; + \\} + , + ".tmp_source.zig:10:14: error: unable to evaluate constant expression", + ".tmp_source.zig:6:20: note: called from here"); + + cases.add("control flow uses comptime var at runtime", + \\export fn foo() { + \\ comptime var i = 0; + \\ while (i < 5; i += 1) { + \\ bar(); + \\ } + \\} + \\ + \\fn bar() { } + , + ".tmp_source.zig:3:5: error: control flow attempts to use compile-time variable at runtime", + ".tmp_source.zig:3:21: note: compile-time variable assigned here"); + + cases.add("ignored return value", + \\export fn foo() { + \\ bar(); + \\} + \\fn bar() -> i32 { 0 } + , ".tmp_source.zig:2:8: error: return value ignored"); + + cases.add("integer literal on a non-comptime var", + \\export fn foo() { + \\ var i = 0; + \\ while (i < 10; i += 1) { } + \\} + , ".tmp_source.zig:2:5: error: unable to infer variable type"); + + cases.add("undefined literal on a non-comptime var", + \\export fn foo() { + \\ var i = undefined; + \\ i = i32(1); + \\} + , ".tmp_source.zig:2:5: error: unable to infer variable type"); + + cases.add("dereference an array", + \\var s_buffer: [10]u8 = undefined; + \\pub fn pass(in: []u8) -> []u8 { + \\ var out = &s_buffer; + \\ *out[0] = in[0]; + \\ return (*out)[0...1]; + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(pass)) } + , ".tmp_source.zig:4:5: error: attempt to dereference non pointer type '[10]u8'"); + + cases.add("pass const ptr to mutable ptr fn", + \\fn foo() -> bool { + \\ const a = ([]const u8)("a"); + \\ const b = &a; + \\ return ptrEql(b, b); + \\} + \\fn ptrEql(a: &[]const u8, b: &[]const u8) -> bool { + \\ return true; + \\} + \\ + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:4:19: error: expected type '&[]const u8', found '&const []const u8'"); + + cases.addCase({ + const tc = cases.create("export collision", + \\const foo = @import("foo.zig"); + \\ + \\export fn bar() -> usize { + \\ return foo.baz; + \\} + , + "foo.zig:1:8: error: exported symbol collision: 'bar'", + ".tmp_source.zig:3:8: note: other symbol is here"); + + tc.addSourceFile("foo.zig", + \\export fn bar() {} + \\pub const baz = 1234; + ); + + tc + }); + + cases.add("pass non-copyable type by value to function", + \\const Point = struct { x: i32, y: i32, }; + \\fn foo(p: Point) { } + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:2:11: error: type 'Point' is not copyable; cannot pass by value"); + + cases.add("implicit cast from array to mutable slice", + \\var global_array: [10]i32 = undefined; + \\fn foo(param: []i32) {} + \\export fn entry() { + \\ foo(global_array); + \\} + , ".tmp_source.zig:4:9: error: expected type '[]i32', found '[10]i32'"); + + cases.add("ptrcast to non-pointer", + \\export fn entry(a: &i32) -> usize { + \\ return @ptrcast(usize, a); + \\} + , ".tmp_source.zig:2:21: error: expected pointer, found 'usize'"); + + cases.add("too many error values to cast to small integer", + \\error A; error B; error C; error D; error E; error F; error G; error H; + \\const u2 = @IntType(false, 2); + \\fn foo(e: error) -> u2 { + \\ return u2(e); + \\} + \\export fn entry() -> usize { @sizeOf(@typeOf(foo)) } + , ".tmp_source.zig:4:14: error: too many error values to fit in 'u2'"); + + cases.add("asm at compile time", + \\comptime { + \\ doSomeAsm(); + \\} + \\ + \\fn doSomeAsm() { + \\ asm volatile ( + \\ \\.globl aoeu; + \\ \\.type aoeu, @function; + \\ \\.set aoeu, derp; + \\ ); + \\} + , ".tmp_source.zig:6:5: error: unable to evaluate constant expression"); + + cases.add("invalid member of builtin enum", + \\export fn entry() { + \\ const foo = Arch.x86; + \\} + , ".tmp_source.zig:2:21: error: container 'Arch' has no member called 'x86'"); + + cases.add("int to ptr of 0 bits", + \\export fn foo() { + \\ var x: usize = 0x1000; + \\ var y: &void = @intToPtr(&void, x); + \\} + , ".tmp_source.zig:3:31: error: type '&void' has 0 bits and cannot store information"); + + cases.add("@fieldParentPtr - non struct", + \\const Foo = i32; + \\export fn foo(a: &i32) -> &Foo { + \\ return @fieldParentPtr(Foo, "a", a); + \\} + , ".tmp_source.zig:3:28: error: expected struct type, found 'i32'"); + + cases.add("@fieldParentPtr - bad field name", + \\const Foo = struct { + \\ derp: i32, + \\}; + \\export fn foo(a: &i32) -> &Foo { + \\ return @fieldParentPtr(Foo, "a", a); + \\} + , ".tmp_source.zig:5:33: error: struct 'Foo' has no field 'a'"); + + cases.add("@fieldParentPtr - field pointer is not pointer", + \\const Foo = struct { + \\ a: i32, + \\}; + \\export fn foo(a: i32) -> &Foo { + \\ return @fieldParentPtr(Foo, "a", a); + \\} + , ".tmp_source.zig:5:38: error: expected pointer, found 'i32'"); + + cases.add("@fieldParentPtr - comptime field ptr not based on struct", + \\const Foo = struct { + \\ a: i32, + \\ b: i32, + \\}; + \\const foo = Foo { .a = 1, .b = 2, }; + \\ + \\comptime { + \\ const field_ptr = @intToPtr(&i32, 0x1234); + \\ const another_foo_ptr = @fieldParentPtr(Foo, "b", field_ptr); + \\} + , ".tmp_source.zig:9:55: error: pointer value not based on parent struct"); + + cases.add("@fieldParentPtr - comptime wrong field index", + \\const Foo = struct { + \\ a: i32, + \\ b: i32, + \\}; + \\const foo = Foo { .a = 1, .b = 2, }; + \\ + \\comptime { + \\ const another_foo_ptr = @fieldParentPtr(Foo, "b", &foo.a); + \\} + , ".tmp_source.zig:8:29: error: field 'b' has index 1 but pointer value is index 0 of struct 'Foo'"); + + cases.addExe("missing main fn in executable", + \\ + , "error: no member named 'main' in '"); + + cases.addExe("private main fn", + \\fn main() {} + , + "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 ef89ee81b..2f960f244 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -16,8 +16,6 @@ enum TestSpecial { TestSpecialNone, - TestSpecialSelfHosted, - TestSpecialStd, TestSpecialLinkStep, }; @@ -60,37 +58,6 @@ static const char *zig_exe = "./zig"; #define NL "\n" #endif -static void add_source_file(TestCase *test_case, const char *path, const char *source) { - test_case->source_files.add_one(); - test_case->source_files.last().relative_path = path; - test_case->source_files.last().source_code = source; -} - -static TestCase *add_simple_case(const char *case_name, const char *source, const char *output) { - TestCase *test_case = allocate(1); - test_case->case_name = case_name; - test_case->output = output; - - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_source_path; - test_case->source_files.at(0).source_code = source; - - test_case->compiler_args.append("build_exe"); - test_case->compiler_args.append(tmp_source_path); - test_case->compiler_args.append("--name"); - test_case->compiler_args.append("test"); - test_case->compiler_args.append("--output"); - test_case->compiler_args.append(tmp_exe_path); - test_case->compiler_args.append("--release"); - test_case->compiler_args.append("--strip"); - test_case->compiler_args.append("--color"); - test_case->compiler_args.append("on"); - - test_cases.append(test_case); - - return test_case; -} - 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; @@ -122,77 +89,6 @@ static TestCase *add_asm_case(const char *case_name, const char *source, const c return test_case; } -static TestCase *add_simple_case_libc(const char *case_name, const char *source, const char *output) { - TestCase *tc = add_simple_case(case_name, source, output); - tc->compiler_args.append("--library"); - tc->compiler_args.append("c"); - return tc; -} - -static TestCase *add_compile_fail_case(const char *case_name, const char *source, size_t count, ...) { - va_list ap; - va_start(ap, count); - - TestCase *test_case = allocate(1); - test_case->case_name = case_name; - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_source_path; - test_case->source_files.at(0).source_code = source; - - for (size_t i = 0; i < count; i += 1) { - const char *arg = va_arg(ap, const char *); - test_case->compile_errors.append(arg); - } - - test_case->compiler_args.append("build_obj"); - test_case->compiler_args.append(tmp_source_path); - - test_case->compiler_args.append("--name"); - test_case->compiler_args.append("test"); - - test_case->compiler_args.append("--output"); - test_case->compiler_args.append(tmp_exe_path); - - test_case->compiler_args.append("--release"); - test_case->compiler_args.append("--strip"); - - test_cases.append(test_case); - - return test_case; -} - -static TestCase *add_compile_fail_case_exe(const char *case_name, const char *source, size_t count, ...) { - va_list ap; - va_start(ap, count); - - TestCase *test_case = allocate(1); - test_case->case_name = case_name; - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_source_path; - test_case->source_files.at(0).source_code = source; - - for (size_t i = 0; i < count; i += 1) { - const char *arg = va_arg(ap, const char *); - test_case->compile_errors.append(arg); - } - - test_case->compiler_args.append("build_exe"); - test_case->compiler_args.append(tmp_source_path); - - test_case->compiler_args.append("--name"); - test_case->compiler_args.append("test"); - - test_case->compiler_args.append("--output"); - test_case->compiler_args.append(tmp_exe_path); - - test_case->compiler_args.append("--release"); - test_case->compiler_args.append("--strip"); - - test_cases.append(test_case); - - 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; @@ -242,1617 +138,6 @@ static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warn va_end(ap); return test_case; } - -static TestCase *add_example_compile_extra(const char *root_source_file, bool libc) { - TestCase *test_case = allocate(1); - test_case->case_name = buf_ptr(buf_sprintf("build example %s", root_source_file)); - test_case->output = nullptr; - test_case->special = TestSpecialNone; - - test_case->compiler_args.append("build_exe"); - test_case->compiler_args.append(buf_ptr(buf_sprintf("../%s", root_source_file))); - - if (libc) { - test_case->compiler_args.append("--library"); - test_case->compiler_args.append("c"); - } - - test_cases.append(test_case); - - return test_case; -} - -static TestCase *add_example_compile(const char *root_source_file) { - return add_example_compile_extra(root_source_file, false); -} - -static TestCase *add_example_compile_libc(const char *root_source_file) { - return add_example_compile_extra(root_source_file, true); -} - -//////////////////////////////////////////////////////////////////////////////////// - - -//////////////////////////////////////////////////////////////////////////////////// - -static void add_compile_failure_test_cases(void) { - add_compile_fail_case("multiple function definitions", R"SOURCE( -fn a() {} -fn a() {} -export fn entry() { a(); } - )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of 'a'"); - - add_compile_fail_case("unreachable with return", R"SOURCE( -fn a() -> noreturn {return;} -export fn entry() { a(); } - )SOURCE", 1, ".tmp_source.zig:2:21: error: expected type 'noreturn', found 'void'"); - - add_compile_fail_case("control reaches end of non-void function", R"SOURCE( -fn a() -> i32 {} -export fn entry() { _ = a(); } - )SOURCE", 1, ".tmp_source.zig:2:15: error: expected type 'i32', found 'void'"); - - add_compile_fail_case("undefined function call", R"SOURCE( -export fn a() { - b(); -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'"); - - add_compile_fail_case("wrong number of arguments", R"SOURCE( -export fn a() { - b(1); -} -fn b(a: i32, b: i32, c: i32) { } - )SOURCE", 1, ".tmp_source.zig:3:6: error: expected 3 arguments, found 1"); - - add_compile_fail_case("invalid type", R"SOURCE( -fn a() -> bogus {} -export fn entry() { _ = a(); } - )SOURCE", 1, ".tmp_source.zig:2:11: error: use of undeclared identifier 'bogus'"); - - add_compile_fail_case("pointer to unreachable", R"SOURCE( -fn a() -> &noreturn {} -export fn entry() { _ = a(); } - )SOURCE", 1, ".tmp_source.zig:2:12: error: pointer to unreachable not allowed"); - - add_compile_fail_case("unreachable code", R"SOURCE( -export fn a() { - return; - b(); -} - -fn b() {} - )SOURCE", 1, ".tmp_source.zig:4:6: error: unreachable code"); - - add_compile_fail_case("bad import", R"SOURCE( -const bogus = @import("bogus-does-not-exist.zig"); -export fn entry() { bogus.bogo(); } - )SOURCE", 1, ".tmp_source.zig:2:15: error: unable to find 'bogus-does-not-exist.zig'"); - - add_compile_fail_case("undeclared identifier", R"SOURCE( -export fn a() { - b + - c -} - )SOURCE", 2, - ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'", - ".tmp_source.zig:4:5: error: use of undeclared identifier 'c'"); - - add_compile_fail_case("parameter redeclaration", R"SOURCE( -fn f(a : i32, a : i32) { -} -export fn entry() { f(1, 2); } - )SOURCE", 1, ".tmp_source.zig:2:15: error: redeclaration of variable 'a'"); - - add_compile_fail_case("local variable redeclaration", R"SOURCE( -export fn f() { - const a : i32 = 0; - const a = 0; -} - )SOURCE", 1, ".tmp_source.zig:4:5: error: redeclaration of variable 'a'"); - - add_compile_fail_case("local variable redeclares parameter", R"SOURCE( -fn f(a : i32) { - const a = 0; -} -export fn entry() { f(1); } - )SOURCE", 1, ".tmp_source.zig:3:5: error: redeclaration of variable 'a'"); - - add_compile_fail_case("variable has wrong type", R"SOURCE( -export fn f() -> i32 { - const a = c"a"; - a -} - )SOURCE", 1, ".tmp_source.zig:4:5: error: expected type 'i32', found '&const u8'"); - - add_compile_fail_case("if condition is bool, not int", R"SOURCE( -export fn f() { - if (0) {} -} - )SOURCE", 1, ".tmp_source.zig:3:9: error: integer value 0 cannot be implicitly casted to type 'bool'"); - - add_compile_fail_case("assign unreachable", R"SOURCE( -export fn f() { - const a = return; -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: unreachable code"); - - add_compile_fail_case("unreachable variable", R"SOURCE( -export fn f() { - const a: noreturn = {}; -} - )SOURCE", 1, ".tmp_source.zig:3:14: error: variable of type 'noreturn' not allowed"); - - add_compile_fail_case("unreachable parameter", R"SOURCE( -fn f(a: noreturn) {} -export fn entry() { f(); } - )SOURCE", 1, ".tmp_source.zig:2:9: error: parameter of type 'noreturn' not allowed"); - - add_compile_fail_case("bad assignment target", R"SOURCE( -export fn f() { - 3 = 3; -} - )SOURCE", 1, ".tmp_source.zig:3:7: error: cannot assign to constant"); - - add_compile_fail_case("assign to constant variable", R"SOURCE( -export fn f() { - const a = 3; - a = 4; -} - )SOURCE", 1, ".tmp_source.zig:4:7: error: cannot assign to constant"); - - add_compile_fail_case("use of undeclared identifier", R"SOURCE( -export fn f() { - b = 3; -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: use of undeclared identifier 'b'"); - - add_compile_fail_case("const is a statement, not an expression", R"SOURCE( -export fn f() { - (const a = 0); -} - )SOURCE", 1, ".tmp_source.zig:3:6: error: invalid token: 'const'"); - - add_compile_fail_case("array access of undeclared identifier", R"SOURCE( -export fn f() { - i[i] = i[i]; -} - )SOURCE", 2, ".tmp_source.zig:3:5: error: use of undeclared identifier 'i'", - ".tmp_source.zig:3:12: error: use of undeclared identifier 'i'"); - - add_compile_fail_case("array access of non array", R"SOURCE( -export fn f() { - var bad : bool = undefined; - bad[bad] = bad[bad]; -} - )SOURCE", 2, ".tmp_source.zig:4:8: error: array access of non-array type 'bool'", - ".tmp_source.zig:4:19: error: array access of non-array type 'bool'"); - - add_compile_fail_case("array access with non integer index", R"SOURCE( -export fn f() { - var array = "aoeu"; - var bad = false; - array[bad] = array[bad]; -} - )SOURCE", 2, ".tmp_source.zig:5:11: error: expected type 'usize', found 'bool'", - ".tmp_source.zig:5:24: error: expected type 'usize', found 'bool'"); - - add_compile_fail_case("write to const global variable", R"SOURCE( -const x : i32 = 99; -fn f() { - x = 1; -} -export fn entry() { f(); } - )SOURCE", 1, ".tmp_source.zig:4:7: error: cannot assign to constant"); - - - add_compile_fail_case("missing else clause", R"SOURCE( -fn f(b: bool) { - const x : i32 = if (b) { 1 }; - const y = if (b) { i32(1) }; -} -export fn entry() { f(true); } - )SOURCE", 2, ".tmp_source.zig:3:30: error: integer value 1 cannot be implicitly casted to type 'void'", - ".tmp_source.zig:4:15: error: incompatible types: 'i32' and 'void'"); - - add_compile_fail_case("direct struct loop", R"SOURCE( -const A = struct { a : A, }; -export fn entry() -> usize { @sizeOf(A) } - )SOURCE", 1, ".tmp_source.zig:2:11: error: struct 'A' contains itself"); - - add_compile_fail_case("indirect struct loop", R"SOURCE( -const A = struct { b : B, }; -const B = struct { c : C, }; -const C = struct { a : A, }; -export fn entry() -> usize { @sizeOf(A) } - )SOURCE", 1, ".tmp_source.zig:2:11: error: struct 'A' contains itself"); - - add_compile_fail_case("invalid struct field", R"SOURCE( -const A = struct { x : i32, }; -export fn f() { - var a : A = undefined; - a.foo = 1; - const y = a.bar; -} - )SOURCE", 2, - ".tmp_source.zig:5:6: error: no member named 'foo' in 'A'", - ".tmp_source.zig:6:16: error: no member named 'bar' in 'A'"); - - add_compile_fail_case("redefinition of struct", R"SOURCE( -const A = struct { x : i32, }; -const A = struct { y : i32, }; - )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of 'A'"); - - add_compile_fail_case("redefinition of enums", R"SOURCE( -const A = enum {}; -const A = enum {}; - )SOURCE", 1, ".tmp_source.zig:3:1: error: redefinition of 'A'"); - - add_compile_fail_case("redefinition of global variables", R"SOURCE( -var a : i32 = 1; -var a : i32 = 2; - )SOURCE", 2, - ".tmp_source.zig:3:1: error: redefinition of 'a'", - ".tmp_source.zig:2:1: note: previous definition is here"); - - add_compile_fail_case("byvalue struct parameter in exported function", R"SOURCE( -const A = struct { x : i32, }; -export fn f(a : A) {} - )SOURCE", 1, ".tmp_source.zig:3:13: error: byvalue types not yet supported on extern function parameters"); - - add_compile_fail_case("byvalue struct return value in exported function", R"SOURCE( -const A = struct { x: i32, }; -export fn f() -> A { - A {.x = 1234 } -} - )SOURCE", 1, ".tmp_source.zig:3:18: error: byvalue types not yet supported on extern function return values"); - - add_compile_fail_case("duplicate field in struct value expression", R"SOURCE( -const A = struct { - x : i32, - y : i32, - z : i32, -}; -export fn f() { - const a = A { - .z = 1, - .y = 2, - .x = 3, - .z = 4, - }; -} - )SOURCE", 1, ".tmp_source.zig:12:9: error: duplicate field"); - - add_compile_fail_case("missing field in struct value expression", R"SOURCE( -const A = struct { - x : i32, - y : i32, - z : i32, -}; -export fn f() { - // we want the error on the '{' not the 'A' because - // the A could be a complicated expression - const a = A { - .z = 4, - .y = 2, - }; -} - )SOURCE", 1, ".tmp_source.zig:10:17: error: missing field: 'x'"); - - add_compile_fail_case("invalid field in struct value expression", R"SOURCE( -const A = struct { - x : i32, - y : i32, - z : i32, -}; -export fn f() { - const a = A { - .z = 4, - .y = 2, - .foo = 42, - }; -} - )SOURCE", 1, ".tmp_source.zig:11:9: error: no member named 'foo' in 'A'"); - - add_compile_fail_case("invalid break expression", R"SOURCE( -export fn f() { - break; -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: 'break' expression outside loop"); - - add_compile_fail_case("invalid continue expression", R"SOURCE( -export fn f() { - continue; -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: 'continue' expression outside loop"); - - add_compile_fail_case("invalid maybe type", R"SOURCE( -export fn f() { - if (const x ?= true) { } -} - )SOURCE", 1, ".tmp_source.zig:3:20: error: expected nullable type, found 'bool'"); - - add_compile_fail_case("cast unreachable", R"SOURCE( -fn f() -> i32 { - i32(return 1) -} -export fn entry() { _ = f(); } - )SOURCE", 1, ".tmp_source.zig:3:8: error: unreachable code"); - - add_compile_fail_case("invalid builtin fn", R"SOURCE( -fn f() -> @bogus(foo) { -} -export fn entry() { _ = f(); } - )SOURCE", 1, ".tmp_source.zig:2:11: error: invalid builtin function: 'bogus'"); - - add_compile_fail_case("top level decl dependency loop", R"SOURCE( -const a : @typeOf(b) = 0; -const b : @typeOf(a) = 0; -export fn entry() { - const c = a + b; -} - )SOURCE", 1, ".tmp_source.zig:2:1: error: 'a' depends on itself"); - - add_compile_fail_case("noalias on non pointer param", R"SOURCE( -fn f(noalias x: i32) {} -export fn entry() { f(1234); } - )SOURCE", 1, ".tmp_source.zig:2:6: error: noalias on non-pointer parameter"); - - add_compile_fail_case("struct init syntax for array", R"SOURCE( -const foo = []u16{.x = 1024,}; -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:2:18: error: type '[]u16' does not support struct initialization syntax"); - - add_compile_fail_case("type variables must be constant", R"SOURCE( -var foo = u8; -export fn entry() -> foo { - return 1; -} - )SOURCE", 1, ".tmp_source.zig:2:1: error: variable of type 'type' must be constant"); - - - add_compile_fail_case("variables shadowing types", R"SOURCE( -const Foo = struct {}; -const Bar = struct {}; - -fn f(Foo: i32) { - var Bar : i32 = undefined; -} - -export fn entry() { - f(1234); -} - )SOURCE", 4, - ".tmp_source.zig:5:6: error: redefinition of 'Foo'", - ".tmp_source.zig:2:1: note: previous definition is here", - ".tmp_source.zig:6:5: error: redefinition of 'Bar'", - ".tmp_source.zig:3:1: note: previous definition is here"); - - add_compile_fail_case("multiple else prongs in a switch", R"SOURCE( -fn f(x: u32) { - const value: bool = switch (x) { - 1234 => false, - else => true, - else => true, - }; -} -export fn entry() { - f(1234); -} - )SOURCE", 1, ".tmp_source.zig:6:9: error: multiple else prongs in switch expression"); - - add_compile_fail_case("global variable initializer must be constant expression", R"SOURCE( -extern fn foo() -> i32; -const x = foo(); -export fn entry() -> i32 { x } - )SOURCE", 1, ".tmp_source.zig:3:11: error: unable to evaluate constant expression"); - - add_compile_fail_case("array concatenation with wrong type", R"SOURCE( -const src = "aoeu"; -const derp = usize(1234); -const a = derp ++ "foo"; - -export fn entry() -> usize { @sizeOf(@typeOf(a)) } - )SOURCE", 1, ".tmp_source.zig:4:11: error: expected array or C string literal, found 'usize'"); - - add_compile_fail_case("non compile time array concatenation", R"SOURCE( -fn f() -> []u8 { - s ++ "foo" -} -var s: [10]u8 = undefined; -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to evaluate constant expression"); - - add_compile_fail_case("@cImport with bogus include", R"SOURCE( -const c = @cImport(@cInclude("bogus.h")); -export fn entry() -> usize { @sizeOf(@typeOf(c.bogo)) } - )SOURCE", 2, ".tmp_source.zig:2:11: error: C import failed", - ".h:1:10: note: 'bogus.h' file not found"); - - add_compile_fail_case("address of number literal", R"SOURCE( -const x = 3; -const y = &x; -fn foo() -> &const i32 { y } -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:4:26: error: expected type '&const i32', found '&const (integer literal)'"); - - add_compile_fail_case("integer overflow error", R"SOURCE( -const x : u8 = 300; -export fn entry() -> usize { @sizeOf(@typeOf(x)) } - )SOURCE", 1, ".tmp_source.zig:2:16: error: integer value 300 cannot be implicitly casted to type 'u8'"); - - add_compile_fail_case("incompatible number literals", R"SOURCE( -const x = 2 == 2.0; -export fn entry() -> usize { @sizeOf(@typeOf(x)) } - )SOURCE", 1, ".tmp_source.zig:2:11: error: integer value 2 cannot be implicitly casted to type '(float literal)'"); - - add_compile_fail_case("missing function call param", R"SOURCE( -const Foo = struct { - a: i32, - b: i32, - - fn member_a(foo: &const Foo) -> i32 { - return foo.a; - } - fn member_b(foo: &const Foo) -> i32 { - return foo.b; - } -}; - -const member_fn_type = @typeOf(Foo.member_a); -const members = []member_fn_type { - Foo.member_a, - Foo.member_b, -}; - -fn f(foo: &const Foo, index: usize) { - const result = members[index](); -} - -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:21:34: error: expected 1 arguments, found 0"); - - add_compile_fail_case("missing function name and param name", R"SOURCE( -fn () {} -fn f(i32) {} -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 2, - ".tmp_source.zig:2:1: error: missing function name", - ".tmp_source.zig:3:6: error: missing parameter name"); - - add_compile_fail_case("wrong function type", R"SOURCE( -const fns = []fn(){ a, b, c }; -fn a() -> i32 {0} -fn b() -> i32 {1} -fn c() -> i32 {2} -export fn entry() -> usize { @sizeOf(@typeOf(fns)) } - )SOURCE", 1, ".tmp_source.zig:2:21: error: expected type 'fn()', found 'fn() -> i32'"); - - add_compile_fail_case("extern function pointer mismatch", R"SOURCE( -const fns = [](fn(i32)->i32){ a, b, c }; -pub fn a(x: i32) -> i32 {x + 0} -pub fn b(x: i32) -> i32 {x + 1} -export fn c(x: i32) -> i32 {x + 2} - -export fn entry() -> usize { @sizeOf(@typeOf(fns)) } - )SOURCE", 1, ".tmp_source.zig:2:37: error: expected type 'fn(i32) -> i32', found 'extern fn(i32) -> i32'"); - - - add_compile_fail_case("implicit cast from f64 to f32", R"SOURCE( -const x : f64 = 1.0; -const y : f32 = x; - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 1, ".tmp_source.zig:3:17: error: expected type 'f32', found 'f64'"); - - - add_compile_fail_case("colliding invalid top level functions", R"SOURCE( -fn func() -> bogus {} -fn func() -> bogus {} -export fn entry() -> usize { @sizeOf(@typeOf(func)) } - )SOURCE", 2, - ".tmp_source.zig:3:1: error: redefinition of 'func'", - ".tmp_source.zig:2:14: error: use of undeclared identifier 'bogus'"); - - - add_compile_fail_case("bogus compile var", R"SOURCE( -const x = @compileVar("bogus"); -export fn entry() -> usize { @sizeOf(@typeOf(x)) } - )SOURCE", 1, ".tmp_source.zig:2:23: error: unrecognized compile variable: 'bogus'"); - - - add_compile_fail_case("non constant expression in array size outside function", R"SOURCE( -const Foo = struct { - y: [get()]u8, -}; -var global_var: usize = 1; -fn get() -> usize { global_var } - -export fn entry() -> usize { @sizeOf(@typeOf(Foo)) } - )SOURCE", 3, - ".tmp_source.zig:6:21: error: unable to evaluate constant expression", - ".tmp_source.zig:3:12: note: called from here", - ".tmp_source.zig:3:8: note: called from here"); - - - add_compile_fail_case("addition with non numbers", R"SOURCE( -const Foo = struct { - field: i32, -}; -const x = Foo {.field = 1} + Foo {.field = 2}; - -export fn entry() -> usize { @sizeOf(@typeOf(x)) } - )SOURCE", 1, ".tmp_source.zig:5:28: error: invalid operands to binary expression: 'Foo' and 'Foo'"); - - - add_compile_fail_case("division by zero", R"SOURCE( -const lit_int_x = 1 / 0; -const lit_float_x = 1.0 / 0.0; -const int_x = i32(1) / i32(0); -const float_x = f32(1.0) / f32(0.0); - -export fn entry1() -> usize { @sizeOf(@typeOf(lit_int_x)) } -export fn entry2() -> usize { @sizeOf(@typeOf(lit_float_x)) } -export fn entry3() -> usize { @sizeOf(@typeOf(int_x)) } -export fn entry4() -> usize { @sizeOf(@typeOf(float_x)) } - )SOURCE", 4, - ".tmp_source.zig:2:21: error: division by zero is undefined", - ".tmp_source.zig:3:25: error: division by zero is undefined", - ".tmp_source.zig:4:22: error: division by zero is undefined", - ".tmp_source.zig:5:26: error: division by zero is undefined"); - - - add_compile_fail_case("missing switch prong", R"SOURCE( -const Number = enum { - One, - Two, - Three, - Four, -}; -fn f(n: Number) -> i32 { - switch (n) { - Number.One => 1, - Number.Two => 2, - Number.Three => i32(3), - } -} - -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:9:5: error: enumeration value 'Number.Four' not handled in switch"); - - add_compile_fail_case("normal string with newline", R"SOURCE( -const foo = "a -b"; - -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:2:13: error: newline not allowed in string literal"); - - add_compile_fail_case("invalid comparison for function pointers", R"SOURCE( -fn foo() {} -const invalid = foo > foo; - -export fn entry() -> usize { @sizeOf(@typeOf(invalid)) } - )SOURCE", 1, ".tmp_source.zig:3:21: error: operator not allowed for type 'fn()'"); - - add_compile_fail_case("generic function instance with non-constant expression", R"SOURCE( -fn foo(comptime x: i32, y: i32) -> i32 { return x + y; } -fn test1(a: i32, b: i32) -> i32 { - return foo(a, b); -} - -export fn entry() -> usize { @sizeOf(@typeOf(test1)) } - )SOURCE", 1, ".tmp_source.zig:4:16: error: unable to evaluate constant expression"); - - add_compile_fail_case("goto jumping into block", R"SOURCE( -export fn f() { - { -a_label: - } - goto a_label; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: no label in scope named 'a_label'"); - - add_compile_fail_case("goto jumping past a defer", R"SOURCE( -fn f(b: bool) { - if (b) goto label; - defer derp(); -label: -} -fn derp(){} - -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:3:12: error: no label in scope named 'label'"); - - add_compile_fail_case("assign null to non-nullable pointer", R"SOURCE( -const a: &u8 = null; - -export fn entry() -> usize { @sizeOf(@typeOf(a)) } - )SOURCE", 1, ".tmp_source.zig:2:16: error: expected type '&u8', found '(null)'"); - - add_compile_fail_case("indexing an array of size zero", R"SOURCE( -const array = []u8{}; -export fn foo() { - const pointer = &array[0]; -} - )SOURCE", 1, ".tmp_source.zig:4:27: error: index 0 outside array of size 0"); - - add_compile_fail_case("compile time division by zero", R"SOURCE( -const y = foo(0); -fn foo(x: i32) -> i32 { - 1 / x -} - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 2, - ".tmp_source.zig:4:7: error: division by zero is undefined", - ".tmp_source.zig:2:14: note: called from here"); - - add_compile_fail_case("branch on undefined value", R"SOURCE( -const x = if (undefined) true else false; - -export fn entry() -> usize { @sizeOf(@typeOf(x)) } - )SOURCE", 1, ".tmp_source.zig:2:15: error: use of undefined value"); - - - add_compile_fail_case("endless loop in function evaluation", R"SOURCE( -const seventh_fib_number = fibbonaci(7); -fn fibbonaci(x: i32) -> i32 { - return fibbonaci(x - 1) + fibbonaci(x - 2); -} - -export fn entry() -> usize { @sizeOf(@typeOf(seventh_fib_number)) } - )SOURCE", 2, - ".tmp_source.zig:4:21: error: evaluation exceeded 1000 backwards branches", - ".tmp_source.zig:4:21: note: called from here"); - - add_compile_fail_case("@embedFile with bogus file", R"SOURCE( -const resource = @embedFile("bogus.txt"); - -export fn entry() -> usize { @sizeOf(@typeOf(resource)) } - )SOURCE", 2, ".tmp_source.zig:2:29: error: unable to find '", "/bogus.txt'"); - - add_compile_fail_case("non-const expression in struct literal outside function", R"SOURCE( -const Foo = struct { - x: i32, -}; -const a = Foo {.x = get_it()}; -extern fn get_it() -> i32; - -export fn entry() -> usize { @sizeOf(@typeOf(a)) } - )SOURCE", 1, ".tmp_source.zig:5:21: error: unable to evaluate constant expression"); - - add_compile_fail_case("non-const expression function call with struct return value outside function", R"SOURCE( -const Foo = struct { - x: i32, -}; -const a = get_it(); -fn get_it() -> Foo { - global_side_effect = true; - Foo {.x = 13} -} -var global_side_effect = false; - -export fn entry() -> usize { @sizeOf(@typeOf(a)) } - )SOURCE", 2, - ".tmp_source.zig:7:24: error: unable to evaluate constant expression", - ".tmp_source.zig:5:17: note: called from here"); - - add_compile_fail_case("undeclared identifier error should mark fn as impure", R"SOURCE( -export fn foo() { - test_a_thing(); -} -fn test_a_thing() { - bad_fn_call(); -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: use of undeclared identifier 'bad_fn_call'"); - - add_compile_fail_case("illegal comparison of types", R"SOURCE( -fn bad_eql_1(a: []u8, b: []u8) -> bool { - a == b -} -const EnumWithData = enum { - One, - Two: i32, -}; -fn bad_eql_2(a: &const EnumWithData, b: &const EnumWithData) -> bool { - *a == *b -} - -export fn entry1() -> usize { @sizeOf(@typeOf(bad_eql_1)) } -export fn entry2() -> usize { @sizeOf(@typeOf(bad_eql_2)) } - )SOURCE", 2, - ".tmp_source.zig:3:7: error: operator not allowed for type '[]u8'", - ".tmp_source.zig:10:8: error: operator not allowed for type 'EnumWithData'"); - - add_compile_fail_case("non-const switch number literal", R"SOURCE( -export fn foo() { - const x = switch (bar()) { - 1, 2 => 1, - 3, 4 => 2, - else => 3, - }; -} -fn bar() -> i32 { - 2 -} - )SOURCE", 1, ".tmp_source.zig:3:15: error: unable to infer expression type"); - - add_compile_fail_case("atomic orderings of cmpxchg - failure stricter than success", R"SOURCE( -export fn f() { - var x: i32 = 1234; - while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Monotonic, AtomicOrder.SeqCst)) {} -} - )SOURCE", 1, ".tmp_source.zig:4:72: error: failure atomic ordering must be no stricter than success"); - - add_compile_fail_case("atomic orderings of cmpxchg - success Monotonic or stricter", R"SOURCE( -export fn f() { - var x: i32 = 1234; - while (!@cmpxchg(&x, 1234, 5678, AtomicOrder.Unordered, AtomicOrder.Unordered)) {} -} - )SOURCE", 1, ".tmp_source.zig:4:49: error: success atomic ordering must be Monotonic or stricter"); - - add_compile_fail_case("negation overflow in function evaluation", R"SOURCE( -const y = neg(-128); -fn neg(x: i8) -> i8 { - -x -} - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 2, - ".tmp_source.zig:4:5: error: negation caused overflow", - ".tmp_source.zig:2:14: note: called from here"); - - add_compile_fail_case("add overflow in function evaluation", R"SOURCE( -const y = add(65530, 10); -fn add(a: u16, b: u16) -> u16 { - a + b -} - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 2, - ".tmp_source.zig:4:7: error: operation caused overflow", - ".tmp_source.zig:2:14: note: called from here"); - - - add_compile_fail_case("sub overflow in function evaluation", R"SOURCE( -const y = sub(10, 20); -fn sub(a: u16, b: u16) -> u16 { - a - b -} - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 2, - ".tmp_source.zig:4:7: error: operation caused overflow", - ".tmp_source.zig:2:14: note: called from here"); - - add_compile_fail_case("mul overflow in function evaluation", R"SOURCE( -const y = mul(300, 6000); -fn mul(a: u16, b: u16) -> u16 { - a * b -} - -export fn entry() -> usize { @sizeOf(@typeOf(y)) } - )SOURCE", 2, - ".tmp_source.zig:4:7: error: operation caused overflow", - ".tmp_source.zig:2:14: note: called from here"); - - add_compile_fail_case("truncate sign mismatch", R"SOURCE( -fn f() -> i8 { - const x: u32 = 10; - @truncate(i8, x) -} - -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:4:19: error: expected signed integer type, found 'u32'"); - - add_compile_fail_case("%return in function with non error return type", R"SOURCE( -export fn f() { - %return something(); -} -fn something() -> %void { } - )SOURCE", 1, - ".tmp_source.zig:3:5: error: expected type 'void', found 'error'"); - - add_compile_fail_case("wrong return type for main", R"SOURCE( -pub fn main() { } - )SOURCE", 1, ".tmp_source.zig:2:15: error: expected return type of main to be '%void', instead is 'void'"); - - add_compile_fail_case("double ?? on main return value", R"SOURCE( -pub fn main() -> ??void { -} - )SOURCE", 1, ".tmp_source.zig:2:18: error: expected return type of main to be '%void', instead is '??void'"); - - add_compile_fail_case("invalid pointer for var type", R"SOURCE( -extern fn ext() -> usize; -var bytes: [ext()]u8 = undefined; -export fn f() { - for (bytes) |*b, i| { - *b = u8(i); - } -} - )SOURCE", 1, ".tmp_source.zig:3:13: error: unable to evaluate constant expression"); - - add_compile_fail_case("export function with comptime parameter", R"SOURCE( -export fn foo(comptime x: i32, y: i32) -> i32{ - x + y -} - )SOURCE", 1, ".tmp_source.zig:2:15: error: comptime parameter not allowed in extern function"); - - add_compile_fail_case("extern function with comptime parameter", R"SOURCE( -extern fn foo(comptime x: i32, y: i32) -> i32; -fn f() -> i32 { - foo(1, 2) -} -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:2:15: error: comptime parameter not allowed in extern function"); - - add_compile_fail_case("convert fixed size array to slice with invalid size", R"SOURCE( -export fn f() { - var array: [5]u8 = undefined; - var foo = ([]const u32)(array)[0]; -} - )SOURCE", 1, ".tmp_source.zig:4:28: error: unable to convert [5]u8 to []const u32: size mismatch"); - - add_compile_fail_case("non-pure function returns type", R"SOURCE( -var a: u32 = 0; -pub fn List(comptime T: type) -> type { - a += 1; - SmallList(T, 8) -} - -pub fn SmallList(comptime T: type, comptime STATIC_SIZE: usize) -> type { - struct { - items: []T, - length: usize, - prealloc_items: [STATIC_SIZE]T, - } -} - -export fn function_with_return_type_type() { - var list: List(i32) = undefined; - list.length = 10; -} - - )SOURCE", 2, - ".tmp_source.zig:4:7: error: unable to evaluate constant expression", - ".tmp_source.zig:17:19: note: called from here"); - - add_compile_fail_case("bogus method call on slice", R"SOURCE( -var self = "aoeu"; -fn f(m: []const u8) { - m.copy(u8, self[0...], m); -} -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:4:6: error: no member named 'copy' in '[]const u8'"); - - add_compile_fail_case("wrong number of arguments for method fn call", R"SOURCE( -const Foo = struct { - fn method(self: &const Foo, a: i32) {} -}; -fn f(foo: &const Foo) { - - foo.method(1, 2); -} -export fn entry() -> usize { @sizeOf(@typeOf(f)) } - )SOURCE", 1, ".tmp_source.zig:7:15: error: expected 2 arguments, found 3"); - - add_compile_fail_case("assign through constant pointer", R"SOURCE( -export fn f() { - var cstr = c"Hat"; - cstr[0] = 'W'; -} - )SOURCE", 1, ".tmp_source.zig:4:11: error: cannot assign to constant"); - - add_compile_fail_case("assign through constant slice", R"SOURCE( -export fn f() { - var cstr: []const u8 = "Hat"; - cstr[0] = 'W'; -} - )SOURCE", 1, ".tmp_source.zig:4:11: error: cannot assign to constant"); - - add_compile_fail_case("main function with bogus args type", R"SOURCE( -pub fn main(args: [][]bogus) -> %void {} - )SOURCE", 1, ".tmp_source.zig:2:23: error: use of undeclared identifier 'bogus'"); - - add_compile_fail_case("for loop missing element param", R"SOURCE( -fn foo(blah: []u8) { - for (blah) { } -} -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:3:5: error: for loop expression missing element parameter"); - - add_compile_fail_case("misspelled type with pointer only reference", R"SOURCE( -const JasonHM = u8; -const JasonList = &JsonNode; - -const JsonOA = enum { - JSONArray: JsonList, - JSONObject: JasonHM, -}; - -const JsonType = enum { - JSONNull: void, - JSONInteger: isize, - JSONDouble: f64, - JSONBool: bool, - JSONString: []u8, - JSONArray, - JSONObject, -}; - -pub const JsonNode = struct { - kind: JsonType, - jobject: ?JsonOA, -}; - -fn foo() { - var jll: JasonList = undefined; - jll.init(1234); - var jd = JsonNode {.kind = JsonType.JSONArray , .jobject = JsonOA.JSONArray {jll} }; -} - -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:6:16: error: use of undeclared identifier 'JsonList'"); - - add_compile_fail_case("method call with first arg type primitive", R"SOURCE( -const Foo = struct { - x: i32, - - fn init(x: i32) -> Foo { - Foo { - .x = x, - } - } -}; - -export fn f() { - const derp = Foo.init(3); - - derp.init(); -} - )SOURCE", 1, ".tmp_source.zig:15:5: error: expected type 'i32', found '&const Foo'"); - - add_compile_fail_case("method call with first arg type wrong container", R"SOURCE( -pub const List = struct { - len: usize, - allocator: &Allocator, - - pub fn init(allocator: &Allocator) -> List { - List { - .len = 0, - .allocator = allocator, - } - } -}; - -pub var global_allocator = Allocator { - .field = 1234, -}; - -pub const Allocator = struct { - field: i32, -}; - -export fn foo() { - var x = List.init(&global_allocator); - x.init(); -} - )SOURCE", 1, ".tmp_source.zig:24:5: error: expected type '&Allocator', found '&List'"); - - add_compile_fail_case("binary not on number literal", R"SOURCE( -const TINY_QUANTUM_SHIFT = 4; -const TINY_QUANTUM_SIZE = 1 << TINY_QUANTUM_SHIFT; -var block_aligned_stuff: usize = (4 + TINY_QUANTUM_SIZE) & ~(TINY_QUANTUM_SIZE - 1); - -export fn entry() -> usize { @sizeOf(@typeOf(block_aligned_stuff)) } - )SOURCE", 1, ".tmp_source.zig:4:60: error: unable to perform binary not operation on type '(integer literal)'"); - - { - TestCase *tc = add_compile_fail_case("multiple files with private function error", R"SOURCE( -const foo = @import("foo.zig"); - -export fn callPrivFunction() { - foo.privateFunction(); -} - )SOURCE", 2, - ".tmp_source.zig:5:8: error: 'privateFunction' is private", - "foo.zig:2:1: note: declared here"); - - add_source_file(tc, "foo.zig", R"SOURCE( -fn privateFunction() { } - )SOURCE"); - } - - add_compile_fail_case("container init with non-type", R"SOURCE( -const zero: i32 = 0; -const a = zero{1}; - -export fn entry() -> usize { @sizeOf(@typeOf(a)) } - )SOURCE", 1, ".tmp_source.zig:3:11: error: expected type, found 'i32'"); - - add_compile_fail_case("assign to constant field", R"SOURCE( -const Foo = struct { - field: i32, -}; -export fn derp() { - const f = Foo {.field = 1234,}; - f.field = 0; -} - )SOURCE", 1, ".tmp_source.zig:7:13: error: cannot assign to constant"); - - add_compile_fail_case("return from defer expression", R"SOURCE( -pub fn testTrickyDefer() -> %void { - defer canFail() %% {}; - - defer %return canFail(); - - const a = maybeInt() ?? return; -} - -fn canFail() -> %void { } - -pub fn maybeInt() -> ?i32 { - return 0; -} - -export fn entry() -> usize { @sizeOf(@typeOf(testTrickyDefer)) } - )SOURCE", 1, ".tmp_source.zig:5:11: error: cannot return from defer expression"); - - add_compile_fail_case("attempt to access var args out of bounds", R"SOURCE( -fn add(args: ...) -> i32 { - args[0] + args[1] -} - -fn foo() -> i32 { - add(i32(1234)) -} - -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 2, - ".tmp_source.zig:3:19: error: index 1 outside argument list of size 1", - ".tmp_source.zig:7:8: note: called from here"); - - add_compile_fail_case("pass integer literal to var args", R"SOURCE( -fn add(args: ...) -> i32 { - var sum = i32(0); - {comptime var i: usize = 0; inline while (i < args.len; i += 1) { - sum += args[i]; - }} - return sum; -} - -fn bar() -> i32 { - add(1, 2, 3, 4) -} - -export fn entry() -> usize { @sizeOf(@typeOf(bar)) } - )SOURCE", 1, ".tmp_source.zig:11:9: error: parameter of type '(integer literal)' requires comptime"); - - add_compile_fail_case("assign too big number to u16", R"SOURCE( -export fn foo() { - var vga_mem: u16 = 0xB8000; -} - )SOURCE", 1, ".tmp_source.zig:3:24: error: integer value 753664 cannot be implicitly casted to type 'u16'"); - - add_compile_fail_case("set global variable alignment to non power of 2", R"SOURCE( -const some_data: [100]u8 = { - @setGlobalAlign(some_data, 3); - undefined -}; -export fn entry() -> usize { @sizeOf(@typeOf(some_data)) } - )SOURCE", 1, ".tmp_source.zig:3:32: error: alignment value must be power of 2"); - - add_compile_fail_case("compile log", R"SOURCE( -export fn foo() { - comptime bar(12, "hi"); -} -fn bar(a: i32, b: []const u8) { - @compileLog("begin"); - @compileLog("a", a, "b", b); - @compileLog("end"); -} - )SOURCE", 6, - ".tmp_source.zig:6:5: error: found compile log statement", - ".tmp_source.zig:3:17: note: called from here", - ".tmp_source.zig:7:5: error: found compile log statement", - ".tmp_source.zig:3:17: note: called from here", - ".tmp_source.zig:8:5: error: found compile log statement", - ".tmp_source.zig:3:17: note: called from here"); - - add_compile_fail_case("casting bit offset pointer to regular pointer", R"SOURCE( -const u2 = @IntType(false, 2); -const u3 = @IntType(false, 3); - -const BitField = packed struct { - a: u3, - b: u3, - c: u2, -}; - -fn foo(bit_field: &const BitField) -> u3 { - return bar(&bit_field.b); -} - -fn bar(x: &const u3) -> u3 { - return *x; -} - -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:12:26: error: expected type '&const u3', found '&:3:6 const u3'"); - - add_compile_fail_case("referring to a struct that is invalid", R"SOURCE( -const UsbDeviceRequest = struct { - Type: u8, -}; - -export fn foo() { - comptime assert(@sizeOf(UsbDeviceRequest) == 0x8); -} - -fn assert(ok: bool) { - if (!ok) unreachable; -} - )SOURCE", 2, - ".tmp_source.zig:11:14: error: unable to evaluate constant expression", - ".tmp_source.zig:7:20: note: called from here"); - - add_compile_fail_case("control flow uses comptime var at runtime", R"SOURCE( -export fn foo() { - comptime var i = 0; - while (i < 5; i += 1) { - bar(); - } -} - -fn bar() { } - )SOURCE", 2, - ".tmp_source.zig:4:5: error: control flow attempts to use compile-time variable at runtime", - ".tmp_source.zig:4:21: note: compile-time variable assigned here"); - - add_compile_fail_case("ignored return value", R"SOURCE( -export fn foo() { - bar(); -} -fn bar() -> i32 { 0 } - )SOURCE", 1, ".tmp_source.zig:3:8: error: return value ignored"); - - add_compile_fail_case("integer literal on a non-comptime var", R"SOURCE( -export fn foo() { - var i = 0; - while (i < 10; i += 1) { } -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to infer variable type"); - - add_compile_fail_case("undefined literal on a non-comptime var", R"SOURCE( -export fn foo() { - var i = undefined; - i = i32(1); -} - )SOURCE", 1, ".tmp_source.zig:3:5: error: unable to infer variable type"); - - add_compile_fail_case("dereference an array", R"SOURCE( -var s_buffer: [10]u8 = undefined; -pub fn pass(in: []u8) -> []u8 { - var out = &s_buffer; - *out[0] = in[0]; - return (*out)[0...1]; -} - -export fn entry() -> usize { @sizeOf(@typeOf(pass)) } - )SOURCE", 1, ".tmp_source.zig:5:5: error: attempt to dereference non pointer type '[10]u8'"); - - add_compile_fail_case("pass const ptr to mutable ptr fn", R"SOURCE( -fn foo() -> bool { - const a = ([]const u8)("a"); - const b = &a; - return ptrEql(b, b); -} -fn ptrEql(a: &[]const u8, b: &[]const u8) -> bool { - return true; -} - -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:5:19: error: expected type '&[]const u8', found '&const []const u8'"); - - { - TestCase *tc = add_compile_fail_case("export collision", R"SOURCE( -const foo = @import("foo.zig"); - -export fn bar() -> usize { - return foo.baz; -} - )SOURCE", 2, - "foo.zig:2:8: error: exported symbol collision: 'bar'", - ".tmp_source.zig:4:8: note: other symbol is here"); - - add_source_file(tc, "foo.zig", R"SOURCE( -export fn bar() {} -pub const baz = 1234; - )SOURCE"); - } - - add_compile_fail_case("pass non-copyable type by value to function", R"SOURCE( -const Point = struct { x: i32, y: i32, }; -fn foo(p: Point) { } -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:3:11: error: type 'Point' is not copyable; cannot pass by value"); - - add_compile_fail_case("implicit cast from array to mutable slice", R"SOURCE( -var global_array: [10]i32 = undefined; -fn foo(param: []i32) {} -export fn entry() { - foo(global_array); -} - )SOURCE", 1, ".tmp_source.zig:5:9: error: expected type '[]i32', found '[10]i32'"); - - add_compile_fail_case("ptrcast to non-pointer", R"SOURCE( -export fn entry(a: &i32) -> usize { - return @ptrcast(usize, a); -} - )SOURCE", 1, ".tmp_source.zig:3:21: error: expected pointer, found 'usize'"); - - add_compile_fail_case("too many error values to cast to small integer", R"SOURCE( -error A; error B; error C; error D; error E; error F; error G; error H; -const u2 = @IntType(false, 2); -fn foo(e: error) -> u2 { - return u2(e); -} -export fn entry() -> usize { @sizeOf(@typeOf(foo)) } - )SOURCE", 1, ".tmp_source.zig:5:14: error: too many error values to fit in 'u2'"); - - add_compile_fail_case("asm at compile time", R"SOURCE( -comptime { - doSomeAsm(); -} - -fn doSomeAsm() { - asm volatile ( - \\.globl aoeu; - \\.type aoeu, @function; - \\.set aoeu, derp; - ); -} - )SOURCE", 1, ".tmp_source.zig:7:5: error: unable to evaluate constant expression"); - - add_compile_fail_case("invalid member of builtin enum", R"SOURCE( -export fn entry() { - const foo = Arch.x86; -} - )SOURCE", 1, ".tmp_source.zig:3:21: error: container 'Arch' has no member called 'x86'"); - - add_compile_fail_case("int to ptr of 0 bits", R"SOURCE( -export fn foo() { - var x: usize = 0x1000; - var y: &void = @intToPtr(&void, x); -} - )SOURCE", 1, ".tmp_source.zig:4:31: error: type '&void' has 0 bits and cannot store information"); - - add_compile_fail_case("@fieldParentPtr - non struct", R"SOURCE( -const Foo = i32; -export fn foo(a: &i32) -> &Foo { - return @fieldParentPtr(Foo, "a", a); -} - )SOURCE", 1, ".tmp_source.zig:4:28: error: expected struct type, found 'i32'"); - - add_compile_fail_case("@fieldParentPtr - bad field name", R"SOURCE( -const Foo = struct { - derp: i32, -}; -export fn foo(a: &i32) -> &Foo { - return @fieldParentPtr(Foo, "a", a); -} - )SOURCE", 1, ".tmp_source.zig:6:33: error: struct 'Foo' has no field 'a'"); - - add_compile_fail_case("@fieldParentPtr - field pointer is not pointer", R"SOURCE( -const Foo = struct { - a: i32, -}; -export fn foo(a: i32) -> &Foo { - return @fieldParentPtr(Foo, "a", a); -} - )SOURCE", 1, ".tmp_source.zig:6:38: error: expected pointer, found 'i32'"); - - add_compile_fail_case("@fieldParentPtr - comptime field ptr not based on struct", R"SOURCE( -const Foo = struct { - a: i32, - b: i32, -}; -const foo = Foo { .a = 1, .b = 2, }; - -comptime { - const field_ptr = @intToPtr(&i32, 0x1234); - const another_foo_ptr = @fieldParentPtr(Foo, "b", field_ptr); -} - )SOURCE", 1, ".tmp_source.zig:10:55: error: pointer value not based on parent struct"); - - add_compile_fail_case("@fieldParentPtr - comptime wrong field index", R"SOURCE( -const Foo = struct { - a: i32, - b: i32, -}; -const foo = Foo { .a = 1, .b = 2, }; - -comptime { - const another_foo_ptr = @fieldParentPtr(Foo, "b", &foo.a); -} - )SOURCE", 1, ".tmp_source.zig:9:29: error: field 'b' has index 1 but pointer value is index 0 of struct 'Foo'"); - - add_compile_fail_case_exe("missing main fn in executable", R"SOURCE( - )SOURCE", 1, "error: no member named 'main' in '"); - - add_compile_fail_case_exe("private main fn", R"SOURCE( -fn main() {} - )SOURCE", 2, - "error: 'main' is private", - ".tmp_source.zig:2:1: note: declared here"); - -} - -////////////////////////////////////////////////////////////////////////////// - -static void add_parse_error_tests(void) { - add_compile_fail_case("implicit semicolon - block statement", R"SOURCE( -export fn entry() { - {} - var good = {}; - ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - block expr", R"SOURCE( -export fn entry() { - _ = {}; - var good = {}; - _ = {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - comptime statement", R"SOURCE( -export fn entry() { - comptime {} - var good = {}; - comptime ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - comptime expression", R"SOURCE( -export fn entry() { - _ = comptime {}; - var good = {}; - _ = comptime {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - defer", R"SOURCE( -export fn entry() { - defer {} - var good = {}; - defer ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: expected token ';', found 'var'"); - - add_compile_fail_case("implicit semicolon - if statement", R"SOURCE( -export fn entry() { - if(true) {} - var good = {}; - if(true) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if expression", R"SOURCE( -export fn entry() { - _ = if(true) {}; - var good = {}; - _ = if(true) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else statement", R"SOURCE( -export fn entry() { - if(true) {} else {} - var good = {}; - if(true) ({}) else ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else expression", R"SOURCE( -export fn entry() { - _ = if(true) {} else {}; - var good = {}; - _ = if(true) {} else {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else-if statement", R"SOURCE( -export fn entry() { - if(true) {} else if(true) {} - var good = {}; - if(true) ({}) else if(true) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else-if expression", R"SOURCE( -export fn entry() { - _ = if(true) {} else if(true) {}; - var good = {}; - _ = if(true) {} else if(true) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else-if-else statement", R"SOURCE( -export fn entry() { - if(true) {} else if(true) {} else {} - var good = {}; - if(true) ({}) else if(true) ({}) else ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if-else-if-else expression", R"SOURCE( -export fn entry() { - _ = if(true) {} else if(true) {} else {}; - var good = {}; - _ = if(true) {} else if(true) {} else {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var) statement", R"SOURCE( -export fn entry() { - if(_=foo()) {} - var good = {}; - if(_=foo()) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var) expression", R"SOURCE( -export fn entry() { - _ = if(_=foo()) {}; - var good = {}; - _ = if(_=foo()) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else statement", R"SOURCE( -export fn entry() { - if(_=foo()) {} else {} - var good = {}; - if(_=foo()) ({}) else ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else expression", R"SOURCE( -export fn entry() { - _ = if(_=foo()) {} else {}; - var good = {}; - _ = if(_=foo()) {} else {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else-if(var) statement", R"SOURCE( -export fn entry() { - if(_=foo()) {} else if(_=foo()) {} - var good = {}; - if(_=foo()) ({}) else if(_=foo()) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else-if(var) expression", R"SOURCE( -export fn entry() { - _ = if(_=foo()) {} else if(_=foo()) {}; - var good = {}; - _ = if(_=foo()) {} else if(_=foo()) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else-if(var)-else statement", R"SOURCE( -export fn entry() { - if(_=foo()) {} else if(_=foo()) {} else {} - var good = {}; - if(_=foo()) ({}) else if(_=foo()) ({}) else ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - if(var)-else-if(var)-else expression", R"SOURCE( -export fn entry() { - _ = if(_=foo()) {} else if(_=foo()) {} else {}; - var good = {}; - _ = if(_=foo()) {} else if(_=foo()) {} else {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - try statement", R"SOURCE( -export fn entry() { - try (_ = foo()) {} - var good = {}; - try (_ = foo()) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - try expression", R"SOURCE( -export fn entry() { - _ = try (_ = foo()) {}; - var good = {}; - _ = try (_ = foo()) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - while statement", R"SOURCE( -export fn entry() { - while(true) {} - var good = {}; - while(true) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - while expression", R"SOURCE( -export fn entry() { - _ = while(true) {}; - var good = {}; - _ = while(true) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - while-continue statement", R"SOURCE( -export fn entry() { - while(true;{}) {} - var good = {}; - while(true;{}) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - while-continue expression", R"SOURCE( -export fn entry() { - _ = while(true;{}) {}; - var good = {}; - _ = while(true;{}) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - for statement", R"SOURCE( -export fn entry() { - for(foo()) {} - var good = {}; - for(foo()) ({}) - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); - - add_compile_fail_case("implicit semicolon - for expression", R"SOURCE( -export fn entry() { - _ = for(foo()) {}; - var good = {}; - _ = for(foo()) {} - var bad = {}; -} - )SOURCE", 1, ".tmp_source.zig:6:5: error: invalid token: 'var'"); -} - ////////////////////////////////////////////////////////////////////////////// static void add_debug_safety_test_cases(void) { @@ -2281,95 +566,6 @@ struct comptime { R"(pub const FOO_CHAR = 63;)"); } -static void run_self_hosted_test(bool is_release_mode) { - Buf self_hosted_tests_file = BUF_INIT; - os_path_join(buf_create_from_str(ZIG_TEST_DIR), - buf_create_from_str("self_hosted.zig"), &self_hosted_tests_file); - - Buf zig_stderr = BUF_INIT; - Buf zig_stdout = BUF_INIT; - ZigList args = {0}; - args.append("test"); - args.append(buf_ptr(&self_hosted_tests_file)); - if (is_release_mode) { - args.append("--release"); - } - Termination term; - os_exec_process(zig_exe, args, &term, &zig_stderr, &zig_stdout); - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nSelf-hosted tests failed:\n"); - printf("./zig"); - for (size_t i = 0; i < args.length; i += 1) { - printf(" %s", args.at(i)); - } - printf("\n%s\n", buf_ptr(&zig_stderr)); - exit(1); - } -} - -static void run_std_lib_test(bool is_release_mode) { - Buf std_index_file = BUF_INIT; - os_path_join(buf_create_from_str(ZIG_STD_DIR), - buf_create_from_str("index.zig"), &std_index_file); - - Buf zig_stderr = BUF_INIT; - Buf zig_stdout = BUF_INIT; - ZigList args = {0}; - args.append("test"); - args.append(buf_ptr(&std_index_file)); - if (is_release_mode) { - args.append("--release"); - } - Termination term; - os_exec_process(zig_exe, args, &term, &zig_stderr, &zig_stdout); - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nstd lib tests failed:\n"); - printf("./zig"); - for (size_t i = 0; i < args.length; i += 1) { - printf(" %s", args.at(i)); - } - printf("\n%s\n", buf_ptr(&zig_stderr)); - exit(1); - } -} - - -static void add_self_hosted_tests(void) { - { - TestCase *test_case = allocate(1); - test_case->case_name = "self hosted tests (debug)"; - test_case->special = TestSpecialSelfHosted; - test_case->is_release_mode = false; - test_cases.append(test_case); - } - { - TestCase *test_case = allocate(1); - test_case->case_name = "self hosted tests (release)"; - test_case->special = TestSpecialSelfHosted; - test_case->is_release_mode = true; - test_cases.append(test_case); - } -} - -static void add_std_lib_tests(void) { - { - TestCase *test_case = allocate(1); - test_case->case_name = "std (debug)"; - test_case->special = TestSpecialStd; - test_case->is_release_mode = false; - test_cases.append(test_case); - } - { - TestCase *test_case = allocate(1); - test_case->case_name = "std (release)"; - test_case->special = TestSpecialStd; - test_case->is_release_mode = true; - test_cases.append(test_case); - } -} - 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( @@ -2423,12 +619,6 @@ static void print_exe_invocation(TestCase *test_case) { } static void run_test(TestCase *test_case) { - if (test_case->special == TestSpecialSelfHosted) { - return run_self_hosted_test(test_case->is_release_mode); - } else if (test_case->special == TestSpecialStd) { - return run_std_lib_test(test_case->is_release_mode); - } - for (size_t i = 0; i < test_case->source_files.length; i += 1) { TestSourceFile *test_source = &test_case->source_files.at(i); os_write_file( @@ -2609,11 +799,7 @@ int main(int argc, char **argv) { } } add_debug_safety_test_cases(); - add_compile_failure_test_cases(); - add_parse_error_tests(); add_parseh_test_cases(); - add_self_hosted_tests(); - add_std_lib_tests(); add_asm_tests(); run_all_tests(grep_text); cleanup(); diff --git a/test/tests.zig b/test/tests.zig index d398133db..7eccf2423 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1,2 +1,3 @@ pub const addCompareOutputTests = @import("compare_output.zig").addCompareOutputTests; pub const addBuildExampleTests = @import("build_examples.zig").addBuildExampleTests; +pub const addCompileErrorTests = @import("compile_errors.zig").addCompileErrorTests; From 35d60eb4e65c38a3c81e3a249de7447017616d02 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 04:28:59 -0400 Subject: [PATCH 04/10] remove unused test code --- build.zig | 7 ------- test/run_tests.zig | 5 ----- 2 files changed, 12 deletions(-) delete mode 100644 test/run_tests.zig diff --git a/build.zig b/build.zig index 8e8b12358..b0338800b 100644 --- a/build.zig +++ b/build.zig @@ -5,11 +5,6 @@ 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 = b.step("test-self-hosted", "Run the self-hosted tests"); test_step.dependOn(self_hosted_tests); for ([]bool{false, true}) |release| { @@ -40,8 +35,6 @@ pub fn build(b: &Builder) { } } - //test_step.dependOn(&run_tests_cmd.step); - test_step.dependOn(tests.addCompareOutputTests(b, test_filter)); test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); diff --git a/test/run_tests.zig b/test/run_tests.zig deleted file mode 100644 index 5cdaa94fe..000000000 --- a/test/run_tests.zig +++ /dev/null @@ -1,5 +0,0 @@ -const io = @import("std").io; - -pub fn main() -> %void { - %%io.stderr.printf("TODO run tests\n"); -} From 666435195fff867417f92e1b4f8ef7e0608470ee Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 04:36:48 -0400 Subject: [PATCH 05/10] update zig build help text when no build.zig found --- src/main.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index 058fa7a88..044ac1151 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -217,11 +217,14 @@ int main(int argc, char **argv) { "Usage: %s build [options]\n" "\n" "General Options:\n" - " --help Print this help and exit.\n" - " --build-file [file] Override path to build.zig.\n" - " --verbose Print commands before executing them.\n" - " --debug-build-verbose Print verbose debugging information for the build system itself.\n" - " --prefix [prefix] Override default install prefix.\n" + " --help Print this help and exit\n" + " --build-file [file] Override path to build.zig\n" + " --verbose Print commands before executing them\n" + " --debug-build-verbose Print verbose debugging information for the build system itself\n" + " --prefix [prefix] Override default install prefix\n" + "\n" + "More options become available when the build file is found.\n" + "Run this command with no options to generate a build.zig template.\n" , zig_exe_path); return 0; } From d1e01e43d3b2078bfb07defb693d819e99eaa6c5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 14:00:12 -0400 Subject: [PATCH 06/10] convert assemble and link tests to zig build system --- build.zig | 1 + src/link.cpp | 8 + src/os.cpp | 7 + src/os.hpp | 2 + std/build.zig | 344 +++++++++++++++++++++++- test/assemble_and_link.zig | 26 ++ test/build_examples.zig | 56 +--- test/compare_output.zig | 189 +------------ test/compile_errors.zig | 239 +---------------- test/run_tests.cpp | 59 ----- test/tests.zig | 531 ++++++++++++++++++++++++++++++++++++- 11 files changed, 921 insertions(+), 541 deletions(-) create mode 100644 test/assemble_and_link.zig diff --git a/build.zig b/build.zig index b0338800b..5b82a4f9c 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 edf5243b4..5c73c2134 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 de0df9271..e8ee2a155 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 bec276689..1d29362d5 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 11c038329..c58a72eec 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 000000000..89b0195fe --- /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 174dc14be..72bed9d29 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 f971d7eef..33ec04995 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 199d60b18..16431c76f 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 2f960f244..a2ff16fab 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 7eccf2423..d14d0a1d1 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); + } + } +}; From 9b7f438882b4f283d252d8ca364607fae385cc1a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 14:41:59 -0400 Subject: [PATCH 07/10] convert debug safety tests to zig build system --- build.zig | 1 + test/debug_safety.zig | 234 ++++++++++++++++++++++++++++++++ test/run_tests.cpp | 309 +++--------------------------------------- test/tests.zig | 207 ++++++++++++++++++++++------ 4 files changed, 415 insertions(+), 336 deletions(-) create mode 100644 test/debug_safety.zig diff --git a/build.zig b/build.zig index 5b82a4f9c..4ce81914f 100644 --- a/build.zig +++ b/build.zig @@ -39,4 +39,5 @@ pub fn build(b: &Builder) { test_step.dependOn(tests.addBuildExampleTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); + test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); } diff --git a/test/debug_safety.zig b/test/debug_safety.zig new file mode 100644 index 000000000..0995a0e54 --- /dev/null +++ b/test/debug_safety.zig @@ -0,0 +1,234 @@ +const tests = @import("tests.zig"); + +pub fn addCases(cases: &tests.CompareOutputContext) { + cases.addDebugSafety("calling panic", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\pub fn main() -> %void { + \\ @panic("oh no"); + \\} + ); + + cases.addDebugSafety("out of bounds slice access", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\pub fn main() -> %void { + \\ const a = []i32{1, 2, 3, 4}; + \\ baz(bar(a)); + \\} + \\fn bar(a: []const i32) -> i32 { + \\ a[4] + \\} + \\fn baz(a: i32) { } + ); + + cases.addDebugSafety("integer addition overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = add(65530, 10); + \\ if (x == 0) return error.Whatever; + \\} + \\fn add(a: u16, b: u16) -> u16 { + \\ a + b + \\} + ); + + cases.addDebugSafety("integer subtraction overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = sub(10, 20); + \\ if (x == 0) return error.Whatever; + \\} + \\fn sub(a: u16, b: u16) -> u16 { + \\ a - b + \\} + ); + + cases.addDebugSafety("integer multiplication overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = mul(300, 6000); + \\ if (x == 0) return error.Whatever; + \\} + \\fn mul(a: u16, b: u16) -> u16 { + \\ a * b + \\} + ); + + cases.addDebugSafety("integer negation overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = neg(-32768); + \\ if (x == 32767) return error.Whatever; + \\} + \\fn neg(a: i16) -> i16 { + \\ -a + \\} + ); + + cases.addDebugSafety("signed integer division overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = div(-32768, -1); + \\ if (x == 32767) return error.Whatever; + \\} + \\fn div(a: i16, b: i16) -> i16 { + \\ a / b + \\} + ); + + cases.addDebugSafety("signed shift left overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = shl(-16385, 1); + \\ if (x == 0) return error.Whatever; + \\} + \\fn shl(a: i16, b: i16) -> i16 { + \\ a << b + \\} + ); + + cases.addDebugSafety("unsigned shift left overflow", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = shl(0b0010111111111111, 3); + \\ if (x == 0) return error.Whatever; + \\} + \\fn shl(a: u16, b: u16) -> u16 { + \\ a << b + \\} + ); + + cases.addDebugSafety("integer division by zero", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = div0(999, 0); + \\} + \\fn div0(a: i32, b: i32) -> i32 { + \\ a / b + \\} + ); + + cases.addDebugSafety("exact division failure", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = divExact(10, 3); + \\ if (x == 0) return error.Whatever; + \\} + \\fn divExact(a: i32, b: i32) -> i32 { + \\ @divExact(a, b) + \\} + ); + + cases.addDebugSafety("cast []u8 to bigger slice of wrong size", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = widenSlice([]u8{1, 2, 3, 4, 5}); + \\ if (x.len == 0) return error.Whatever; + \\} + \\fn widenSlice(slice: []const u8) -> []const i32 { + \\ ([]const i32)(slice) + \\} + ); + + cases.addDebugSafety("value does not fit in shortening cast", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = shorten_cast(200); + \\ if (x == 0) return error.Whatever; + \\} + \\fn shorten_cast(x: i32) -> i8 { + \\ i8(x) + \\} + ); + + cases.addDebugSafety("signed integer not fitting in cast to unsigned integer", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ const x = unsigned_cast(-10); + \\ if (x == 0) return error.Whatever; + \\} + \\fn unsigned_cast(x: i32) -> u32 { + \\ u32(x) + \\} + ); + + cases.addDebugSafety("unwrap error", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\error Whatever; + \\pub fn main() -> %void { + \\ %%bar(); + \\} + \\fn bar() -> %void { + \\ return error.Whatever; + \\} + ); + + cases.addDebugSafety("cast integer to error and no code matches", + \\pub fn panic(message: []const u8) -> noreturn { + \\ @breakpoint(); + \\ while (true) {} + \\} + \\pub fn main() -> %void { + \\ _ = bar(9999); + \\} + \\fn bar(x: u32) -> error { + \\ return error(x); + \\} + ); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp index a2ff16fab..97776b61d 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -40,7 +40,6 @@ struct TestCase { bool is_parseh; TestSpecial special; bool is_release_mode; - bool is_debug_safety; AllowWarnings allow_warnings; }; @@ -58,26 +57,6 @@ static const char *zig_exe = "./zig"; #define NL "\n" #endif -static void add_debug_safety_case(const char *case_name, const char *source) { - TestCase *test_case = allocate(1); - test_case->is_debug_safety = true; - test_case->case_name = buf_ptr(buf_sprintf("%s", case_name)); - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_source_path; - test_case->source_files.at(0).source_code = source; - - test_case->compiler_args.append("build_exe"); - test_case->compiler_args.append(tmp_source_path); - - test_case->compiler_args.append("--name"); - test_case->compiler_args.append("test"); - - test_case->compiler_args.append("--output"); - test_case->compiler_args.append(tmp_exe_path); - - test_cases.append(test_case); -} - static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warnings, const char *source, size_t count, ...) { @@ -107,240 +86,6 @@ static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warn va_end(ap); return test_case; } -////////////////////////////////////////////////////////////////////////////// - -static void add_debug_safety_test_cases(void) { - add_debug_safety_case("calling panic", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -pub fn main() -> %void { - @panic("oh no"); -} - )SOURCE"); - - add_debug_safety_case("out of bounds slice access", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -pub fn main() -> %void { - const a = []i32{1, 2, 3, 4}; - baz(bar(a)); -} -fn bar(a: []const i32) -> i32 { - a[4] -} -fn baz(a: i32) { } - )SOURCE"); - - add_debug_safety_case("integer addition overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = add(65530, 10); - if (x == 0) return error.Whatever; -} -fn add(a: u16, b: u16) -> u16 { - a + b -} - )SOURCE"); - - add_debug_safety_case("integer subtraction overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = sub(10, 20); - if (x == 0) return error.Whatever; -} -fn sub(a: u16, b: u16) -> u16 { - a - b -} - )SOURCE"); - - add_debug_safety_case("integer multiplication overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = mul(300, 6000); - if (x == 0) return error.Whatever; -} -fn mul(a: u16, b: u16) -> u16 { - a * b -} - )SOURCE"); - - add_debug_safety_case("integer negation overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = neg(-32768); - if (x == 32767) return error.Whatever; -} -fn neg(a: i16) -> i16 { - -a -} - )SOURCE"); - - add_debug_safety_case("signed integer division overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = div(-32768, -1); - if (x == 32767) return error.Whatever; -} -fn div(a: i16, b: i16) -> i16 { - a / b -} - )SOURCE"); - - add_debug_safety_case("signed shift left overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = shl(-16385, 1); - if (x == 0) return error.Whatever; -} -fn shl(a: i16, b: i16) -> i16 { - a << b -} - )SOURCE"); - - add_debug_safety_case("unsigned shift left overflow", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = shl(0b0010111111111111, 3); - if (x == 0) return error.Whatever; -} -fn shl(a: u16, b: u16) -> u16 { - a << b -} - )SOURCE"); - - add_debug_safety_case("integer division by zero", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = div0(999, 0); -} -fn div0(a: i32, b: i32) -> i32 { - a / b -} - )SOURCE"); - - add_debug_safety_case("exact division failure", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = divExact(10, 3); - if (x == 0) return error.Whatever; -} -fn divExact(a: i32, b: i32) -> i32 { - @divExact(a, b) -} - )SOURCE"); - - add_debug_safety_case("cast []u8 to bigger slice of wrong size", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = widenSlice([]u8{1, 2, 3, 4, 5}); - if (x.len == 0) return error.Whatever; -} -fn widenSlice(slice: []const u8) -> []const i32 { - ([]const i32)(slice) -} - )SOURCE"); - - add_debug_safety_case("value does not fit in shortening cast", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = shorten_cast(200); - if (x == 0) return error.Whatever; -} -fn shorten_cast(x: i32) -> i8 { - i8(x) -} - )SOURCE"); - - add_debug_safety_case("signed integer not fitting in cast to unsigned integer", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - const x = unsigned_cast(-10); - if (x == 0) return error.Whatever; -} -fn unsigned_cast(x: i32) -> u32 { - u32(x) -} - )SOURCE"); - - add_debug_safety_case("unwrap error", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -error Whatever; -pub fn main() -> %void { - %%bar(); -} -fn bar() -> %void { - return error.Whatever; -} - )SOURCE"); - - add_debug_safety_case("cast integer to error and no code matches", R"SOURCE( -pub fn panic(message: []const u8) -> noreturn { - @breakpoint(); - while (true) {} -} -pub fn main() -> %void { - _ = bar(9999); -} -fn bar(x: u32) -> error { - return error(x); -} - )SOURCE"); -} ////////////////////////////////////////////////////////////////////////////// @@ -653,43 +398,24 @@ static void run_test(TestCase *test_case) { Buf program_stdout = BUF_INIT; os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout); - if (test_case->is_debug_safety) { - int debug_trap_signal = 5; - if (term.how != TerminationIdSignaled || term.code != debug_trap_signal) { - if (term.how == TerminationIdClean) { - printf("\nProgram expected to hit debug trap (signal %d) but exited with return code %d\n", - debug_trap_signal, term.code); - } else if (term.how == TerminationIdSignaled) { - printf("\nProgram expected to hit debug trap (signal %d) but signaled with code %d\n", - debug_trap_signal, term.code); - } else { - printf("\nProgram expected to hit debug trap (signal %d) exited in an unexpected way\n", - debug_trap_signal); - } - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - exit(1); - } - } else { - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nProgram exited with error\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("%s\n", buf_ptr(&program_stderr)); - exit(1); - } + if (term.how != TerminationIdClean || term.code != 0) { + printf("\nProgram exited with error\n"); + print_compiler_invocation(test_case); + print_exe_invocation(test_case); + printf("%s\n", buf_ptr(&program_stderr)); + exit(1); + } - if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) { - printf("\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("==== Test failed. Expected output: ====\n"); - printf("%s\n", test_case->output); - printf("========= Actual output: ==============\n"); - printf("%s\n", buf_ptr(&program_stdout)); - printf("=======================================\n"); - exit(1); - } + if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) { + printf("\n"); + print_compiler_invocation(test_case); + print_exe_invocation(test_case); + printf("==== Test failed. Expected output: ====\n"); + printf("%s\n", test_case->output); + printf("========= Actual output: ==============\n"); + printf("%s\n", buf_ptr(&program_stdout)); + printf("=======================================\n"); + exit(1); } } @@ -740,7 +466,6 @@ int main(int argc, char **argv) { } } } - add_debug_safety_test_cases(); add_parseh_test_cases(); run_all_tests(grep_text); cleanup(); diff --git a/test/tests.zig b/test/tests.zig index d14d0a1d1..61dc1fc05 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -16,6 +16,7 @@ pub const compare_output = @import("compare_output.zig"); pub const build_examples = @import("build_examples.zig"); pub const compile_errors = @import("compile_errors.zig"); pub const assemble_and_link = @import("assemble_and_link.zig"); +pub const debug_safety = @import("debug_safety.zig"); pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { const cases = %%b.allocator.create(CompareOutputContext); @@ -31,6 +32,20 @@ pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &bu return cases.step; } +pub fn addDebugSafetyTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(CompareOutputContext); + *cases = CompareOutputContext { + .b = b, + .step = b.step("test-debug-safety", "Run the debug safety tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + debug_safety.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 { @@ -79,12 +94,18 @@ pub const CompareOutputContext = struct { test_index: usize, test_filter: ?[]const u8, + const Special = enum { + None, + Asm, + DebugSafety, + }; + const TestCase = struct { name: []const u8, sources: List(SourceFile), expected_output: []const u8, link_libc: bool, - is_asm: bool, + special: Special, const SourceFile = struct { filename: []const u8, @@ -175,17 +196,83 @@ pub const CompareOutputContext = struct { } }; + const DebugSafetyRunStep = struct { + step: build.Step, + context: &CompareOutputContext, + exe_path: []const u8, + name: []const u8, + test_index: usize, + + pub fn create(context: &CompareOutputContext, exe_path: []const u8, + name: []const u8) -> &DebugSafetyRunStep + { + const allocator = context.b.allocator; + const ptr = %%allocator.create(DebugSafetyRunStep); + *ptr = DebugSafetyRunStep { + .context = context, + .exe_path = exe_path, + .name = name, + .test_index = context.test_index, + .step = build.Step.init("DebugSafetyRun", allocator, make), + }; + context.test_index += 1; + return ptr; + } + + fn make(step: &build.Step) -> %void { + const self = @fieldParentPtr(DebugSafetyRunStep, "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)); + }; + + const debug_trap_signal: i32 = 5; + switch (term) { + Term.Clean => |code| { + %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++ + "but exited with return code {}\n", debug_trap_signal, code); + return error.TestFailed; + }, + Term.Signal => |sig| { + if (sig != debug_trap_signal) { + %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++ + "but instead signaled {}\n", debug_trap_signal, sig); + return error.TestFailed; + } + }, + else => { + %%io.stderr.printf("\nProgram expected to hit debug trap (signal {}) " ++ + " but exited in an unexpected way\n", debug_trap_signal); + 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 + expected_output: []const u8, special: Special) -> 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, + .special = special, }; - const root_src_name = if (is_asm) "source.s" else "source.zig"; + const root_src_name = if (special == Special.Asm) "source.s" else "source.zig"; tc.addSourceFile(root_src_name, source); return tc; } @@ -193,7 +280,7 @@ pub const CompareOutputContext = struct { pub fn create(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) -> TestCase { - return createExtra(self, name, source, expected_output, false); + return createExtra(self, name, source, expected_output, Special.None); } pub fn addC(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) { @@ -208,7 +295,12 @@ pub const CompareOutputContext = struct { } pub fn addAsm(self: &CompareOutputContext, name: []const u8, source: []const u8, expected_output: []const u8) { - const tc = self.createExtra(name, source, expected_output, true); + const tc = self.createExtra(name, source, expected_output, Special.Asm); + self.addCase(tc); + } + + pub fn addDebugSafety(self: &CompareOutputContext, name: []const u8, source: []const u8) { + const tc = self.createExtra(name, source, undefined, Special.DebugSafety); self.addCase(tc); } @@ -218,45 +310,74 @@ pub const CompareOutputContext = struct { 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"); + switch (case.special) { + Special.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) - continue; + 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); + }, + Special.None => { + 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); + } + }, + Special.DebugSafety => { + const obj_path = %%os.path.join(b.allocator, "test_artifacts", "test.o"); + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "debug-safety {}", case.name); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; } const exe = b.addExecutable("test", root_src); exe.setOutputPath(exe_path); - exe.setRelease(release); if (case.link_libc) { exe.linkLibrary("c"); } @@ -267,14 +388,12 @@ pub const CompareOutputContext = struct { exe.step.dependOn(&write_src.step); } - const run_and_cmp_output = RunCompareOutputStep.create(self, exe_path, annotated_case_name, - case.expected_output); + const run_and_cmp_output = DebugSafetyRunStep.create(self, exe_path, annotated_case_name); run_and_cmp_output.step.dependOn(&exe.step); self.step.dependOn(&run_and_cmp_output.step); - } - }; - + }, + } } }; From d12f1f5b4954d893111c05420060354537a1485c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 15:38:12 -0400 Subject: [PATCH 08/10] test framework supports name prefix and filter argument rename self hosted tests to behavior tests --- build.zig | 20 ++++--- src/all_types.hpp | 3 + src/analyze.cpp | 9 ++- src/codegen.cpp | 8 +++ src/codegen.hpp | 2 + src/main.cpp | 17 ++++++ std/build.zig | 80 +++++++++++++++----------- test/{self_hosted.zig => behavior.zig} | 0 test/tests.zig | 4 +- 9 files changed, 98 insertions(+), 45 deletions(-) rename test/{self_hosted.zig => behavior.zig} (100%) diff --git a/build.zig b/build.zig index 4ce81914f..fc9df1149 100644 --- a/build.zig +++ b/build.zig @@ -5,18 +5,20 @@ 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 self_hosted_tests = b.step("test-self-hosted", "Run the self-hosted tests"); - test_step.dependOn(self_hosted_tests); + const behavior_tests = b.step("test-behavior", "Run the behavior tests"); + test_step.dependOn(behavior_tests); for ([]bool{false, true}) |release| { for ([]bool{false, true}) |link_libc| { - const these_tests = b.addTest("test/self_hosted.zig"); - // TODO add prefix to test names - // TODO pass test_filter to these_tests + const these_tests = b.addTest("test/behavior.zig"); + these_tests.setNamePrefix(b.fmt("behavior-{}-{} ", + if (release) "release" else "debug", + if (link_libc) "c" else "bare")); + these_tests.setFilter(test_filter); these_tests.setRelease(release); if (link_libc) { these_tests.linkLibrary("c"); } - self_hosted_tests.dependOn(&these_tests.step); + behavior_tests.dependOn(&these_tests.step); } } @@ -25,8 +27,10 @@ pub fn build(b: &Builder) { for ([]bool{false, true}) |release| { for ([]bool{false, true}) |link_libc| { const these_tests = b.addTest("std/index.zig"); - // TODO add prefix to test names - // TODO pass test_filter to these_tests + these_tests.setNamePrefix(b.fmt("std-{}-{} ", + if (release) "release" else "debug", + if (link_libc) "c" else "bare")); + these_tests.setFilter(test_filter); these_tests.setRelease(release); if (link_libc) { these_tests.linkLibrary("c"); diff --git a/src/all_types.hpp b/src/all_types.hpp index 8386050ca..5dc7b9057 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1458,6 +1458,9 @@ struct CodeGen { ZigList link_objects; ZigList name_table_enums; + + Buf *test_filter; + Buf *test_name_prefix; }; enum VarLinkage { diff --git a/src/analyze.cpp b/src/analyze.cpp index af910cd59..aef85ca0b 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -1959,7 +1959,14 @@ static void preview_test_decl(CodeGen *g, AstNode *node, ScopeDecls *decls_scope if (import->package != g->root_package) return; - Buf *test_name = node->data.test_decl.name; + Buf *decl_name_buf = node->data.test_decl.name; + + Buf *test_name = g->test_name_prefix ? + buf_sprintf("%s%s", buf_ptr(g->test_name_prefix), buf_ptr(decl_name_buf)) : decl_name_buf; + + if (g->test_filter != nullptr && strstr(buf_ptr(test_name), buf_ptr(g->test_filter)) == nullptr) { + return; + } TldFn *tld_fn = allocate(1); init_tld(&tld_fn->base, TldIdFn, test_name, VisibModPrivate, node, &decls_scope->base); diff --git a/src/codegen.cpp b/src/codegen.cpp index 3061c8276..fc57b4977 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -147,6 +147,14 @@ void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt) { g->omit_zigrt = omit_zigrt; } +void codegen_set_test_filter(CodeGen *g, Buf *filter) { + g->test_filter = filter; +} + +void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix) { + g->test_name_prefix = prefix; +} + void codegen_set_is_test(CodeGen *g, bool is_test_build) { g->is_test_build = is_test_build; } diff --git a/src/codegen.hpp b/src/codegen.hpp index e40fee9b1..03aa6f53e 100644 --- a/src/codegen.hpp +++ b/src/codegen.hpp @@ -44,6 +44,8 @@ void codegen_set_mmacosx_version_min(CodeGen *g, Buf *mmacosx_version_min); void codegen_set_mios_version_min(CodeGen *g, Buf *mios_version_min); void codegen_set_linker_script(CodeGen *g, const char *linker_script); void codegen_set_omit_zigrt(CodeGen *g, bool omit_zigrt); +void codegen_set_test_filter(CodeGen *g, Buf *filter); +void codegen_set_test_name_prefix(CodeGen *g, Buf *prefix); PackageTableEntry *new_package(const char *root_src_dir, const char *root_src_path); void codegen_add_root_code(CodeGen *g, Buf *source_dir, Buf *source_basename, Buf *source_code); diff --git a/src/main.cpp b/src/main.cpp index 044ac1151..1d3735242 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -64,6 +64,9 @@ static int usage(const char *arg0) { " -mwindows (windows only) --subsystem windows to the linker\n" " -rdynamic add all symbols to the dynamic symbol table\n" " -rpath [path] add directory to the runtime library search path\n" + "Test Options:\n" + " --test-filter [text] skip tests that do not match filter\n" + " --test-name-prefix [text] add prefix to all tests\n" , arg0); return EXIT_FAILURE; } @@ -151,6 +154,8 @@ int main(int argc, char **argv) { ZigList rpath_list = {0}; bool each_lib_rpath = false; ZigList objects = {0}; + const char *test_filter = nullptr; + const char *test_name_prefix = nullptr; if (argc >= 2 && strcmp(argv[1], "build") == 0) { const char *zig_exe_path = arg0; @@ -341,6 +346,10 @@ int main(int argc, char **argv) { linker_script = argv[i]; } else if (strcmp(arg, "-rpath") == 0) { rpath_list.append(argv[i]); + } else if (strcmp(arg, "--test-filter") == 0) { + test_filter = argv[i]; + } else if (strcmp(arg, "--test-name-prefix") == 0) { + test_name_prefix = argv[i]; } else { fprintf(stderr, "Invalid argument: %s\n", arg); return usage(arg0); @@ -562,6 +571,14 @@ int main(int argc, char **argv) { codegen_set_mios_version_min(g, buf_create_from_str(mios_version_min)); } + if (test_filter) { + codegen_set_test_filter(g, buf_create_from_str(test_filter)); + } + + if (test_name_prefix) { + codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix)); + } + if (cmd == CmdBuild) { codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code); codegen_link(g, out_file); diff --git a/std/build.zig b/std/build.zig index c58a72eec..20fb4cad5 100644 --- a/std/build.zig +++ b/std/build.zig @@ -10,7 +10,7 @@ const StdIo = os.ChildProcess.StdIo; const Term = os.ChildProcess.Term; const BufSet = @import("buf_set.zig").BufSet; const BufMap = @import("buf_map.zig").BufMap; -const fmt = @import("fmt.zig"); +const fmt_lib = @import("fmt.zig"); error ExtraArg; error UncleanExit; @@ -189,7 +189,7 @@ pub const Builder = struct { } pub fn addLog(self: &Builder, comptime format: []const u8, args: ...) -> &LogStep { - const data = %%fmt.allocPrint(self.allocator, format, args); + const data = self.fmt(format, args); const log_step = %%self.allocator.create(LogStep); *log_step = LogStep.init(self, data); return log_step; @@ -543,6 +543,10 @@ pub const Builder = struct { fn pathFromRoot(self: &Builder, rel_path: []const u8) -> []u8 { return %%os.path.join(self.allocator, self.build_root, rel_path); } + + pub fn fmt(self: &Builder, comptime format: []const u8, args: ...) -> []u8 { + return %%fmt_lib.allocPrint(self.allocator, format, args); + } }; const Version = struct { @@ -891,19 +895,16 @@ pub const LinkStep = struct { fn computeOutFileName(self: &LinkStep) { switch (self.out_type) { OutType.Exe => { - self.out_filename = %%fmt.allocPrint(self.builder.allocator, "{}{}", - self.name, self.target.exeFileExt()); + self.out_filename = self.builder.fmt("{}{}", self.name, self.target.exeFileExt()); }, OutType.Lib => { if (self.static) { - self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.a", self.name); + self.out_filename = self.builder.fmt("lib{}.a", self.name); } else { - self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.so.{d}.{d}.{d}", + self.out_filename = self.builder.fmt("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); + self.major_only_filename = self.builder.fmt("lib{}.so.{d}", self.name, self.version.major); + self.name_only_filename = self.builder.fmt("lib{}.so", self.name); } }, } @@ -1051,15 +1052,19 @@ pub const TestStep = struct { release: bool, verbose: bool, link_libs: BufSet, + name_prefix: []const u8, + filter: ?[]const u8, pub fn init(builder: &Builder, root_src: []const u8) -> TestStep { - const step_name = %%fmt.allocPrint(builder.allocator, "test {}", root_src); + const step_name = builder.fmt("test {}", root_src); TestStep { .step = Step.init(step_name, builder.allocator, make), .builder = builder, .root_src = root_src, .release = false, .verbose = false, + .name_prefix = "", + .filter = null, .link_libs = BufSet.init(builder.allocator), } } @@ -1076,6 +1081,14 @@ pub const TestStep = struct { %%self.link_libs.put(name); } + pub fn setNamePrefix(self: &TestStep, text: []const u8) { + self.name_prefix = text; + } + + pub fn setFilter(self: &TestStep, text: ?[]const u8) { + self.filter = text; + } + fn make(step: &Step) -> %void { const self = @fieldParentPtr(TestStep, "step", step); const builder = self.builder; @@ -1094,6 +1107,16 @@ pub const TestStep = struct { %%zig_args.append("--release"); } + if (const filter ?= self.filter) { + %%zig_args.append("--test-filter"); + %%zig_args.append(filter); + } + + if (self.name_prefix.len != 0) { + %%zig_args.append("--test-name-prefix"); + %%zig_args.append(self.name_prefix); + } + { var it = self.link_libs.iterator(); while (true) { @@ -1169,14 +1192,12 @@ pub const CLibrary = struct { fn computeOutFileName(self: &CLibrary) { if (self.static) { - self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.a", self.name); + self.out_filename = self.builder.fmt("lib{}.a", self.name); } else { - self.out_filename = %%fmt.allocPrint(self.builder.allocator, "lib{}.so.{d}.{d}.{d}", + self.out_filename = self.builder.fmt("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); + self.major_only_filename = self.builder.fmt("lib{}.so.{d}", self.name, self.version.major); + self.name_only_filename = self.builder.fmt("lib{}.so", self.name); } } @@ -1235,7 +1256,7 @@ pub const CLibrary = struct { %%cc_args.append(source_file); // TODO don't dump the .o file in the same place as the source file - const o_file = %%fmt.allocPrint(builder.allocator, "{}{}", source_file, self.target.oFileExt()); + const o_file = builder.fmt("{}{}", source_file, self.target.oFileExt()); defer builder.allocator.free(o_file); %%cc_args.append("-o"); %%cc_args.append(o_file); @@ -1262,8 +1283,7 @@ pub const CLibrary = struct { %%cc_args.append("-fPIC"); %%cc_args.append("-shared"); - const soname_arg = %%fmt.allocPrint(builder.allocator, "-Wl,-soname,lib{}.so.{d}", - self.name, self.version.major); + const soname_arg = builder.fmt("-Wl,-soname,lib{}.so.{d}", self.name, self.version.major); defer builder.allocator.free(soname_arg); %%cc_args.append(soname_arg); @@ -1372,7 +1392,7 @@ pub const CExecutable = struct { %%cc_args.append(source_file); // TODO don't dump the .o file in the same place as the source file - const o_file = %%fmt.allocPrint(builder.allocator, "{}{}", source_file, self.target.oFileExt()); + const o_file = builder.fmt("{}{}", source_file, self.target.oFileExt()); defer builder.allocator.free(o_file); %%cc_args.append("-o"); %%cc_args.append(o_file); @@ -1400,7 +1420,7 @@ pub const CExecutable = struct { %%cc_args.append("-o"); %%cc_args.append(self.name); - const rpath_arg = %%fmt.allocPrint(builder.allocator, "-Wl,-rpath,{}", builder.out_dir); + const rpath_arg = builder.fmt("-Wl,-rpath,{}", builder.out_dir); defer builder.allocator.free(rpath_arg); %%cc_args.append(rpath_arg); @@ -1462,9 +1482,7 @@ pub const InstallCLibraryStep = struct { pub fn init(builder: &Builder, lib: &CLibrary) -> InstallCLibraryStep { var self = InstallCLibraryStep { .builder = builder, - .step = Step.init( - %%fmt.allocPrint(builder.allocator, "install {}", lib.step.name), - builder.allocator, make), + .step = Step.init(builder.fmt("install {}", lib.step.name), builder.allocator, make), .lib = lib, .dest_file = undefined, }; @@ -1497,9 +1515,7 @@ pub const InstallFileStep = struct { pub fn init(builder: &Builder, src_path: []const u8, dest_path: []const u8) -> InstallFileStep { return InstallFileStep { .builder = builder, - .step = Step.init( - %%fmt.allocPrint(builder.allocator, "install {}", src_path), - builder.allocator, make), + .step = Step.init(builder.fmt("install {}", src_path), builder.allocator, make), .src_path = src_path, .dest_path = dest_path, }; @@ -1521,9 +1537,7 @@ pub const WriteFileStep = struct { 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), + .step = Step.init(builder.fmt("writefile {}", file_path), builder.allocator, make), .file_path = file_path, .data = data, }; @@ -1550,9 +1564,7 @@ pub const LogStep = struct { 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), + .step = Step.init(builder.fmt("log {}", data), builder.allocator, make), .data = data, }; } diff --git a/test/self_hosted.zig b/test/behavior.zig similarity index 100% rename from test/self_hosted.zig rename to test/behavior.zig diff --git a/test/tests.zig b/test/tests.zig index 61dc1fc05..8e15bcc12 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -341,8 +341,8 @@ pub const CompareOutputContext = struct { }, Special.None => { for ([]bool{false, true}) |release| { - const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} ({})", - case.name, if (release) "release" else "debug"); + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "{} {} ({})", + "compare-output", case.name, if (release) "release" else "debug"); if (const filter ?= self.test_filter) { if (mem.indexOf(u8, annotated_case_name, filter) == null) continue; From 1ff73a8e69a09b3bc993cffb755cc2ca98c7040b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 19 Apr 2017 16:59:20 -0400 Subject: [PATCH 09/10] convert parseh tests to zig build system --- CMakeLists.txt | 17 +- build.zig | 1 + test/parseh.zig | 243 +++++++++++++++++++++++ test/run_tests.cpp | 472 --------------------------------------------- test/tests.zig | 212 +++++++++++++++++++- 5 files changed, 451 insertions(+), 494 deletions(-) create mode 100644 test/parseh.zig delete mode 100644 test/run_tests.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 67adb2604..86dca3aea 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -63,14 +63,6 @@ set(ZIG_SOURCES "${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp" ) -set(TEST_SOURCES - "${CMAKE_SOURCE_DIR}/src/buffer.cpp" - "${CMAKE_SOURCE_DIR}/src/util.cpp" - "${CMAKE_SOURCE_DIR}/src/os.cpp" - "${CMAKE_SOURCE_DIR}/src/error.cpp" - "${CMAKE_SOURCE_DIR}/test/run_tests.cpp" -) - set(C_HEADERS "${CMAKE_SOURCE_DIR}/c_headers/Intrin.h" "${CMAKE_SOURCE_DIR}/c_headers/__stddef_max_align_t.h" @@ -248,19 +240,12 @@ install(FILES "${CMAKE_SOURCE_DIR}/std/special/test_runner.zig" DESTINATION "${Z install(FILES "${CMAKE_SOURCE_DIR}/std/special/zigrt.zig" DESTINATION "${ZIG_STD_DEST}/special") install(FILES "${CMAKE_SOURCE_DIR}/std/target.zig" DESTINATION "${ZIG_STD_DEST}") -add_executable(run_tests ${TEST_SOURCES}) -target_link_libraries(run_tests) -set_target_properties(run_tests PROPERTIES - COMPILE_FLAGS ${EXE_CFLAGS} - LINK_FLAGS ${EXE_LDFLAGS} -) - if (ZIG_TEST_COVERAGE) add_custom_target(coverage DEPENDS run_tests WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMAND lcov --directory . --zerocounters --rc lcov_branch_coverage=1 - COMMAND ./run_tests + COMMAND ./zig build --build-file ../build.zig test COMMAND lcov --directory . --capture --output-file coverage.info --rc lcov_branch_coverage=1 COMMAND lcov --remove coverage.info '/usr/*' --output-file coverage.info.cleaned --rc lcov_branch_coverage=1 COMMAND genhtml -o coverage coverage.info.cleaned --rc lcov_branch_coverage=1 diff --git a/build.zig b/build.zig index fc9df1149..918a629cf 100644 --- a/build.zig +++ b/build.zig @@ -44,4 +44,5 @@ pub fn build(b: &Builder) { test_step.dependOn(tests.addCompileErrorTests(b, test_filter)); test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); + test_step.dependOn(tests.addParseHTests(b, test_filter)); } diff --git a/test/parseh.zig b/test/parseh.zig new file mode 100644 index 000000000..08740a0ac --- /dev/null +++ b/test/parseh.zig @@ -0,0 +1,243 @@ +const tests = @import("tests.zig"); + +pub fn addCases(cases: &tests.ParseHContext) { + cases.addAllowWarnings("simple data types", + \\#include + \\int foo(char a, unsigned char b, signed char c); + \\int foo(char a, unsigned char b, signed char c); // test a duplicate prototype + \\void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); + \\void baz(int8_t a, int16_t b, int32_t c, int64_t d); + , + \\pub extern fn foo(a: u8, b: u8, c: i8) -> c_int; + , + \\pub extern fn bar(a: u8, b: u16, c: u32, d: u64); + , + \\pub extern fn baz(a: i8, b: i16, c: i32, d: i64); + ); + + cases.add("noreturn attribute", + \\void foo(void) __attribute__((noreturn)); + , + \\pub extern fn foo() -> noreturn; + ); + + cases.add("enums", + \\enum Foo { + \\ FooA, + \\ FooB, + \\ Foo1, + \\}; + , + \\pub const enum_Foo = extern enum { + \\ A, + \\ B, + \\ @"1", + \\}; + , + \\pub const FooA = 0; + , + \\pub const FooB = 1; + , + \\pub const Foo1 = 2; + , + \\pub const Foo = enum_Foo + ); + + cases.add("restrict -> noalias", + \\void foo(void *restrict bar, void *restrict); + , + \\pub extern fn foo(noalias bar: ?&c_void, noalias arg1: ?&c_void); + ); + + cases.add("simple struct", + \\struct Foo { + \\ int x; + \\ char *y; + \\}; + , + \\const struct_Foo = extern struct { + \\ x: c_int, + \\ y: ?&u8, + \\}; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("qualified struct and enum", + \\struct Foo { + \\ int x; + \\ int y; + \\}; + \\enum Bar { + \\ BarA, + \\ BarB, + \\}; + \\void func(struct Foo *a, enum Bar **b); + , + \\pub const struct_Foo = extern struct { + \\ x: c_int, + \\ y: c_int, + \\}; + , + \\pub const enum_Bar = extern enum { + \\ A, + \\ B, + \\}; + , + \\pub const BarA = 0; + , + \\pub const BarB = 1; + , + \\pub extern fn func(a: ?&struct_Foo, b: ?&?&enum_Bar); + , + \\pub const Foo = struct_Foo; + , + \\pub const Bar = enum_Bar; + ); + + cases.add("constant size array", + \\void func(int array[20]); + , + \\pub extern fn func(array: ?&c_int); + ); + + cases.add("self referential struct with function pointer", + \\struct Foo { + \\ void (*derp)(struct Foo *foo); + \\}; + , + \\pub const struct_Foo = extern struct { + \\ derp: ?extern fn(?&struct_Foo), + \\}; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("struct prototype used in func", + \\struct Foo; + \\struct Foo *some_func(struct Foo *foo, int x); + , + \\pub const struct_Foo = @OpaqueType(); + , + \\pub extern fn some_func(foo: ?&struct_Foo, x: c_int) -> ?&struct_Foo; + , + \\pub const Foo = struct_Foo; + ); + + cases.add("#define a char literal", + \\#define A_CHAR 'a' + , + \\pub const A_CHAR = 97; + ); + + cases.add("#define an unsigned integer literal", + \\#define CHANNEL_COUNT 24 + , + \\pub const CHANNEL_COUNT = 24; + ); + + cases.add("#define referencing another #define", + \\#define THING2 THING1 + \\#define THING1 1234 + , + \\pub const THING1 = 1234; + , + \\pub const THING2 = THING1; + ); + + cases.add("variables", + \\extern int extern_var; + \\static const int int_var = 13; + , + \\pub extern var extern_var: c_int; + , + \\pub const int_var: c_int = 13; + ); + + cases.add("circular struct definitions", + \\struct Bar; + \\ + \\struct Foo { + \\ struct Bar *next; + \\}; + \\ + \\struct Bar { + \\ struct Foo *next; + \\}; + , + \\pub const struct_Bar = extern struct { + \\ next: ?&struct_Foo, + \\}; + , + \\pub const struct_Foo = extern struct { + \\ next: ?&struct_Bar, + \\}; + ); + + cases.add("typedef void", + \\typedef void Foo; + \\Foo fun(Foo *a); + , + \\pub const Foo = c_void; + , + \\pub extern fn fun(a: ?&c_void); + ); + + cases.add("generate inline func for #define global extern fn", + \\extern void (*fn_ptr)(void); + \\#define foo fn_ptr + \\ + \\extern char (*fn_ptr2)(int, float); + \\#define bar fn_ptr2 + , + \\pub extern var fn_ptr: ?extern fn(); + , + \\pub fn foo(); + , + \\pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8; + , + \\pub fn bar(arg0: c_int, arg1: f32) -> u8; + ); + + cases.add("#define string", + \\#define foo "a string" + , + \\pub const foo: &const u8 = &(c str lit); + ); + + cases.add("__cdecl doesn't mess up function pointers", + \\void foo(void (__cdecl *fn_ptr)(void)); + , + \\pub extern fn foo(fn_ptr: ?extern fn()); + ); + + cases.add("comment after integer literal", + \\#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ + , + \\pub const SDL_INIT_VIDEO = 32; + ); + + cases.add("zig keywords in C code", + \\struct comptime { + \\ int defer; + \\}; + , + \\pub const struct_comptime = extern struct { + \\ @"defer": c_int, + \\}; + , + \\pub const @"comptime" = struct_comptime; + ); + + cases.add("macro defines string literal with octal", + \\#define FOO "aoeu\023 derp" + \\#define FOO2 "aoeu\0234 derp" + \\#define FOO_CHAR '\077' + , + \\pub const FOO: &const u8 = &(c str lit); + , + \\pub const FOO2: &const u8 = &(c str lit); + , + \\pub const FOO_CHAR = 63; + ); +} diff --git a/test/run_tests.cpp b/test/run_tests.cpp deleted file mode 100644 index 97776b61d..000000000 --- a/test/run_tests.cpp +++ /dev/null @@ -1,472 +0,0 @@ -/* - * Copyright (c) 2015 Andrew Kelley - * - * This file is part of zig, which is MIT licensed. - * See http://opensource.org/licenses/MIT - */ - -#include "list.hpp" -#include "buffer.hpp" -#include "os.hpp" -#include "error.hpp" -#include "config.h" - -#include -#include - -enum TestSpecial { - TestSpecialNone, - TestSpecialLinkStep, -}; - -struct TestSourceFile { - const char *relative_path; - const char *source_code; -}; - -enum AllowWarnings { - AllowWarningsNo, - AllowWarningsYes, -}; - -struct TestCase { - const char *case_name; - const char *output; - ZigList source_files; - ZigList compile_errors; - ZigList compiler_args; - ZigList linker_args; - ZigList program_args; - bool is_parseh; - TestSpecial special; - bool is_release_mode; - AllowWarnings allow_warnings; -}; - -static ZigList test_cases = {0}; -static const char *tmp_source_path = ".tmp_source.zig"; -static const char *tmp_h_path = ".tmp_header.h"; - -#if defined(_WIN32) -static const char *tmp_exe_path = "./.tmp_exe.exe"; -static const char *zig_exe = "./zig.exe"; -#define NL "\r\n" -#else -static const char *tmp_exe_path = "./.tmp_exe"; -static const char *zig_exe = "./zig"; -#define NL "\n" -#endif - -static TestCase *add_parseh_case(const char *case_name, AllowWarnings allow_warnings, - const char *source, size_t count, ...) -{ - va_list ap; - va_start(ap, count); - - TestCase *test_case = allocate(1); - test_case->case_name = case_name; - test_case->is_parseh = true; - test_case->allow_warnings = allow_warnings; - - test_case->source_files.resize(1); - test_case->source_files.at(0).relative_path = tmp_h_path; - test_case->source_files.at(0).source_code = source; - - for (size_t i = 0; i < count; i += 1) { - const char *arg = va_arg(ap, const char *); - test_case->compile_errors.append(arg); - } - - test_case->compiler_args.append("parseh"); - test_case->compiler_args.append(tmp_h_path); - //test_case->compiler_args.append("--verbose"); - - test_cases.append(test_case); - - va_end(ap); - return test_case; -} - -////////////////////////////////////////////////////////////////////////////// - -static void add_parseh_test_cases(void) { - add_parseh_case("simple data types", AllowWarningsYes, R"SOURCE( -#include -int foo(char a, unsigned char b, signed char c); -int foo(char a, unsigned char b, signed char c); // test a duplicate prototype -void bar(uint8_t a, uint16_t b, uint32_t c, uint64_t d); -void baz(int8_t a, int16_t b, int32_t c, int64_t d); - )SOURCE", 3, - "pub extern fn foo(a: u8, b: u8, c: i8) -> c_int;", - "pub extern fn bar(a: u8, b: u16, c: u32, d: u64);", - "pub extern fn baz(a: i8, b: i16, c: i32, d: i64);"); - - add_parseh_case("noreturn attribute", AllowWarningsNo, R"SOURCE( -void foo(void) __attribute__((noreturn)); - )SOURCE", 1, R"OUTPUT(pub extern fn foo() -> noreturn;)OUTPUT"); - - add_parseh_case("enums", AllowWarningsNo, R"SOURCE( -enum Foo { - FooA, - FooB, - Foo1, -}; - )SOURCE", 5, R"(pub const enum_Foo = extern enum { - A, - B, - @"1", -};)", - R"(pub const FooA = 0;)", - R"(pub const FooB = 1;)", - R"(pub const Foo1 = 2;)", - R"(pub const Foo = enum_Foo;)"); - - add_parseh_case("restrict -> noalias", AllowWarningsNo, R"SOURCE( -void foo(void *restrict bar, void *restrict); - )SOURCE", 1, R"OUTPUT(pub extern fn foo(noalias bar: ?&c_void, noalias arg1: ?&c_void);)OUTPUT"); - - add_parseh_case("simple struct", AllowWarningsNo, R"SOURCE( -struct Foo { - int x; - char *y; -}; - )SOURCE", 2, - R"OUTPUT(const struct_Foo = extern struct { - x: c_int, - y: ?&u8, -};)OUTPUT", R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - add_parseh_case("qualified struct and enum", AllowWarningsNo, R"SOURCE( -struct Foo { - int x; - int y; -}; -enum Bar { - BarA, - BarB, -}; -void func(struct Foo *a, enum Bar **b); - )SOURCE", 7, R"OUTPUT(pub const struct_Foo = extern struct { - x: c_int, - y: c_int, -};)OUTPUT", R"OUTPUT(pub const enum_Bar = extern enum { - A, - B, -};)OUTPUT", - R"OUTPUT(pub const BarA = 0;)OUTPUT", - R"OUTPUT(pub const BarB = 1;)OUTPUT", - "pub extern fn func(a: ?&struct_Foo, b: ?&?&enum_Bar);", - R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT", - R"OUTPUT(pub const Bar = enum_Bar;)OUTPUT"); - - add_parseh_case("constant size array", AllowWarningsNo, R"SOURCE( -void func(int array[20]); - )SOURCE", 1, "pub extern fn func(array: ?&c_int);"); - - - add_parseh_case("self referential struct with function pointer", - AllowWarningsNo, R"SOURCE( -struct Foo { - void (*derp)(struct Foo *foo); -}; - )SOURCE", 2, R"OUTPUT(pub const struct_Foo = extern struct { - derp: ?extern fn(?&struct_Foo), -};)OUTPUT", R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - - add_parseh_case("struct prototype used in func", AllowWarningsNo, R"SOURCE( -struct Foo; -struct Foo *some_func(struct Foo *foo, int x); - )SOURCE", 3, R"OUTPUT(pub const struct_Foo = @OpaqueType();)OUTPUT", - R"OUTPUT(pub extern fn some_func(foo: ?&struct_Foo, x: c_int) -> ?&struct_Foo;)OUTPUT", - R"OUTPUT(pub const Foo = struct_Foo;)OUTPUT"); - - - add_parseh_case("#define a char literal", AllowWarningsNo, R"SOURCE( -#define A_CHAR 'a' - )SOURCE", 1, R"OUTPUT(pub const A_CHAR = 97;)OUTPUT"); - - - add_parseh_case("#define an unsigned integer literal", AllowWarningsNo, - R"SOURCE( -#define CHANNEL_COUNT 24 - )SOURCE", 1, R"OUTPUT(pub const CHANNEL_COUNT = 24;)OUTPUT"); - - - add_parseh_case("#define referencing another #define", AllowWarningsNo, - R"SOURCE( -#define THING2 THING1 -#define THING1 1234 - )SOURCE", 2, - "pub const THING1 = 1234;", - "pub const THING2 = THING1;"); - - - add_parseh_case("variables", AllowWarningsNo, R"SOURCE( -extern int extern_var; -static const int int_var = 13; - )SOURCE", 2, - "pub extern var extern_var: c_int;", - "pub const int_var: c_int = 13;"); - - - add_parseh_case("circular struct definitions", AllowWarningsNo, R"SOURCE( -struct Bar; - -struct Foo { - struct Bar *next; -}; - -struct Bar { - struct Foo *next; -}; - )SOURCE", 2, - R"SOURCE(pub const struct_Bar = extern struct { - next: ?&struct_Foo, -};)SOURCE", - R"SOURCE(pub const struct_Foo = extern struct { - next: ?&struct_Bar, -};)SOURCE"); - - - add_parseh_case("typedef void", AllowWarningsNo, R"SOURCE( -typedef void Foo; -Foo fun(Foo *a); - )SOURCE", 2, - "pub const Foo = c_void;", - "pub extern fn fun(a: ?&c_void);"); - - add_parseh_case("generate inline func for #define global extern fn", AllowWarningsNo, - R"SOURCE( -extern void (*fn_ptr)(void); -#define foo fn_ptr - -extern char (*fn_ptr2)(int, float); -#define bar fn_ptr2 - )SOURCE", 4, - "pub extern var fn_ptr: ?extern fn();", - "pub fn foo();", - "pub extern var fn_ptr2: ?extern fn(c_int, f32) -> u8;", - "pub fn bar(arg0: c_int, arg1: f32) -> u8;"); - - - add_parseh_case("#define string", AllowWarningsNo, R"SOURCE( -#define foo "a string" - )SOURCE", 1, "pub const foo: &const u8 = &(c str lit);"); - - add_parseh_case("__cdecl doesn't mess up function pointers", AllowWarningsNo, R"SOURCE( -void foo(void (__cdecl *fn_ptr)(void)); - )SOURCE", 1, "pub extern fn foo(fn_ptr: ?extern fn());"); - - add_parseh_case("comment after integer literal", AllowWarningsNo, R"SOURCE( -#define SDL_INIT_VIDEO 0x00000020 /**< SDL_INIT_VIDEO implies SDL_INIT_EVENTS */ - )SOURCE", 1, "pub const SDL_INIT_VIDEO = 32;"); - - add_parseh_case("zig keywords in C code", AllowWarningsNo, R"SOURCE( -struct comptime { - int defer; -}; - )SOURCE", 2, R"(pub const struct_comptime = extern struct { - @"defer": c_int, -};)", R"(pub const @"comptime" = struct_comptime;)"); - - add_parseh_case("macro defines string literal with octal", AllowWarningsNo, R"SOURCE( -#define FOO "aoeu\023 derp" -#define FOO2 "aoeu\0234 derp" -#define FOO_CHAR '\077' - )SOURCE", 3, - R"(pub const FOO: &const u8 = &(c str lit);)", - R"(pub const FOO2: &const u8 = &(c str lit);)", - R"(pub const FOO_CHAR = 63;)"); -} - -static void print_compiler_invocation(TestCase *test_case) { - printf("%s", zig_exe); - for (size_t i = 0; i < test_case->compiler_args.length; i += 1) { - printf(" %s", test_case->compiler_args.at(i)); - } - printf("\n"); -} - -static void print_linker_invocation(TestCase *test_case) { - printf("%s", zig_exe); - for (size_t i = 0; i < test_case->linker_args.length; i += 1) { - printf(" %s", test_case->linker_args.at(i)); - } - printf("\n"); -} - - -static void print_exe_invocation(TestCase *test_case) { - printf("%s", tmp_exe_path); - for (size_t i = 0; i < test_case->program_args.length; i += 1) { - printf(" %s", test_case->program_args.at(i)); - } - printf("\n"); -} - -static void run_test(TestCase *test_case) { - for (size_t i = 0; i < test_case->source_files.length; i += 1) { - TestSourceFile *test_source = &test_case->source_files.at(i); - os_write_file( - buf_create_from_str(test_source->relative_path), - buf_create_from_str(test_source->source_code)); - } - - Buf zig_stderr = BUF_INIT; - Buf zig_stdout = BUF_INIT; - int err; - Termination term; - if ((err = os_exec_process(zig_exe, test_case->compiler_args, &term, &zig_stderr, &zig_stdout))) { - fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err)); - } - - if (!test_case->is_parseh && test_case->compile_errors.length) { - if (term.how != TerminationIdClean || term.code != 0) { - for (size_t i = 0; i < test_case->compile_errors.length; i += 1) { - const char *err_text = test_case->compile_errors.at(i); - if (!strstr(buf_ptr(&zig_stderr), err_text)) { - printf("\n"); - printf("========= Expected this compile error: =========\n"); - printf("%s\n", err_text); - printf("================================================\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - return; // success - } else { - printf("\nCompile failed with return code 0 (Expected failure):\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nCompile failed:\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - - if (test_case->is_parseh) { - if (buf_len(&zig_stderr) > 0) { - printf("\nparseh emitted warnings:\n"); - printf("------------------------------\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - printf("------------------------------\n"); - if (test_case->allow_warnings == AllowWarningsNo) { - exit(1); - } - } - - for (size_t i = 0; i < test_case->compile_errors.length; i += 1) { - const char *output = test_case->compile_errors.at(i); - - if (!strstr(buf_ptr(&zig_stdout), output)) { - printf("\n"); - printf("========= Expected this output: =========\n"); - printf("%s\n", output); - printf("================================================\n"); - print_compiler_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stdout)); - exit(1); - } - } - } else { - if (test_case->special == TestSpecialLinkStep) { - Buf link_stderr = BUF_INIT; - Buf link_stdout = BUF_INIT; - int err; - Termination term; - if ((err = os_exec_process(zig_exe, test_case->linker_args, &term, &link_stderr, &link_stdout))) { - fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err)); - } - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nLink failed:\n"); - print_linker_invocation(test_case); - printf("%s\n", buf_ptr(&zig_stderr)); - exit(1); - } - } - - Buf program_stderr = BUF_INIT; - Buf program_stdout = BUF_INIT; - os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout); - - if (term.how != TerminationIdClean || term.code != 0) { - printf("\nProgram exited with error\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("%s\n", buf_ptr(&program_stderr)); - exit(1); - } - - if (test_case->output != nullptr && !buf_eql_str(&program_stdout, test_case->output)) { - printf("\n"); - print_compiler_invocation(test_case); - print_exe_invocation(test_case); - printf("==== Test failed. Expected output: ====\n"); - printf("%s\n", test_case->output); - printf("========= Actual output: ==============\n"); - printf("%s\n", buf_ptr(&program_stdout)); - printf("=======================================\n"); - exit(1); - } - } - - for (size_t i = 0; i < test_case->source_files.length; i += 1) { - TestSourceFile *test_source = &test_case->source_files.at(i); - remove(test_source->relative_path); - } -} - -static void run_all_tests(const char *grep_text) { - for (size_t i = 0; i < test_cases.length; i += 1) { - TestCase *test_case = test_cases.at(i); - if (grep_text != nullptr && strstr(test_case->case_name, grep_text) == nullptr) { - continue; - } - - printf("Test %zu/%zu %s...", i + 1, test_cases.length, test_case->case_name); - fflush(stdout); - run_test(test_case); - printf("OK\n"); - } - printf("%zu tests passed.\n", test_cases.length); -} - -static void cleanup(void) { - remove(tmp_source_path); - remove(tmp_h_path); - remove(tmp_exe_path); -} - -static int usage(const char *arg0) { - fprintf(stderr, "Usage: %s [--grep text]\n", arg0); - return 1; -} - -int main(int argc, char **argv) { - const char *grep_text = nullptr; - for (int i = 1; i < argc; i += 1) { - const char *arg = argv[i]; - if (i + 1 >= argc) { - return usage(argv[0]); - } else { - i += 1; - if (strcmp(arg, "--grep") == 0) { - grep_text = argv[i]; - } else { - return usage(argv[0]); - } - } - } - add_parseh_test_cases(); - run_all_tests(grep_text); - cleanup(); -} diff --git a/test/tests.zig b/test/tests.zig index 8e15bcc12..88e223130 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -10,13 +10,14 @@ const mem = std.mem; const fmt = std.fmt; const List = std.list.List; -error TestFailed; +const compare_output = @import("compare_output.zig"); +const build_examples = @import("build_examples.zig"); +const compile_errors = @import("compile_errors.zig"); +const assemble_and_link = @import("assemble_and_link.zig"); +const debug_safety = @import("debug_safety.zig"); +const parseh = @import("parseh.zig"); -pub const compare_output = @import("compare_output.zig"); -pub const build_examples = @import("build_examples.zig"); -pub const compile_errors = @import("compile_errors.zig"); -pub const assemble_and_link = @import("assemble_and_link.zig"); -pub const debug_safety = @import("debug_safety.zig"); +error TestFailed; pub fn addCompareOutputTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { const cases = %%b.allocator.create(CompareOutputContext); @@ -88,6 +89,20 @@ pub fn addAssembleAndLinkTests(b: &build.Builder, test_filter: ?[]const u8) -> & return cases.step; } +pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Step { + const cases = %%b.allocator.create(ParseHContext); + *cases = ParseHContext { + .b = b, + .step = b.step("test-parseh", "Run the C header file parsing tests"), + .test_index = 0, + .test_filter = test_filter, + }; + + parseh.addCases(cases); + + return cases.step; +} + pub const CompareOutputContext = struct { b: &build.Builder, step: &build.Step, @@ -645,3 +660,188 @@ pub const BuildExamplesContext = struct { } } }; + +pub const ParseHContext = struct { + b: &build.Builder, + step: &build.Step, + test_index: usize, + test_filter: ?[]const u8, + + const TestCase = struct { + name: []const u8, + sources: List(SourceFile), + expected_lines: List([]const u8), + allow_warnings: bool, + + const SourceFile = struct { + filename: []const u8, + source: []const u8, + }; + + pub fn addSourceFile(self: &TestCase, filename: []const u8, source: []const u8) { + %%self.sources.append(SourceFile { + .filename = filename, + .source = source, + }); + } + + pub fn addExpectedError(self: &TestCase, text: []const u8) { + %%self.expected_lines.append(text); + } + }; + + const ParseHCmpOutputStep = struct { + step: build.Step, + context: &ParseHContext, + name: []const u8, + test_index: usize, + case: &const TestCase, + + pub fn create(context: &ParseHContext, name: []const u8, case: &const TestCase) -> &ParseHCmpOutputStep { + const allocator = context.b.allocator; + const ptr = %%allocator.create(ParseHCmpOutputStep); + *ptr = ParseHCmpOutputStep { + .step = build.Step.init("ParseHCmpOutput", allocator, make), + .context = context, + .name = name, + .test_index = context.test_index, + .case = case, + }; + context.test_index += 1; + return ptr; + } + + fn make(step: &build.Step) -> %void { + const self = @fieldParentPtr(ParseHCmpOutputStep, "step", step); + const b = self.context.b; + + const root_src = %%os.path.join(b.allocator, "test_artifacts", self.case.sources.items[0].filename); + + var zig_args = List([]const u8).init(b.allocator); + %%zig_args.append("parseh"); + %%zig_args.append(b.pathFromRoot(root_src)); + + %%io.stderr.printf("Test {}/{} {}...", self.test_index+1, self.context.test_index, self.name); + + if (b.verbose) { + printInvocation(b.zig_exe, zig_args.toSliceConst()); + } + + var child = os.ChildProcess.spawn(b.zig_exe, zig_args.toSliceConst(), &b.env_map, + StdIo.Ignore, StdIo.Pipe, StdIo.Pipe, b.allocator) %% |err| + { + debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err)); + }; + + const term = child.wait() %% |err| { + debug.panic("Unable to spawn {}: {}\n", b.zig_exe, @errorName(err)); + }; + switch (term) { + Term.Clean => |code| { + if (code != 0) { + %%io.stderr.printf("Compilation failed with exit code {}\n", code); + return error.TestFailed; + } + }, + Term.Signal => |code| { + %%io.stderr.printf("Compilation failed with signal {}\n", code); + return error.TestFailed; + }, + else => { + %%io.stderr.printf("Compilation terminated unexpectedly\n"); + return error.TestFailed; + }, + }; + + var stdout_buf = %%Buffer0.initEmpty(b.allocator); + var stderr_buf = %%Buffer0.initEmpty(b.allocator); + + %%(??child.stdout).readAll(&stdout_buf); + %%(??child.stderr).readAll(&stderr_buf); + + const stdout = stdout_buf.toSliceConst(); + const stderr = stderr_buf.toSliceConst(); + + if (stderr.len != 0 and !self.case.allow_warnings) { + %%io.stderr.printf( + \\====== parseh emitted warnings: ============ + \\{} + \\============================================ + \\ + , stderr); + return error.TestFailed; + } + + for (self.case.expected_lines.toSliceConst()) |expected_line| { + if (mem.indexOf(u8, stdout, expected_line) == null) { + %%io.stderr.printf( + \\ + \\========= Expected this output: ================ + \\{} + \\================================================ + \\{} + \\ + , expected_line, stdout); + return error.TestFailed; + } + } + %%io.stderr.printf("OK\n"); + } + }; + + fn printInvocation(exe_path: []const u8, args: []const []const u8) { + %%io.stderr.printf("{}", exe_path); + for (args) |arg| { + %%io.stderr.printf(" {}", arg); + } + %%io.stderr.printf("\n"); + } + + pub fn create(self: &ParseHContext, allow_warnings: bool, name: []const u8, + source: []const u8, expected_lines: ...) -> &TestCase + { + const tc = %%self.b.allocator.create(TestCase); + *tc = TestCase { + .name = name, + .sources = List(TestCase.SourceFile).init(self.b.allocator), + .expected_lines = List([]const u8).init(self.b.allocator), + .allow_warnings = allow_warnings, + }; + tc.addSourceFile("source.h", source); + comptime var arg_i = 0; + inline while (arg_i < expected_lines.len; arg_i += 1) { + // TODO mem.dupe is because of issue #336 + tc.addExpectedError(%%mem.dupe(self.b.allocator, u8, expected_lines[arg_i])); + } + return tc; + } + + pub fn add(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create(false, name, source, expected_lines); + self.addCase(tc); + } + + pub fn addAllowWarnings(self: &ParseHContext, name: []const u8, source: []const u8, expected_lines: ...) { + const tc = self.create(true, name, source, expected_lines); + self.addCase(tc); + } + + pub fn addCase(self: &ParseHContext, case: &const TestCase) { + const b = self.b; + + const annotated_case_name = %%fmt.allocPrint(self.b.allocator, "parseh {}", case.name); + if (const filter ?= self.test_filter) { + if (mem.indexOf(u8, annotated_case_name, filter) == null) + return; + } + + const parseh_and_cmp = ParseHCmpOutputStep.create(self, annotated_case_name, case); + self.step.dependOn(&parseh_and_cmp.step); + + for (case.sources.toSliceConst()) |src_file| { + const expanded_src_path = %%os.path.join(b.allocator, "test_artifacts", src_file.filename); + const write_src = b.addWriteFile(expanded_src_path, src_file.source); + parseh_and_cmp.step.dependOn(&write_src.step); + } + } +}; From 8654bc18104d64c7a7f9f80bdba75ed4e0c005fa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 20 Apr 2017 02:26:36 -0400 Subject: [PATCH 10/10] delete test_artifacts directory when tests complete * add std.os.deleteTree * add std.os.deleteDir * add std.os.page_size * add std.os API for iterating over directories * refactor duplication in build.zig * update documentation on how to run tests --- README.md | 6 +- build.zig | 51 ++++---------- std/build.zig | 36 +++++++++- std/io.zig | 10 ++- std/list.zig | 10 ++- std/mem.zig | 2 + std/os/index.zig | 178 +++++++++++++++++++++++++++++++++++++++++++++++ std/os/linux.zig | 17 +++++ test/tests.zig | 21 ++++++ 9 files changed, 281 insertions(+), 50 deletions(-) diff --git a/README.md b/README.md index 524d0235c..2e29f9b49 100644 --- a/README.md +++ b/README.md @@ -45,8 +45,8 @@ compromises backward compatibility. * Release mode produces heavily optimized code. What other projects call "Link Time Optimization" Zig does automatically. * Mark functions as tests and automatically run them with `zig test`. - * Currently supported architectures: `x86_64`, `i386` - * Currently supported operating systems: linux, macosx + * Currently supported architectures: `x86_64` + * Currently supported operating systems: linux * Friendly toward package maintainers. Reproducible build, bootstrapping process carefully documented. Issues filed by package maintainers are considered especially important. @@ -103,7 +103,7 @@ cd build cmake .. -DCMAKE_INSTALL_PREFIX=$(pwd) -DZIG_LIBC_LIB_DIR=$(dirname $(cc -print-file-name=crt1.o)) -DZIG_LIBC_INCLUDE_DIR=$(echo -n | cc -E -x c - -v 2>&1 | grep -B1 "End of search list." | head -n1 | cut -c 2- | sed "s/ .*//") -DZIG_LIBC_STATIC_LIB_DIR=$(dirname $(cc -print-file-name=crtbegin.o)) make make install -./run_tests +./zig build --build-file ../build.zig test ``` ### Release / Install Build diff --git a/build.zig b/build.zig index 918a629cf..378ea66c8 100644 --- a/build.zig +++ b/build.zig @@ -5,44 +5,19 @@ 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 behavior_tests = b.step("test-behavior", "Run the behavior tests"); - test_step.dependOn(behavior_tests); - for ([]bool{false, true}) |release| { - for ([]bool{false, true}) |link_libc| { - const these_tests = b.addTest("test/behavior.zig"); - these_tests.setNamePrefix(b.fmt("behavior-{}-{} ", - if (release) "release" else "debug", - if (link_libc) "c" else "bare")); - these_tests.setFilter(test_filter); - these_tests.setRelease(release); - if (link_libc) { - these_tests.linkLibrary("c"); - } - behavior_tests.dependOn(&these_tests.step); - } - } + const cleanup = b.addRemoveDirTree("test_artifacts"); + test_step.dependOn(&cleanup.step); - const std_lib_tests = b.step("test-std", "Run the standard library tests"); - test_step.dependOn(std_lib_tests); - for ([]bool{false, true}) |release| { - for ([]bool{false, true}) |link_libc| { - const these_tests = b.addTest("std/index.zig"); - these_tests.setNamePrefix(b.fmt("std-{}-{} ", - if (release) "release" else "debug", - if (link_libc) "c" else "bare")); - these_tests.setFilter(test_filter); - these_tests.setRelease(release); - if (link_libc) { - these_tests.linkLibrary("c"); - } - std_lib_tests.dependOn(&these_tests.step); - } - } + cleanup.step.dependOn(tests.addPkgTests(b, test_filter, + "test/behavior.zig", "behavior", "Run the behavior tests")); - 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)); - test_step.dependOn(tests.addDebugSafetyTests(b, test_filter)); - test_step.dependOn(tests.addParseHTests(b, test_filter)); + cleanup.step.dependOn(tests.addPkgTests(b, test_filter, + "std/index.zig", "std", "Run the standard library tests")); + + cleanup.step.dependOn(tests.addCompareOutputTests(b, test_filter)); + cleanup.step.dependOn(tests.addBuildExampleTests(b, test_filter)); + cleanup.step.dependOn(tests.addCompileErrorTests(b, test_filter)); + cleanup.step.dependOn(tests.addAssembleAndLinkTests(b, test_filter)); + cleanup.step.dependOn(tests.addDebugSafetyTests(b, test_filter)); + cleanup.step.dependOn(tests.addParseHTests(b, test_filter)); } diff --git a/std/build.zig b/std/build.zig index 20fb4cad5..d22d6b660 100644 --- a/std/build.zig +++ b/std/build.zig @@ -195,6 +195,12 @@ pub const Builder = struct { return log_step; } + pub fn addRemoveDirTree(self: &Builder, dir_path: []const u8) -> &RemoveDirStep { + const remove_dir_step = %%self.allocator.create(RemoveDirStep); + *remove_dir_step = RemoveDirStep.init(self, dir_path); + return remove_dir_step; + } + pub fn version(self: &const Builder, major: u32, minor: u32, patch: u32) -> Version { Version { .major = major, @@ -1548,10 +1554,12 @@ pub const WriteFileStep = struct { 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.stderr.printf("unable to make path {}: {}\n", full_path_dir, @errorName(err)); + return err; }; io.writeFile(full_path, self.data, self.builder.allocator) %% |err| { - debug.panic("unable to write {}: {}\n", full_path, @errorName(err)); + %%io.stderr.printf("unable to write {}: {}\n", full_path, @errorName(err)); + return err; }; } }; @@ -1576,6 +1584,30 @@ pub const LogStep = struct { } }; +pub const RemoveDirStep = struct { + step: Step, + builder: &Builder, + dir_path: []const u8, + + pub fn init(builder: &Builder, dir_path: []const u8) -> RemoveDirStep { + return RemoveDirStep { + .builder = builder, + .step = Step.init(builder.fmt("RemoveDir {}", dir_path), builder.allocator, make), + .dir_path = dir_path, + }; + } + + fn make(step: &Step) -> %void { + const self = @fieldParentPtr(RemoveDirStep, "step", step); + + const full_path = self.builder.pathFromRoot(self.dir_path); + os.deleteTree(self.builder.allocator, full_path) %% |err| { + %%io.stderr.printf("Unable to remove {}: {}\n", full_path, @errorName(err)); + return err; + }; + } +}; + pub const Step = struct { name: []const u8, makeFn: fn(self: &Step) -> %void, diff --git a/std/io.zig b/std/io.zig index 03960867b..e9c70d961 100644 --- a/std/io.zig +++ b/std/io.zig @@ -57,8 +57,6 @@ error NoMem; error Unseekable; error Eof; -const buffer_size = 4 * 1024; - pub const OpenRead = 0b0001; pub const OpenWrite = 0b0010; pub const OpenCreate = 0b0100; @@ -66,7 +64,7 @@ pub const OpenTruncate = 0b1000; pub const OutStream = struct { fd: i32, - buffer: [buffer_size]u8, + buffer: [os.page_size]u8, index: usize, /// `path` may need to be copied in memory to add a null terminating byte. In this case @@ -97,7 +95,7 @@ pub const OutStream = struct { } pub fn write(self: &OutStream, bytes: []const u8) -> %void { - if (bytes.len >= buffer_size) { + if (bytes.len >= self.buffer.len) { %return self.flush(); return os.posixWrite(self.fd, bytes); } @@ -329,7 +327,7 @@ pub const InStream = struct { } pub fn readAll(is: &InStream, buf: &Buffer0) -> %void { - %return buf.resize(buffer_size); + %return buf.resize(os.page_size); var actual_buf_len: usize = 0; while (true) { @@ -341,7 +339,7 @@ pub const InStream = struct { return buf.resize(actual_buf_len); } - %return buf.resize(actual_buf_len + buffer_size); + %return buf.resize(actual_buf_len + os.page_size); } } }; diff --git a/std/list.zig b/std/list.zig index d01e3a88a..b20cd06fe 100644 --- a/std/list.zig +++ b/std/list.zig @@ -61,10 +61,15 @@ pub fn List(comptime T: type) -> type{ l.len = new_length; return result; } + + pub fn pop(self: &Self) -> T { + self.len -= 1; + return self.items[self.len]; + } } } -test "basicListTest" { +test "basic list test" { var list = List(i32).init(&debug.global_allocator); defer list.deinit(); @@ -75,4 +80,7 @@ test "basicListTest" { {var i: usize = 0; while (i < 10; i += 1) { assert(list.items[i] == i32(i + 1)); }} + + assert(list.pop() == 10); + assert(list.len == 9); } diff --git a/std/mem.zig b/std/mem.zig index 66e119b50..6a2d854fc 100644 --- a/std/mem.zig +++ b/std/mem.zig @@ -9,6 +9,8 @@ error NoMem; pub const Allocator = struct { allocFn: fn (self: &Allocator, n: usize) -> %[]u8, + /// Note that old_mem may be a slice of length 0, in which case reallocFn + /// should simply call allocFn reallocFn: fn (self: &Allocator, old_mem: []u8, new_size: usize) -> %[]u8, freeFn: fn (self: &Allocator, mem: []u8), diff --git a/std/os/index.zig b/std/os/index.zig index 7da623d3c..011370679 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -17,6 +17,8 @@ pub const line_sep = switch (@compileVar("os")) { else => "\n", }; +pub const page_size = 4 * 1024; + const debug = @import("../debug.zig"); const assert = debug.assert; @@ -32,6 +34,7 @@ const cstr = @import("../cstr.zig"); const io = @import("../io.zig"); const base64 = @import("../base64.zig"); +const List = @import("../list.zig").List; error Unexpected; error SystemResources; @@ -46,6 +49,7 @@ error SymLinkLoop; error ReadOnlyFileSystem; error LinkQuotaExceeded; error RenameAcrossMountPoints; +error DirNotEmpty; /// Fills `buf` with random bytes. If linking against libc, this calls the /// appropriate OS-specific library call. Otherwise it uses the zig standard @@ -585,3 +589,177 @@ pub fn makePath(allocator: &Allocator, full_path: []const u8) -> %void { // TODO stat the file and return an error if it's not a directory }; } + +/// Returns ::error.DirNotEmpty if the directory is not empty. +/// To delete a directory recursively, see ::deleteTree +pub fn deleteDir(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.rmdir(path_buf.ptr)); + if (err > 0) { + return switch (err) { + errno.EACCES, errno.EPERM => error.AccessDenied, + errno.EBUSY => error.FileBusy, + errno.EFAULT, errno.EINVAL => unreachable, + errno.ELOOP => error.SymLinkLoop, + errno.ENAMETOOLONG => error.NameTooLong, + errno.ENOENT => error.FileNotFound, + errno.ENOMEM => error.SystemResources, + errno.ENOTDIR => error.NotDir, + errno.EEXIST, errno.ENOTEMPTY => error.DirNotEmpty, + errno.EROFS => error.ReadOnlyFileSystem, + else => error.Unexpected, + }; + } +} + +/// Whether ::full_path describes a symlink, file, or directory, this function +/// removes it. If it cannot be removed because it is a non-empty directory, +/// this function recursively removes its entries and then tries again. +// TODO non-recursive implementation +pub fn deleteTree(allocator: &Allocator, full_path: []const u8) -> %void { +start_over: + // First, try deleting the item as a file. This way we don't follow sym links. + try (deleteFile(allocator, full_path)) { + return; + } else |err| { + if (err == error.FileNotFound) + return; + if (err != error.IsDir) + return err; + } + { + var dir = Dir.open(allocator, full_path) %% |err| { + if (err == error.FileNotFound) + return; + if (err == error.NotDir) + goto start_over; + return err; + }; + defer dir.close(); + + var full_entry_buf = List(u8).init(allocator); + defer full_entry_buf.deinit(); + + while (true) { + const entry = (%return dir.next()) ?? break; + + %return full_entry_buf.resize(full_path.len + entry.name.len + 1); + const full_entry_path = full_entry_buf.toSlice(); + mem.copy(u8, full_entry_path, full_path); + full_entry_path[full_path.len] = '/'; + mem.copy(u8, full_entry_path[full_path.len + 1...], entry.name); + + %return deleteTree(allocator, full_entry_path); + } + } + return deleteDir(allocator, full_path); +} + +pub const Dir = struct { + fd: i32, + allocator: &Allocator, + buf: []u8, + index: usize, + end_index: usize, + + const LinuxEntry = extern struct { + d_ino: usize, + d_off: usize, + d_reclen: u16, + d_name: u8, // field address is the address of first byte of name + }; + + pub const Entry = struct { + name: []const u8, + kind: Kind, + + pub const Kind = enum { + BlockDevice, + CharacterDevice, + Directory, + NamedPipe, + SymLink, + File, + UnixDomainSocket, + Unknown, + }; + }; + + pub fn open(allocator: &Allocator, dir_path: []const u8) -> %Dir { + const fd = %return posixOpen(dir_path, posix.O_RDONLY|posix.O_DIRECTORY|posix.O_CLOEXEC, 0, allocator); + return Dir { + .allocator = allocator, + .fd = fd, + .index = 0, + .end_index = 0, + .buf = []u8{}, + }; + } + + pub fn close(self: &Dir) { + self.allocator.free(self.buf); + posixClose(self.fd); + } + + /// Memory such as file names referenced in this returned entry becomes invalid + /// with subsequent calls to next, as well as when this ::Dir is deinitialized. + pub fn next(self: &Dir) -> %?Entry { + start_over: + if (self.index >= self.end_index) { + if (self.buf.len == 0) { + self.buf = %return self.allocator.alloc(u8, 2); //page_size); + } + + while (true) { + const result = posix.getdents(self.fd, self.buf.ptr, self.buf.len); + const err = linux.getErrno(result); + if (err > 0) { + switch (err) { + errno.EBADF, errno.EFAULT, errno.ENOTDIR => unreachable, + errno.EINVAL => { + self.buf = %return self.allocator.realloc(u8, self.buf, self.buf.len * 2); + continue; + }, + else => return error.Unexpected, + }; + } + if (result == 0) + return null; + self.index = 0; + self.end_index = result; + break; + } + } + const linux_entry = @ptrcast(&LinuxEntry, &self.buf[self.index]); + const next_index = self.index + linux_entry.d_reclen; + self.index = next_index; + + const name = (&linux_entry.d_name)[0...cstr.len(&linux_entry.d_name)]; + + // skip . and .. entries + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + goto start_over; + } + + const type_char = self.buf[next_index - 1]; + const entry_kind = switch (type_char) { + posix.DT_BLK => Entry.Kind.BlockDevice, + posix.DT_CHR => Entry.Kind.CharacterDevice, + posix.DT_DIR => Entry.Kind.Directory, + posix.DT_FIFO => Entry.Kind.NamedPipe, + posix.DT_LNK => Entry.Kind.SymLink, + posix.DT_REG => Entry.Kind.File, + posix.DT_SOCK => Entry.Kind.UnixDomainSocket, + else => Entry.Kind.Unknown, + }; + return Entry { + .name = name, + .kind = entry_kind, + }; + } +}; diff --git a/std/os/linux.zig b/std/os/linux.zig index 7c841fafa..1ebade3ba 100644 --- a/std/os/linux.zig +++ b/std/os/linux.zig @@ -241,6 +241,15 @@ pub const AF_NFC = PF_NFC; pub const AF_VSOCK = PF_VSOCK; pub const AF_MAX = PF_MAX; +pub const DT_UNKNOWN = 0; +pub const DT_FIFO = 1; +pub const DT_CHR = 2; +pub const DT_DIR = 4; +pub const DT_BLK = 6; +pub const DT_REG = 8; +pub const DT_LNK = 10; +pub const DT_SOCK = 12; +pub const DT_WHT = 14; fn unsigned(s: i32) -> u32 { *@ptrcast(&u32, &s) } fn signed(s: u32) -> i32 { *@ptrcast(&i32, &s) } @@ -273,6 +282,10 @@ pub fn getcwd(buf: &u8, size: usize) -> usize { arch.syscall2(arch.SYS_getcwd, usize(buf), size) } +pub fn getdents(fd: i32, dirp: &u8, count: usize) -> usize { + arch.syscall3(arch.SYS_getdents, usize(fd), usize(dirp), usize(count)) +} + pub fn mkdir(path: &const u8, mode: usize) -> usize { arch.syscall2(arch.SYS_mkdir, usize(path), mode) } @@ -291,6 +304,10 @@ pub fn read(fd: i32, buf: &u8, count: usize) -> usize { arch.syscall3(arch.SYS_read, usize(fd), usize(buf), count) } +pub fn rmdir(path: &const u8) -> usize { + arch.syscall1(arch.SYS_rmdir, usize(path)) +} + pub fn symlink(existing: &const u8, new: &const u8) -> usize { arch.syscall2(arch.SYS_symlink, usize(existing), usize(new)) } diff --git a/test/tests.zig b/test/tests.zig index 88e223130..d76085633 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -103,6 +103,27 @@ pub fn addParseHTests(b: &build.Builder, test_filter: ?[]const u8) -> &build.Ste return cases.step; } +pub fn addPkgTests(b: &build.Builder, test_filter: ?[]const u8, root_src: []const u8, + name:[] const u8, desc: []const u8) -> &build.Step +{ + const step = b.step(b.fmt("test-{}", name), desc); + for ([]bool{false, true}) |release| { + for ([]bool{false, true}) |link_libc| { + const these_tests = b.addTest(root_src); + these_tests.setNamePrefix(b.fmt("{}-{}-{} ", name, + if (release) "release" else "debug", + if (link_libc) "c" else "bare")); + these_tests.setFilter(test_filter); + these_tests.setRelease(release); + if (link_libc) { + these_tests.linkLibrary("c"); + } + step.dependOn(&these_tests.step); + } + } + return step; +} + pub const CompareOutputContext = struct { b: &build.Builder, step: &build.Step,