diff --git a/build.zig b/build.zig new file mode 100644 index 0000000000..24653ab155 --- /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 32b22e8bda..058fa7a884 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 ca3cb6554a..11c0383291 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 99bfb0adee..66e119b503 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 c154f59ee3..7da623d3ce 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 d79b8f045e..7c841fafad 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 e8b650567a..66c07ad6de 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 ea91ebb4cd..dfb2efeec4 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 21ab1baaf8..7e9b31b241 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 aad51dbe1f..1be5f69bab 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 0000000000..5cdaa94fe7 --- /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 0000000000..f971d7eef1 --- /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"); + } +}; +