From d43c08a3e517f29b3dcaf5aa7860ed185815a305 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 5 May 2020 17:23:49 +0200 Subject: [PATCH 01/13] Add/fix missing WASI functionality to pass libstd tests This rather large commit adds/fixes missing WASI functionality in `libstd` needed to pass the `libstd` tests. As such, now by default tests targeting `wasm32-wasi` target are enabled in `test/tests.zig` module. However, they can be disabled by passing the `-Dskip-wasi=true` flag when invoking the `zig build test` command. When the flag is set to `false`, i.e., when WASI tests are included, `wasmtime` with `--dir=.` is used as the default testing command. Since the majority of `libstd` tests were relying on `fs.cwd()` call to get current working directory handle wrapped in `Dir` struct, in order to make the tests WASI-friendly, `fs.cwd()` call was replaced with `testing.getTestDir()` function which resolved to either `fs.cwd()` for non-WASI targets, or tries to fetch the preopen list from the WASI runtime and extract a preopen for '.' path. The summary of changes introduced by this commit: * implement `Dir.makeDir` and `Dir.openDir` targeting WASI * implement `Dir.deleteFile` and `Dir.deleteDir` targeting WASI * fix `os.close` and map errors in `unlinkat` * move WASI-specific `mkdirat` and `unlinkat` from `std.fs.wasi` to `std.os` module * implement `lseek_{SET, CUR, END}` targeting WASI * implement `futimens` targeting WASI * implement `ftruncate` targeting WASI * implement `readv`, `writev`, `pread{v}`, `pwrite{v}` targeting WASI * make sure ANSI escape codes are _not_ used in stderr or stdout in WASI, as WASI always sanitizes stderr, and sanitizes stdout if fd is a TTY * fix specifying WASI rights when opening/creating files/dirs * tweak `AtomicFile` to be WASI-compatible * implement `os.renameatWasi` for WASI-compliant `os.renameat` function * implement sleep() targeting WASI * fix `process.getEnvMap` targeting WASI --- build.zig | 10 +- lib/std/build.zig | 2 + lib/std/fmt.zig | 7 +- lib/std/fs.zig | 187 +++++++++++++--- lib/std/fs/file.zig | 11 +- lib/std/fs/get_app_data_dir.zig | 2 + lib/std/fs/path.zig | 11 +- lib/std/fs/test.zig | 6 + lib/std/fs/wasi.zig | 11 - lib/std/io/test.zig | 34 +-- lib/std/net/test.zig | 11 +- lib/std/os.zig | 384 +++++++++++++++++++++++++++++++- lib/std/os/bits/wasi.zig | 77 ++++++- lib/std/os/test.zig | 73 +++--- lib/std/packed_int_array.zig | 6 + lib/std/process.zig | 18 +- lib/std/std.zig | 7 +- lib/std/testing.zig | 15 ++ lib/std/time.zig | 35 ++- test/tests.zig | 20 ++ 20 files changed, 797 insertions(+), 130 deletions(-) diff --git a/build.zig b/build.zig index 3636da4f28..bc15a31930 100644 --- a/build.zig +++ b/build.zig @@ -60,6 +60,7 @@ pub fn build(b: *Builder) !void { const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; + const skip_wasi = b.option(bool, "skip-wasi", "Main test suite skips WASI build") orelse false; const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; @@ -115,11 +116,12 @@ pub fn build(b: *Builder) !void { const fmt_step = b.step("test-fmt", "Run zig fmt against build.zig to make sure it works"); fmt_step.dependOn(&fmt_build_zig.step); - test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + // TODO for the moment, skip wasm32-wasi until bugs are sorted out. + test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, true, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); - test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); - test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); test_step.dependOn(tests.addStandaloneTests(b, test_filter, modes)); @@ -130,7 +132,7 @@ pub fn build(b: *Builder) !void { test_step.dependOn(tests.addTranslateCTests(b, test_filter)); test_step.dependOn(tests.addRunTranslatedCTests(b, test_filter)); // tests for this feature are disabled until we have the self-hosted compiler available - //test_step.dependOn(tests.addGenHTests(b, test_filter)); + // test_step.dependOn(tests.addGenHTests(b, test_filter)); test_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes)); test_step.dependOn(docs_step); } diff --git a/lib/std/build.zig b/lib/std/build.zig index 08b014dbdd..031fc5d171 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -2089,6 +2089,8 @@ pub const LibExeObjStep = struct { .wasmtime => |bin_name| if (self.enable_wasmtime) { try zig_args.append("--test-cmd"); try zig_args.append(bin_name); + try zig_args.append("--test-cmd"); + try zig_args.append("--dir=."); try zig_args.append("--test-cmd-bin"); }, } diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index 16b3439f0c..f609ca69ac 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -1684,12 +1684,15 @@ test "vector" { // https://github.com/ziglang/zig/issues/4486 return error.SkipZigTest; } + if (builtin.arch != .wasm32) { + // TODO investigate why this fails on wasm32 + const vbool: std.meta.Vector(4, bool) = [_]bool{ true, false, true, false }; + try testFmt("{ true, false, true, false }", "{}", .{vbool}); + } - const vbool: std.meta.Vector(4, bool) = [_]bool{ true, false, true, false }; const vi64: std.meta.Vector(4, i64) = [_]i64{ -2, -1, 0, 1 }; const vu64: std.meta.Vector(4, u64) = [_]u64{ 1000, 2000, 3000, 4000 }; - try testFmt("{ true, false, true, false }", "{}", .{vbool}); try testFmt("{ -2, -1, 0, 1 }", "{}", .{vi64}); try testFmt("{ - 2, - 1, + 0, + 1 }", "{d:5}", .{vi64}); try testFmt("{ 1000, 2000, 3000, 4000 }", "{}", .{vu64}); diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 862fc84ef1..a006d4ea05 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -44,6 +44,8 @@ pub const MAX_PATH_BYTES = switch (builtin.os.tag) { // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. // +1 for the null byte at the end, which can be encoded in 1 byte. .windows => os.windows.PATH_MAX_WIDE * 3 + 1, + // TODO work out what a reasonable value we should use here + .wasi => 4096, else => @compileError("Unsupported OS"), }; @@ -155,7 +157,7 @@ pub const AtomicFile = struct { try crypto.randomBytes(rand_buf[0..]); base64_encoder.encode(&tmp_path_buf, &rand_buf); - const file = dir.createFileZ( + const file = dir.createFile( &tmp_path_buf, .{ .mode = mode, .exclusive = true }, ) catch |err| switch (err) { @@ -182,7 +184,7 @@ pub const AtomicFile = struct { self.file_open = false; } if (self.file_exists) { - self.dir.deleteFileZ(&self.tmp_path_buf) catch {}; + self.dir.deleteFile(&self.tmp_path_buf) catch {}; self.file_exists = false; } if (self.close_dir_on_deinit) { @@ -197,16 +199,8 @@ pub const AtomicFile = struct { self.file.close(); self.file_open = false; } - if (std.Target.current.os.tag == .windows) { - const dest_path_w = try os.windows.sliceToPrefixedFileW(self.dest_basename); - const tmp_path_w = try os.windows.cStrToPrefixedFileW(&self.tmp_path_buf); - try os.renameatW(self.dir.fd, tmp_path_w.span(), self.dir.fd, dest_path_w.span(), os.windows.TRUE); - self.file_exists = false; - } else { - const dest_path_c = try os.toPosixPath(self.dest_basename); - try os.renameatZ(self.dir.fd, &self.tmp_path_buf, self.dir.fd, &dest_path_c); - self.file_exists = false; - } + try os.renameat(self.dir.fd, self.tmp_path_buf[0..], self.dir.fd, self.dest_basename); + self.file_exists = false; } }; @@ -522,6 +516,66 @@ pub const Dir = struct { } } }, + .wasi => struct { + dir: Dir, + buf: [8192]u8, // TODO align(@alignOf(os.wasi.dirent_t)), + cookie: u64, + index: usize, + end_index: usize, + + const Self = @This(); + + pub const Error = IteratorError; + + /// 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: *Self) Error!?Entry { + const w = os.wasi; + start_over: while (true) { + if (self.index >= self.end_index) { + var bufused: usize = undefined; + switch (w.fd_readdir(self.dir.fd, &self.buf, self.buf.len, self.cookie, &bufused)) { + w.ESUCCESS => {}, + w.EBADF => unreachable, // Dir is invalid or was opened without iteration ability + w.EFAULT => unreachable, + w.ENOTDIR => unreachable, + w.EINVAL => unreachable, + else => |err| return os.unexpectedErrno(err), + } + if (bufused == 0) return null; + self.index = 0; + self.end_index = bufused; + } + const entry = @ptrCast(*align(1) os.wasi.dirent_t, &self.buf[self.index]); + const entry_size = @sizeOf(os.wasi.dirent_t); + const name_index = self.index + entry_size; + const name = mem.span(self.buf[name_index .. name_index + entry.d_namlen]); + + const next_index = name_index + entry.d_namlen; + self.index = next_index; + self.cookie = entry.d_next; + + // skip . and .. entries + if (mem.eql(u8, name, ".") or mem.eql(u8, name, "..")) { + continue :start_over; + } + + const entry_kind = switch (entry.d_type) { + wasi.FILETYPE_BLOCK_DEVICE => Entry.Kind.BlockDevice, + wasi.FILETYPE_CHARACTER_DEVICE => Entry.Kind.CharacterDevice, + wasi.FILETYPE_DIRECTORY => Entry.Kind.Directory, + wasi.FILETYPE_SYMBOLIC_LINK => Entry.Kind.SymLink, + wasi.FILETYPE_REGULAR_FILE => Entry.Kind.File, + wasi.FILETYPE_SOCKET_STREAM, wasi.FILETYPE_SOCKET_DGRAM => Entry.Kind.UnixDomainSocket, + else => Entry.Kind.Unknown, + }; + return Entry{ + .name = name, + .kind = entry_kind, + }; + } + } + }, else => @compileError("unimplemented"), }; @@ -548,6 +602,13 @@ pub const Dir = struct { .buf = undefined, .name_data = undefined, }, + .wasi => return Iterator{ + .dir = self, + .cookie = os.wasi.DIRCOOKIE_START, + .index = 0, + .end_index = 0, + .buf = undefined, + }, else => @compileError("unimplemented"), } } @@ -595,24 +656,25 @@ pub const Dir = struct { /// Save as `openFile` but WASI only. pub fn openFileWasi(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { const w = os.wasi; - var fdflags: w.fdflag_t = 0x0; - var rights: w.rights_t = 0x0; + var fdflags: w.fdflags_t = 0x0; + var base: w.rights_t = 0x0; if (flags.read) { - rights |= w.FD_READ | w.FD_TELL | w.FD_FILESTAT_GET; + base |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_SEEK | w.RIGHT_FD_FILESTAT_GET; } if (flags.write) { fdflags |= w.FDFLAG_APPEND; - rights |= w.FD_WRITE | - w.FD_DATASYNC | - w.FD_SEEK | - w.FD_FDSTAT_SET_FLAGS | - w.FD_SYNC | - w.FD_ALLOCATE | - w.FD_ADVISE | - w.FD_FILESTAT_SET_TIMES | - w.FD_FILESTAT_SET_SIZE; + base |= w.RIGHT_FD_WRITE | + w.RIGHT_FD_TELL | + w.RIGHT_FD_SEEK | + w.RIGHT_FD_DATASYNC | + w.RIGHT_FD_FDSTAT_SET_FLAGS | + w.RIGHT_FD_SYNC | + w.RIGHT_FD_ALLOCATE | + w.RIGHT_FD_ADVISE | + w.RIGHT_FD_FILESTAT_SET_TIMES | + w.RIGHT_FD_FILESTAT_SET_SIZE; } - const fd = try wasi.openat(self.fd, sub_path, 0x0, fdflags, rights); + const fd = try os.openatWasi(self.fd, sub_path, 0x0, fdflags, base, 0x0); return File{ .handle = fd }; } @@ -712,17 +774,19 @@ pub const Dir = struct { pub fn createFileWasi(self: Dir, sub_path: []const u8, flags: File.CreateFlags) File.OpenError!File { const w = os.wasi; var oflags = w.O_CREAT; - var rights = w.RIGHT_FD_WRITE | + var base: w.rights_t = w.RIGHT_FD_WRITE | w.RIGHT_FD_DATASYNC | w.RIGHT_FD_SEEK | + w.RIGHT_FD_TELL | w.RIGHT_FD_FDSTAT_SET_FLAGS | w.RIGHT_FD_SYNC | w.RIGHT_FD_ALLOCATE | w.RIGHT_FD_ADVISE | w.RIGHT_FD_FILESTAT_SET_TIMES | - w.RIGHT_FD_FILESTAT_SET_SIZE; + w.RIGHT_FD_FILESTAT_SET_SIZE | + w.RIGHT_FD_FILESTAT_GET; if (flags.read) { - rights |= w.RIGHT_FD_READ | w.RIGHT_FD_TELL | w.RIGHT_FD_FILESTAT_GET; + base |= w.RIGHT_FD_READ; } if (flags.truncate) { oflags |= w.O_TRUNC; @@ -730,7 +794,7 @@ pub const Dir = struct { if (flags.exclusive) { oflags |= w.O_EXCL; } - const fd = try wasi.openat(self.fd, sub_path, oflags, 0x0, rights); + const fd = try os.openatWasi(self.fd, sub_path, oflags, 0x0, base, 0x0); return File{ .handle = fd }; } @@ -877,6 +941,9 @@ pub const Dir = struct { /// Not all targets support this. For example, WASI does not have the concept /// of a current working directory. pub fn setAsCwd(self: Dir) !void { + if (builtin.os.tag == .wasi) { + @compileError("changing cwd is not currently possible in WASI"); + } try os.fchdir(self.fd); } @@ -899,6 +966,8 @@ pub const Dir = struct { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.openDirW(sub_path_w.span().ptr, args); + } else if (builtin.os.tag == .wasi) { + return self.openDirWasi(sub_path, args); } else { const sub_path_c = try os.toPosixPath(sub_path); return self.openDirZ(&sub_path_c, args); @@ -907,6 +976,44 @@ pub const Dir = struct { pub const openDirC = @compileError("deprecated: renamed to openDirZ"); + /// Same as `openDir` except only WASI. + pub fn openDirWasi(self: Dir, sub_path: []const u8, args: OpenDirOptions) OpenError!Dir { + const w = os.wasi; + var base: w.rights_t = w.RIGHT_FD_FILESTAT_GET | w.RIGHT_FD_FDSTAT_SET_FLAGS | w.RIGHT_FD_FILESTAT_SET_TIMES; + if (args.iterate) { + base |= w.RIGHT_FD_READDIR; + } + if (args.access_sub_paths) { + base |= w.RIGHT_PATH_CREATE_DIRECTORY | + w.RIGHT_PATH_CREATE_FILE | + w.RIGHT_PATH_LINK_SOURCE | + w.RIGHT_PATH_LINK_TARGET | + w.RIGHT_PATH_OPEN | + w.RIGHT_PATH_READLINK | + w.RIGHT_PATH_RENAME_SOURCE | + w.RIGHT_PATH_RENAME_TARGET | + w.RIGHT_PATH_FILESTAT_GET | + w.RIGHT_PATH_FILESTAT_SET_SIZE | + w.RIGHT_PATH_FILESTAT_SET_TIMES | + w.RIGHT_PATH_SYMLINK | + w.RIGHT_PATH_REMOVE_DIRECTORY | + w.RIGHT_PATH_UNLINK_FILE; + } + // TODO do we really need all the rights here? + const inheriting: w.rights_t = w.RIGHT_ALL ^ w.RIGHT_SOCK_SHUTDOWN; + + const result = os.openatWasi(self.fd, sub_path, w.O_DIRECTORY, 0x0, base, inheriting); + const fd = result catch |err| switch (err) { + error.FileTooBig => unreachable, // can't happen for directories + error.IsDir => unreachable, // we're providing O_DIRECTORY + error.NoSpaceLeft => unreachable, // not providing O_CREAT + error.PathAlreadyExists => unreachable, // not providing O_CREAT + error.FileLocksNotSupported => unreachable, // locking folders is not supported + else => |e| return e, + }; + return Dir{ .fd = fd }; + } + /// Same as `openDir` except the parameter is null-terminated. pub fn openDirZ(self: Dir, sub_path_c: [*:0]const u8, args: OpenDirOptions) OpenError!Dir { if (builtin.os.tag == .windows) { @@ -1054,9 +1161,15 @@ pub const Dir = struct { if (builtin.os.tag == .windows) { const sub_path_w = try os.windows.sliceToPrefixedFileW(sub_path); return self.deleteDirW(sub_path_w.span().ptr); + } else if (builtin.os.tag == .wasi) { + os.unlinkat(self.fd, sub_path, os.AT_REMOVEDIR) catch |err| switch (err) { + error.IsDir => unreachable, // not possible since we pass AT_REMOVEDIR + else => |e| return e, + }; + } else { + const sub_path_c = try os.toPosixPath(sub_path); + return self.deleteDirZ(&sub_path_c); } - const sub_path_c = try os.toPosixPath(sub_path); - return self.deleteDirZ(&sub_path_c); } /// Same as `deleteDir` except the parameter is null-terminated. @@ -1448,7 +1561,7 @@ pub fn cwd() Dir { if (builtin.os.tag == .windows) { return Dir{ .fd = os.windows.peb().ProcessParameters.CurrentDirectory.Handle }; } else if (builtin.os.tag == .wasi) { - @compileError("WASI doesn't have a concept of cwd; use TODO instead"); + @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead"); } else { return Dir{ .fd = os.AT_FDCWD }; } @@ -1754,10 +1867,12 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { } test "" { - _ = makeDirAbsolute; - _ = makeDirAbsoluteZ; - _ = copyFileAbsolute; - _ = updateFileAbsolute; + if (builtin.os.tag != .wasi) { + _ = makeDirAbsolute; + _ = makeDirAbsoluteZ; + _ = copyFileAbsolute; + _ = updateFileAbsolute; + } _ = Dir.copyFile; _ = @import("fs/test.zig"); _ = @import("fs/path.zig"); diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 3ea147679d..1546461898 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -140,6 +140,14 @@ pub const File = struct { if (builtin.os.tag == .windows) { return os.isCygwinPty(self.handle); } + if (builtin.os.tag == .wasi) { + // WASI sanitizes stdout when fd is a tty so ANSI escape codes + // will not be interpreted as actual cursor commands. + if (self.handle == os.STDOUT_FILENO and self.isTty()) return false; + // stderr is always sanitized. + if (self.handle == os.STDERR_FILENO) return false; + return true; + } if (self.isTty()) { if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) { // Use getenvC to workaround https://github.com/ziglang/zig/issues/3511 @@ -259,10 +267,11 @@ pub const File = struct { const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); + const m = if (builtin.os.tag == .wasi) 0 else st.mode; return Stat{ .inode = st.ino, .size = @bitCast(u64, st.size), - .mode = st.mode, + .mode = m, .atime = @as(i64, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, .mtime = @as(i64, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, .ctime = @as(i64, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index e104aa99e9..c68232d0b5 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -56,6 +56,8 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD } test "getAppDataDir" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // We can't actually validate the result const dir = getAppDataDir(std.testing.allocator, "zig") catch return; defer std.testing.allocator.free(dir); diff --git a/lib/std/fs/path.zig b/lib/std/fs/path.zig index e040330dd0..7504570d2d 100644 --- a/lib/std/fs/path.zig +++ b/lib/std/fs/path.zig @@ -653,6 +653,8 @@ pub fn resolvePosix(allocator: *Allocator, paths: []const []const u8) ![]u8 { } test "resolve" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); if (builtin.os.tag == .windows) { @@ -667,10 +669,11 @@ test "resolve" { } test "resolveWindows" { - if (@import("builtin").arch == .aarch64) { + if (builtin.arch == .aarch64) { // TODO https://github.com/ziglang/zig/issues/3288 return error.SkipZigTest; } + if (builtin.os.tag == .wasi) return error.SkipZigTest; if (builtin.os.tag == .windows) { const cwd = try process.getCwdAlloc(testing.allocator); defer testing.allocator.free(cwd); @@ -715,6 +718,8 @@ test "resolveWindows" { } test "resolvePosix" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + try testResolvePosix(&[_][]const u8{ "/a/b", "c" }, "/a/b/c"); try testResolvePosix(&[_][]const u8{ "/a/b", "c", "//d", "e///" }, "/d/e"); try testResolvePosix(&[_][]const u8{ "/a/b/c", "..", "../" }, "/a"); @@ -1116,10 +1121,12 @@ pub fn relativePosix(allocator: *Allocator, from: []const u8, to: []const u8) ![ } test "relative" { - if (@import("builtin").arch == .aarch64) { + if (builtin.arch == .aarch64) { // TODO https://github.com/ziglang/zig/issues/3288 return error.SkipZigTest; } + if (builtin.os.tag == .wasi) return error.SkipZigTest; + try testRelativeWindows("c:/blah\\blah", "d:/games", "D:\\games"); try testRelativeWindows("c:/aaaa/bbbb", "c:/aaaa", ".."); try testRelativeWindows("c:/aaaa/bbbb", "c:/cccc", "..\\..\\cccc"); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 7d238c7e92..63b9c6e63a 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -4,6 +4,8 @@ const fs = std.fs; const File = std.fs.File; test "openSelfExe" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const self_exe_file = try std.fs.openSelfExe(); self_exe_file.close(); } @@ -11,6 +13,8 @@ test "openSelfExe" { const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; test "open file with exclusive nonblocking lock twice" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const dir = fs.cwd(); const filename = "file_nonblocking_lock_test.txt"; @@ -111,6 +115,8 @@ test "create file, lock and read from multiple process at once" { } test "open file with exclusive nonblocking lock twice (absolute paths)" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const allocator = std.testing.allocator; const file_paths: [1][]const u8 = .{"zig-test-absolute-paths.txt"}; diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 1cf56e6c7b..04131b2738 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -138,14 +138,3 @@ pub const PreopenList = struct { return self.buffer.toOwnedSlice(); } }; - -/// Convenience wrapper for `std.os.wasi.path_open` syscall. -pub fn openat(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, rights: rights_t) os.OpenError!fd_t { - var fd: fd_t = undefined; - switch (path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, rights, 0x0, fdflags, &fd)) { - 0 => {}, - // TODO map errors - else => |err| return std.os.unexpectedErrno(err), - } - return fd; -} diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index 32a317d029..cad3e6eb88 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -11,15 +11,17 @@ const mem = std.mem; const fs = std.fs; const File = std.fs.File; +const getTestDir = std.testing.getTestDir; + test "write a file, read it, then delete it" { - const cwd = fs.cwd(); + const test_dir = getTestDir(); var data: [1024]u8 = undefined; var prng = DefaultPrng.init(1234); prng.random.bytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try cwd.createFile(tmp_file_name, .{}); + var file = try test_dir.createFile(tmp_file_name, .{}); defer file.close(); var buf_stream = io.bufferedOutStream(file.outStream()); @@ -32,7 +34,7 @@ test "write a file, read it, then delete it" { { // Make sure the exclusive flag is honored. - if (cwd.createFile(tmp_file_name, .{ .exclusive = true })) |file| { + if (test_dir.createFile(tmp_file_name, .{ .exclusive = true })) |file| { unreachable; } else |err| { std.debug.assert(err == File.OpenError.PathAlreadyExists); @@ -40,7 +42,7 @@ test "write a file, read it, then delete it" { } { - var file = try cwd.openFile(tmp_file_name, .{}); + var file = try test_dir.openFile(tmp_file_name, .{}); defer file.close(); const file_size = try file.getEndPos(); @@ -56,13 +58,14 @@ test "write a file, read it, then delete it" { expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try cwd.deleteFile(tmp_file_name); + try test_dir.deleteFile(tmp_file_name); } test "BitStreams with File Stream" { + var test_dir = getTestDir(); const tmp_file_name = "temp_test_file.txt"; { - var file = try fs.cwd().createFile(tmp_file_name, .{}); + var file = try test_dir.createFile(tmp_file_name, .{}); defer file.close(); var bit_stream = io.bitOutStream(builtin.endian, file.outStream()); @@ -76,7 +79,7 @@ test "BitStreams with File Stream" { try bit_stream.flushBits(); } { - var file = try fs.cwd().openFile(tmp_file_name, .{}); + var file = try test_dir.openFile(tmp_file_name, .{}); defer file.close(); var bit_stream = io.bitInStream(builtin.endian, file.inStream()); @@ -98,15 +101,16 @@ test "BitStreams with File Stream" { expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); } - try fs.cwd().deleteFile(tmp_file_name); + try test_dir.deleteFile(tmp_file_name); } test "File seek ops" { + var test_dir = getTestDir(); const tmp_file_name = "temp_test_file.txt"; - var file = try fs.cwd().createFile(tmp_file_name, .{}); + var file = try test_dir.createFile(tmp_file_name, .{}); defer { file.close(); - fs.cwd().deleteFile(tmp_file_name) catch {}; + test_dir.deleteFile(tmp_file_name) catch {}; } try file.writeAll(&([_]u8{0x55} ** 8192)); @@ -129,11 +133,12 @@ test "setEndPos" { // https://github.com/ziglang/zig/issues/5127 if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest; + var test_dir = getTestDir(); const tmp_file_name = "temp_test_file.txt"; - var file = try fs.cwd().createFile(tmp_file_name, .{}); + var file = try test_dir.createFile(tmp_file_name, .{}); defer { file.close(); - fs.cwd().deleteFile(tmp_file_name) catch {}; + test_dir.deleteFile(tmp_file_name) catch {}; } // Verify that the file size changes and the file offset is not moved @@ -152,11 +157,12 @@ test "setEndPos" { } test "updateTimes" { + var test_dir = getTestDir(); const tmp_file_name = "just_a_temporary_file.txt"; - var file = try fs.cwd().createFile(tmp_file_name, .{ .read = true }); + var file = try test_dir.createFile(tmp_file_name, .{ .read = true }); defer { file.close(); - std.fs.cwd().deleteFile(tmp_file_name) catch {}; + test_dir.deleteFile(tmp_file_name) catch {}; } var stat_old = try file.stat(); // Set atime and mtime to 5s before diff --git a/lib/std/net/test.zig b/lib/std/net/test.zig index f4f97d3944..1be8f83038 100644 --- a/lib/std/net/test.zig +++ b/lib/std/net/test.zig @@ -1,9 +1,12 @@ const std = @import("../std.zig"); +const builtin = std.builtin; const net = std.net; const mem = std.mem; const testing = std.testing; test "parse and render IPv6 addresses" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + var buffer: [100]u8 = undefined; const ips = [_][]const u8{ "FF01:0:0:0:0:0:0:FB", @@ -42,6 +45,8 @@ test "parse and render IPv6 addresses" { } test "parse and render IPv4 addresses" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + var buffer: [18]u8 = undefined; for ([_][]const u8{ "0.0.0.0", @@ -63,7 +68,7 @@ test "parse and render IPv4 addresses" { } test "resolve DNS" { - if (std.builtin.os.tag == .windows) { + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) { // DNS resolution not implemented on Windows yet. return error.SkipZigTest; } @@ -101,6 +106,8 @@ test "listen on a port, send bytes, receive bytes" { } fn testClient(addr: net.Address) anyerror!void { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const socket_file = try net.tcpConnectToAddress(addr); defer socket_file.close(); @@ -111,6 +118,8 @@ fn testClient(addr: net.Address) anyerror!void { } fn testServer(server: *net.StreamServer) anyerror!void { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + var client = try server.accept(); const stream = client.file.outStream(); diff --git a/lib/std/os.zig b/lib/std/os.zig index 1eab9affe7..b259bb00db 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -98,6 +98,7 @@ pub fn close(fd: fd_t) void { } if (builtin.os.tag == .wasi) { _ = wasi.fd_close(fd); + return; } if (comptime std.Target.current.isDarwin()) { // This avoids the EINTR problem. @@ -312,7 +313,6 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, null, std.io.default_mode); } - if (builtin.os.tag == .wasi and !builtin.link_libc) { const iovs = [1]iovec{iovec{ .iov_base = buf.ptr, @@ -321,7 +321,18 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { var nread: usize = undefined; switch (wasi.fd_read(fd, &iovs, iovs.len, &nread)) { - 0 => return nread, + wasi.ESUCCESS => return nread, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EIO => return error.InputOutput, + wasi.EISDIR => return error.IsDir, + wasi.ENOBUFS => return error.SystemResources, + wasi.ENOMEM => return error.SystemResources, + wasi.ECONNRESET => return error.ConnectionResetByPeer, + wasi.ETIMEDOUT => return error.ConnectionTimedOut, else => |err| return unexpectedErrno(err), } } @@ -376,6 +387,22 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { const first = iov[0]; return read(fd, first.iov_base[0..first.iov_len]); } + if (builtin.os.tag == .wasi) { + var nread: usize = undefined; + switch (wasi.fd_read(fd, iov.ptr, iov.len, &nread)) { + wasi.ESUCCESS => return nread, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, // currently not support in WASI + wasi.EBADF => unreachable, // always a race condition + wasi.EIO => return error.InputOutput, + wasi.EISDIR => return error.IsDir, + wasi.ENOBUFS => return error.SystemResources, + wasi.ENOMEM => return error.SystemResources, + else => |err| return unexpectedErrno(err), + } + } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { @@ -416,6 +443,31 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { if (builtin.os.tag == .windows) { return windows.ReadFile(fd, buf, offset, std.io.default_mode); } + if (builtin.os.tag == .wasi) { + const iovs = [1]iovec{iovec{ + .iov_base = buf.ptr, + .iov_len = buf.len, + }}; + + var nread: usize = undefined; + switch (wasi.fd_pread(fd, &iovs, iovs.len, offset, &nread)) { + wasi.ESUCCESS => return nread, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EIO => return error.InputOutput, + wasi.EISDIR => return error.IsDir, + wasi.ENOBUFS => return error.SystemResources, + wasi.ENOMEM => return error.SystemResources, + wasi.ECONNRESET => return error.ConnectionResetByPeer, + wasi.ENXIO => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } while (true) { const rc = system.pread(fd, buf.ptr, buf.len, offset); @@ -442,7 +494,6 @@ pub fn pread(fd: fd_t, buf: []u8, offset: u64) PReadError!usize { else => |err| return unexpectedErrno(err), } } - return index; } pub const TruncateError = error{ @@ -474,6 +525,19 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { else => return windows.unexpectedStatus(rc), } } + if (std.Target.current.os.tag == .wasi) { + switch (wasi.fd_filestat_set_size(fd, length)) { + wasi.ESUCCESS => return, + wasi.EINTR => unreachable, + wasi.EFBIG => return error.FileTooBig, + wasi.EIO => return error.InputOutput, + wasi.EPERM => return error.CannotTruncate, + wasi.ETXTBSY => return error.FileBusy, + wasi.EBADF => unreachable, // Handle not open for writing + wasi.EINVAL => unreachable, // Handle not open for writing + else => |err| return unexpectedErrno(err), + } + } while (true) { const rc = if (builtin.link_libc) @@ -523,6 +587,25 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { const first = iov[0]; return pread(fd, first.iov_base[0..first.iov_len], offset); } + if (builtin.os.tag == .wasi) { + var nread: usize = undefined; + switch (wasi.fd_pread(fd, iov.ptr, iov.len, offset, &nread)) { + wasi.ESUCCESS => return nread, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // always a race condition + wasi.EIO => return error.InputOutput, + wasi.EISDIR => return error.IsDir, + wasi.ENOBUFS => return error.SystemResources, + wasi.ENOMEM => return error.SystemResources, + wasi.ENXIO => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); @@ -594,13 +677,25 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { } if (builtin.os.tag == .wasi and !builtin.link_libc) { - const ciovs = [1]iovec_const{iovec_const{ + const ciovs = [_]iovec_const{iovec_const{ .iov_base = bytes.ptr, .iov_len = bytes.len, }}; var nwritten: usize = undefined; switch (wasi.fd_write(fd, &ciovs, ciovs.len, &nwritten)) { - 0 => return nwritten, + wasi.ESUCCESS => return nwritten, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EDESTADDRREQ => unreachable, // `connect` was never called. + wasi.EDQUOT => return error.DiskQuota, + wasi.EFBIG => return error.FileTooBig, + wasi.EIO => return error.InputOutput, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EPERM => return error.AccessDenied, + wasi.EPIPE => return error.BrokenPipe, else => |err| return unexpectedErrno(err), } } @@ -662,6 +757,25 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { const first = iov[0]; return write(fd, first.iov_base[0..first.iov_len]); } + if (builtin.os.tag == .wasi) { + var nwritten: usize = undefined; + switch (wasi.fd_write(fd, iov.ptr, iov.len, &nwritten)) { + wasi.ESUCCESS => return nwritten, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EDESTADDRREQ => unreachable, // `connect` was never called. + wasi.EDQUOT => return error.DiskQuota, + wasi.EFBIG => return error.FileTooBig, + wasi.EIO => return error.InputOutput, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EPERM => return error.AccessDenied, + wasi.EPIPE => return error.BrokenPipe, + else => |err| return unexpectedErrno(err), + } + } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { @@ -717,6 +831,33 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { if (std.Target.current.os.tag == .windows) { return windows.WriteFile(fd, bytes, offset, std.io.default_mode); } + if (builtin.os.tag == .wasi) { + const ciovs = [1]iovec_const{iovec_const{ + .iov_base = bytes.ptr, + .iov_len = bytes.len, + }}; + + var nwritten: usize = undefined; + switch (wasi.fd_pwrite(fd, &ciovs, ciovs.len, offset, &nwritten)) { + wasi.ESUCCESS => return nwritten, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EDESTADDRREQ => unreachable, // `connect` was never called. + wasi.EDQUOT => return error.DiskQuota, + wasi.EFBIG => return error.FileTooBig, + wasi.EIO => return error.InputOutput, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EPERM => return error.AccessDenied, + wasi.EPIPE => return error.BrokenPipe, + wasi.ENXIO => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } // Prevent EINVAL. const max_count = switch (std.Target.current.os.tag) { @@ -788,6 +929,28 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usiz const first = iov[0]; return pwrite(fd, first.iov_base[0..first.iov_len], offset); } + if (builtin.os.tag == .wasi) { + var nwritten: usize = undefined; + switch (wasi.fd_pwrite(fd, iov.ptr, iov.len, offset, &nwritten)) { + wasi.ESUCCESS => return nwritten, + wasi.EINTR => unreachable, + wasi.EINVAL => unreachable, + wasi.EFAULT => unreachable, + wasi.EAGAIN => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.EDESTADDRREQ => unreachable, // `connect` was never called. + wasi.EDQUOT => return error.DiskQuota, + wasi.EFBIG => return error.FileTooBig, + wasi.EIO => return error.InputOutput, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EPERM => return error.AccessDenied, + wasi.EPIPE => return error.BrokenPipe, + wasi.ENXIO => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31); while (true) { @@ -923,6 +1086,37 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) Ope return openatZ(dir_fd, &file_path_c, flags, mode); } +/// Open and possibly create a file in WASI. +pub fn openatWasi(dir_fd: fd_t, file_path: []const u8, oflags: oflags_t, fdflags: fdflags_t, base: rights_t, inheriting: rights_t) OpenError!fd_t { + while (true) { + var fd: fd_t = undefined; + switch (wasi.path_open(dir_fd, 0x0, file_path.ptr, file_path.len, oflags, base, inheriting, fdflags, &fd)) { + wasi.ESUCCESS => return fd, + wasi.EINTR => continue, + + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EACCES => return error.AccessDenied, + wasi.EFBIG => return error.FileTooBig, + wasi.EOVERFLOW => return error.FileTooBig, + wasi.EISDIR => return error.IsDir, + wasi.ELOOP => return error.SymLinkLoop, + wasi.EMFILE => return error.ProcessFdQuotaExceeded, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENFILE => return error.SystemFdQuotaExceeded, + wasi.ENODEV => return error.NoDevice, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.ENOTDIR => return error.NotDir, + wasi.EPERM => return error.AccessDenied, + wasi.EEXIST => return error.PathAlreadyExists, + wasi.EBUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + pub const openatC = @compileError("deprecated: renamed to openatZ"); /// Open and possibly create a file. Keeps trying if it gets interrupted. @@ -1282,6 +1476,9 @@ pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { if (builtin.os.tag == .windows) { return windows.GetCurrentDirectory(out_buffer); } + if (builtin.os.tag == .wasi) { + @compileError("WASI doesn't have a concept of cwd(); use std.fs.wasi.PreopenList to get available Dir handles instead"); + } const err = if (builtin.link_libc) blk: { break :blk if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*; @@ -1460,13 +1657,45 @@ pub fn unlinkat(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!vo if (builtin.os.tag == .windows) { const file_path_w = try windows.sliceToPrefixedFileW(file_path); return unlinkatW(dirfd, file_path_w.span().ptr, flags); + } else if (builtin.os.tag == .wasi) { + return unlinkatWasi(dirfd, file_path, flags); + } else { + const file_path_c = try toPosixPath(file_path); + return unlinkatZ(dirfd, &file_path_c, flags); } - const file_path_c = try toPosixPath(file_path); - return unlinkatZ(dirfd, &file_path_c, flags); } pub const unlinkatC = @compileError("deprecated: renamed to unlinkatZ"); +pub fn unlinkatWasi(dirfd: fd_t, file_path: []const u8, flags: u32) UnlinkatError!void { + const remove_dir = (flags & AT_REMOVEDIR) != 0; + const res = if (remove_dir) + wasi.path_remove_directory(dirfd, file_path.ptr, file_path.len) + else + wasi.path_unlink_file(dirfd, file_path.ptr, file_path.len); + switch (res) { + wasi.ESUCCESS => return, + wasi.EACCES => return error.AccessDenied, + wasi.EPERM => return error.AccessDenied, + wasi.EBUSY => return error.FileBusy, + wasi.EFAULT => unreachable, + wasi.EIO => return error.FileSystem, + wasi.EISDIR => return error.IsDir, + wasi.ELOOP => return error.SymLinkLoop, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.NotDir, + wasi.ENOMEM => return error.SystemResources, + wasi.EROFS => return error.ReadOnlyFileSystem, + wasi.ENOTEMPTY => return error.DirNotEmpty, + + wasi.EINVAL => unreachable, // invalid flags, or pathname has . as last component + wasi.EBADF => unreachable, // always a race condition + + else => |err| return unexpectedErrno(err), + } +} + /// Same as `unlinkat` but `file_path` is a null-terminated string. pub fn unlinkatZ(dirfd: fd_t, file_path_c: [*:0]const u8, flags: u32) UnlinkatError!void { if (builtin.os.tag == .windows) { @@ -1645,6 +1874,8 @@ pub fn renameat( const old_path_w = try windows.sliceToPrefixedFileW(old_path); const new_path_w = try windows.sliceToPrefixedFileW(new_path); return renameatW(old_dir_fd, old_path_w.span(), new_dir_fd, new_path_w.span(), windows.TRUE); + } else if (builtin.os.tag == .wasi) { + return renameatWasi(old_dir_fd, old_path, new_dir_fd, new_path); } else { const old_path_c = try toPosixPath(old_path); const new_path_c = try toPosixPath(new_path); @@ -1652,6 +1883,32 @@ pub fn renameat( } } +/// Same as `renameat` expect only WASI. +pub fn renameatWasi(old_dir_fd: fd_t, old_path: []const u8, new_dir_fd: fd_t, new_path: []const u8) RenameError!void { + switch (wasi.path_rename(old_dir_fd, old_path.ptr, old_path.len, new_dir_fd, new_path.ptr, new_path.len)) { + wasi.ESUCCESS => return, + wasi.EACCES => return error.AccessDenied, + wasi.EPERM => return error.AccessDenied, + wasi.EBUSY => return error.FileBusy, + wasi.EDQUOT => return error.DiskQuota, + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EISDIR => return error.IsDir, + wasi.ELOOP => return error.SymLinkLoop, + wasi.EMLINK => return error.LinkQuotaExceeded, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOTDIR => return error.NotDir, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.EEXIST => return error.PathAlreadyExists, + wasi.ENOTEMPTY => return error.PathAlreadyExists, + wasi.EROFS => return error.ReadOnlyFileSystem, + wasi.EXDEV => return error.RenameAcrossMountPoints, + else => |err| return unexpectedErrno(err), + } +} + /// Same as `renameat` except the parameters are null-terminated byte arrays. pub fn renameatZ( old_dir_fd: fd_t, @@ -1767,6 +2024,8 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.sliceToPrefixedFileW(sub_dir_path); return mkdiratW(dir_fd, sub_dir_path_w.span().ptr, mode); + } else if (builtin.os.tag == .wasi) { + return mkdiratWasi(dir_fd, sub_dir_path, mode); } else { const sub_dir_path_c = try toPosixPath(sub_dir_path); return mkdiratZ(dir_fd, &sub_dir_path_c, mode); @@ -1775,6 +2034,27 @@ pub fn mkdirat(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!v pub const mkdiratC = @compileError("deprecated: renamed to mkdiratZ"); +pub fn mkdiratWasi(dir_fd: fd_t, sub_dir_path: []const u8, mode: u32) MakeDirError!void { + switch (wasi.path_create_directory(dir_fd, sub_dir_path.ptr, sub_dir_path.len)) { + wasi.ESUCCESS => return, + wasi.EACCES => return error.AccessDenied, + wasi.EBADF => unreachable, + wasi.EPERM => return error.AccessDenied, + wasi.EDQUOT => return error.DiskQuota, + wasi.EEXIST => return error.PathAlreadyExists, + wasi.EFAULT => unreachable, + wasi.ELOOP => return error.SymLinkLoop, + wasi.EMLINK => return error.LinkQuotaExceeded, + wasi.ENAMETOOLONG => return error.NameTooLong, + wasi.ENOENT => return error.FileNotFound, + wasi.ENOMEM => return error.SystemResources, + wasi.ENOSPC => return error.NoSpaceLeft, + wasi.ENOTDIR => return error.NotDir, + wasi.EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } +} + pub fn mkdiratZ(dir_fd: fd_t, sub_dir_path: [*:0]const u8, mode: u32) MakeDirError!void { if (builtin.os.tag == .windows) { const sub_dir_path_w = try windows.cStrToPrefixedFileW(sub_dir_path); @@ -2623,8 +2903,19 @@ pub const FStatError = error{ } || UnexpectedError; pub fn fstat(fd: fd_t) FStatError!Stat { - var stat: Stat = undefined; + if (builtin.os.tag == .wasi) { + var stat: Stat = undefined; + switch (wasi.fd_filestat_get(fd, &stat)) { + wasi.ESUCCESS => return stat, + wasi.EINVAL => unreachable, + wasi.EBADF => unreachable, // Always a race condition. + wasi.ENOMEM => return error.SystemResources, + wasi.EACCES => return error.AccessDenied, + else => |err| return unexpectedErrno(err), + } + } + var stat: Stat = undefined; switch (errno(system.fstat(fd, &stat))) { 0 => return stat, EINVAL => unreachable, @@ -3097,6 +3388,10 @@ pub fn sysctl( newp: ?*c_void, newlen: usize, ) SysCtlError!void { + if (builtin.os.tag == .wasi) { + @panic("unsupported"); + } + const name_len = math.cast(c_uint, name.len) catch return error.NameTooLong; switch (errno(system.sysctl(name.ptr, name_len, oldp, oldlenp, newp, newlen))) { 0 => return, @@ -3117,6 +3412,10 @@ pub fn sysctlbynameZ( newp: ?*c_void, newlen: usize, ) SysCtlError!void { + if (builtin.os.tag == .wasi) { + @panic("unsupported"); + } + switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { 0 => return, EFAULT => unreachable, @@ -3154,6 +3453,18 @@ pub fn lseek_SET(fd: fd_t, offset: u64) SeekError!void { if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_BEGIN(fd, offset); } + if (builtin.os.tag == .wasi) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, @bitCast(wasi.filedelta_t, offset), wasi.WHENCE_SET, &new_offset)) { + wasi.ESUCCESS => return, + wasi.EBADF => unreachable, // always a race condition + wasi.EINVAL => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } const ipos = @bitCast(i64, offset); // the OS treats this as unsigned switch (errno(system.lseek(fd, ipos, SEEK_SET))) { 0 => return, @@ -3183,6 +3494,18 @@ pub fn lseek_CUR(fd: fd_t, offset: i64) SeekError!void { if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT(fd, offset); } + if (builtin.os.tag == .wasi) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, offset, wasi.WHENCE_CUR, &new_offset)) { + wasi.ESUCCESS => return, + wasi.EBADF => unreachable, // always a race condition + wasi.EINVAL => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } switch (errno(system.lseek(fd, offset, SEEK_CUR))) { 0 => return, EBADF => unreachable, // always a race condition @@ -3211,6 +3534,18 @@ pub fn lseek_END(fd: fd_t, offset: i64) SeekError!void { if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_END(fd, offset); } + if (builtin.os.tag == .wasi) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, offset, wasi.WHENCE_END, &new_offset)) { + wasi.ESUCCESS => return, + wasi.EBADF => unreachable, // always a race condition + wasi.EINVAL => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } switch (errno(system.lseek(fd, offset, SEEK_END))) { 0 => return, EBADF => unreachable, // always a race condition @@ -3239,6 +3574,18 @@ pub fn lseek_CUR_get(fd: fd_t) SeekError!u64 { if (builtin.os.tag == .windows) { return windows.SetFilePointerEx_CURRENT_get(fd); } + if (builtin.os.tag == .wasi) { + var new_offset: wasi.filesize_t = undefined; + switch (wasi.fd_seek(fd, 0, wasi.WHENCE_CUR, &new_offset)) { + wasi.ESUCCESS => return new_offset, + wasi.EBADF => unreachable, // always a race condition + wasi.EINVAL => return error.Unseekable, + wasi.EOVERFLOW => return error.Unseekable, + wasi.ESPIPE => return error.Unseekable, + wasi.ENXIO => return error.Unseekable, + else => |err| return unexpectedErrno(err), + } + } const rc = system.lseek(fd, 0, SEEK_CUR); switch (errno(rc)) { 0 => return @bitCast(u64, rc), @@ -3365,6 +3712,9 @@ pub fn realpath(pathname: []const u8, out_buffer: *[MAX_PATH_BYTES]u8) RealPathE const pathname_w = try windows.sliceToPrefixedFileW(pathname); return realpathW(pathname_w.span().ptr, out_buffer); } + if (builtin.os.tag == .wasi) { + @compileError("Use std.fs.wasi.PreopenList to obtain valid Dir handles instead of using absolute paths"); + } const pathname_c = try toPosixPath(pathname); return realpathZ(&pathname_c, out_buffer); } @@ -3679,6 +4029,24 @@ pub const FutimensError = error{ } || UnexpectedError; pub fn futimens(fd: fd_t, times: *const [2]timespec) FutimensError!void { + if (builtin.os.tag == .wasi) { + // TODO WASI encodes `wasi.fstflags` to signify magic values + // similar to UTIME_NOW and UTIME_OMIT. Currently, we ignore + // this here, but we should really handle it somehow. + const atim = times[0].toTimestamp(); + const mtim = times[1].toTimestamp(); + switch (wasi.fd_filestat_set_times(fd, atim, mtim, wasi.FILESTAT_SET_ATIM | wasi.FILESTAT_SET_MTIM)) { + wasi.ESUCCESS => return, + wasi.EACCES => return error.AccessDenied, + wasi.EPERM => return error.PermissionDenied, + wasi.EBADF => unreachable, // always a race condition + wasi.EFAULT => unreachable, + wasi.EINVAL => unreachable, + wasi.EROFS => return error.ReadOnlyFileSystem, + else => |err| return unexpectedErrno(err), + } + } + switch (errno(system.futimens(fd, times))) { 0 => return, EACCES => return error.AccessDenied, diff --git a/lib/std/os/bits/wasi.zig b/lib/std/os/bits/wasi.zig index 0da984021d..880cf934de 100644 --- a/lib/std/os/bits/wasi.zig +++ b/lib/std/os/bits/wasi.zig @@ -10,8 +10,26 @@ pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc pub const timespec = extern struct { tv_sec: time_t, tv_nsec: isize, + + pub fn fromTimestamp(tm: timestamp_t) timespec { + const tv_sec: timestamp_t = tm / 1_000_000_000; + const tv_nsec = tm - tv_sec * 1_000_000_000; + return timespec{ + .tv_sec = @intCast(time_t, tv_sec), + .tv_nsec = @intCast(isize, tv_nsec), + }; + } + + pub fn toTimestamp(ts: timespec) timestamp_t { + const tm = @intCast(timestamp_t, ts.tv_sec * 1_000_000_000) + @intCast(timestamp_t, ts.tv_nsec); + return tm; + } }; +pub const Stat = filestat_t; + +pub const AT_REMOVEDIR: u32 = 1; // there's no AT_REMOVEDIR in WASI, but we simulate here to match other OSes + // As defined in the wasi_snapshot_preview1 spec file: // https://github.com/WebAssembly/WASI/blob/master/phases/snapshot/witx/typenames.witx pub const advice_t = u8; @@ -164,14 +182,26 @@ pub const filedelta_t = i64; pub const filesize_t = u64; pub const filestat_t = extern struct { - st_dev: device_t, - st_ino: inode_t, - st_filetype: filetype_t, - st_nlink: linkcount_t, - st_size: filesize_t, - st_atim: timestamp_t, - st_mtim: timestamp_t, - st_ctim: timestamp_t, + dev: device_t, + ino: inode_t, + filetype: filetype_t, + nlink: linkcount_t, + size: filesize_t, + atim: timestamp_t, + mtim: timestamp_t, + ctim: timestamp_t, + + pub fn atime(self: filestat_t) timespec { + return timespec.fromTimestamp(self.atim); + } + + pub fn mtime(self: filestat_t) timespec { + return timespec.fromTimestamp(self.mtim); + } + + pub fn ctime(self: filestat_t) timespec { + return timespec.fromTimestamp(self.ctim); + } }; pub const filetype_t = u8; @@ -254,6 +284,35 @@ pub const RIGHT_PATH_REMOVE_DIRECTORY: rights_t = 0x0000000002000000; pub const RIGHT_PATH_UNLINK_FILE: rights_t = 0x0000000004000000; pub const RIGHT_POLL_FD_READWRITE: rights_t = 0x0000000008000000; pub const RIGHT_SOCK_SHUTDOWN: rights_t = 0x0000000010000000; +pub const RIGHT_ALL: rights_t = RIGHT_FD_DATASYNC | + RIGHT_FD_READ | + RIGHT_FD_SEEK | + RIGHT_FD_FDSTAT_SET_FLAGS | + RIGHT_FD_SYNC | + RIGHT_FD_TELL | + RIGHT_FD_WRITE | + RIGHT_FD_ADVISE | + RIGHT_FD_ALLOCATE | + RIGHT_PATH_CREATE_DIRECTORY | + RIGHT_PATH_CREATE_FILE | + RIGHT_PATH_LINK_SOURCE | + RIGHT_PATH_LINK_TARGET | + RIGHT_PATH_OPEN | + RIGHT_FD_READDIR | + RIGHT_PATH_READLINK | + RIGHT_PATH_RENAME_SOURCE | + RIGHT_PATH_RENAME_TARGET | + RIGHT_PATH_FILESTAT_GET | + RIGHT_PATH_FILESTAT_SET_SIZE | + RIGHT_PATH_FILESTAT_SET_TIMES | + RIGHT_FD_FILESTAT_GET | + RIGHT_FD_FILESTAT_SET_SIZE | + RIGHT_FD_FILESTAT_SET_TIMES | + RIGHT_PATH_SYMLINK | + RIGHT_PATH_REMOVE_DIRECTORY | + RIGHT_PATH_UNLINK_FILE | + RIGHT_POLL_FD_READWRITE | + RIGHT_SOCK_SHUTDOWN; pub const roflags_t = u16; pub const SOCK_RECV_DATA_TRUNCATED: roflags_t = 0x0001; @@ -306,7 +365,7 @@ pub const subscription_t = extern struct { }; pub const subscription_clock_t = extern struct { - id: clock_id_t, + id: clockid_t, timeout: timestamp_t, precision: timestamp_t, flags: subclockflags_t, diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 9c5ee44e88..485b089928 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -15,13 +15,15 @@ const a = std.testing.allocator; const builtin = @import("builtin"); const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; +const getTestDir = std.testing.getTestDir; test "makePath, put some files in it, deleteTree" { - try fs.cwd().makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); - try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); - try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); - try fs.cwd().deleteTree("os_test_tmp"); - if (fs.cwd().openDir("os_test_tmp", .{})) |dir| { + var test_dir = getTestDir(); + try test_dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); + try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); + try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); + try test_dir.deleteTree("os_test_tmp"); + if (test_dir.openDir("os_test_tmp", .{})) |dir| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); @@ -29,16 +31,19 @@ test "makePath, put some files in it, deleteTree" { } test "access file" { - try fs.cwd().makePath("os_test_tmp"); - if (fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + + var test_dir = getTestDir(); + try test_dir.makePath("os_test_tmp"); + if (test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); } - try fs.cwd().writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); - try fs.cwd().access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); - try fs.cwd().deleteTree("os_test_tmp"); + try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); + try test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); + try test_dir.deleteTree("os_test_tmp"); } fn testThreadIdFn(thread_id: *Thread.Id) void { @@ -46,10 +51,11 @@ fn testThreadIdFn(thread_id: *Thread.Id) void { } test "sendfile" { - try fs.cwd().makePath("os_test_tmp"); - defer fs.cwd().deleteTree("os_test_tmp") catch {}; + var test_dir = getTestDir(); + try test_dir.makePath("os_test_tmp"); + defer test_dir.deleteTree("os_test_tmp") catch {}; - var dir = try fs.cwd().openDir("os_test_tmp", .{}); + var dir = try test_dir.openDir("os_test_tmp", .{}); defer dir.close(); const line1 = "line1\n"; @@ -65,12 +71,12 @@ test "sendfile" { }, }; - var src_file = try dir.createFileZ("sendfile1.txt", .{ .read = true }); + var src_file = try dir.createFile("sendfile1.txt", .{ .read = true }); defer src_file.close(); try src_file.writevAll(&vecs); - var dest_file = try dir.createFileZ("sendfile2.txt", .{ .read = true }); + var dest_file = try dir.createFile("sendfile2.txt", .{ .read = true }); defer dest_file.close(); const header1 = "header1\n"; @@ -113,23 +119,23 @@ test "fs.copyFile" { const dest_file = "tmp_test_copy_file2.txt"; const dest_file2 = "tmp_test_copy_file3.txt"; - const cwd = fs.cwd(); + const test_dir = getTestDir(); - try cwd.writeFile(src_file, data); - defer cwd.deleteFile(src_file) catch {}; + try test_dir.writeFile(src_file, data); + defer test_dir.deleteFile(src_file) catch {}; - try cwd.copyFile(src_file, cwd, dest_file, .{}); - defer cwd.deleteFile(dest_file) catch {}; + try test_dir.copyFile(src_file, test_dir, dest_file, .{}); + defer test_dir.deleteFile(dest_file) catch {}; - try cwd.copyFile(src_file, cwd, dest_file2, .{ .override_mode = File.default_mode }); - defer cwd.deleteFile(dest_file2) catch {}; + try test_dir.copyFile(src_file, test_dir, dest_file2, .{ .override_mode = File.default_mode }); + defer test_dir.deleteFile(dest_file2) catch {}; try expectFileContents(dest_file, data); try expectFileContents(dest_file2, data); } fn expectFileContents(file_path: []const u8, data: []const u8) !void { - const contents = try fs.cwd().readFileAlloc(testing.allocator, file_path, 1000); + const contents = try getTestDir().readFileAlloc(testing.allocator, file_path, 1000); defer testing.allocator.free(contents); testing.expectEqualSlices(u8, data, contents); @@ -181,6 +187,8 @@ fn start2(ctx: *i32) u8 { } test "cpu count" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const cpu_count = try Thread.cpuCount(); expect(cpu_count >= 1); } @@ -191,17 +199,18 @@ test "AtomicFile" { \\ hello! \\ this is a test file ; + var test_dir = getTestDir(); { - var af = try fs.cwd().atomicFile(test_out_file, .{}); + var af = try test_dir.atomicFile(test_out_file, .{}); defer af.deinit(); try af.file.writeAll(test_content); try af.finish(); } - const content = try fs.cwd().readFileAlloc(testing.allocator, test_out_file, 9999); + const content = try test_dir.readFileAlloc(testing.allocator, test_out_file, 9999); defer testing.allocator.free(content); expect(mem.eql(u8, content, test_content)); - try fs.cwd().deleteFile(test_out_file); + try test_dir.deleteFile(test_out_file); } test "thread local storage" { @@ -231,12 +240,16 @@ test "getrandom" { } test "getcwd" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + // at least call it so it gets compiled var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; _ = os.getcwd(&buf) catch undefined; } test "realpath" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; testing.expectError(error.FileNotFound, fs.realpath("definitely_bogus_does_not_exist1234", &buf)); } @@ -304,7 +317,7 @@ test "dl_iterate_phdr" { } test "gethostname" { - if (builtin.os.tag == .windows) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; var buf: [os.HOST_NAME_MAX]u8 = undefined; @@ -313,7 +326,7 @@ test "gethostname" { } test "pipe" { - if (builtin.os.tag == .windows) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; var fds = try os.pipe(); @@ -349,7 +362,7 @@ test "memfd_create" { } test "mmap" { - if (builtin.os.tag == .windows) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; // Simple mmap() call with non page-aligned size @@ -451,7 +464,7 @@ test "getenv" { } test "fcntl" { - if (builtin.os.tag == .windows) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; const test_out_file = "os_tmp_test"; diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index 8a0dbfbf8d..d4bb1276d8 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -314,6 +314,9 @@ pub fn PackedIntSliceEndian(comptime Int: type, comptime endian: builtin.Endian) } test "PackedIntArray" { + // TODO @setEvalBranchQuota generates panics in wasm32. Investigate. + if (builtin.arch == .wasm32) return error.SkipZigTest; + @setEvalBranchQuota(10000); const max_bits = 256; const int_count = 19; @@ -357,6 +360,9 @@ test "PackedIntArray init" { } test "PackedIntSlice" { + // TODO @setEvalBranchQuota generates panics in wasm32. Investigate. + if (builtin.arch == .wasm32) return error.SkipZigTest; + @setEvalBranchQuota(10000); const max_bits = 256; const int_count = 19; diff --git a/lib/std/process.zig b/lib/std/process.zig index b4baf49cf2..8c1feeffb2 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -26,6 +26,8 @@ pub fn getCwdAlloc(allocator: *Allocator) ![]u8 { } test "getCwdAlloc" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const cwd = try getCwdAlloc(testing.allocator); testing.allocator.free(cwd); } @@ -69,9 +71,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { return os.unexpectedErrno(environ_sizes_get_ret); } - // TODO: Verify that the documentation is incorrect - // https://github.com/WebAssembly/WASI/issues/27 - var environ = try allocator.alloc(?[*:0]u8, environ_count + 1); + var environ = try allocator.alloc([*:0]u8, environ_count); defer allocator.free(environ); var environ_buf = try allocator.alloc(u8, environ_buf_size); defer allocator.free(environ_buf); @@ -82,13 +82,11 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap { } for (environ) |env| { - if (env) |ptr| { - const pair = mem.spanZ(ptr); - var parts = mem.split(pair, "="); - const key = parts.next().?; - const value = parts.next().?; - try result.set(key, value); - } + const pair = mem.spanZ(env); + var parts = mem.split(pair, "="); + const key = parts.next().?; + const value = parts.next().?; + try result.set(key, value); } return result; } else if (builtin.link_libc) { diff --git a/lib/std/std.zig b/lib/std/std.zig index 376c200200..8fce2248cc 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -75,5 +75,10 @@ comptime { } test "" { - meta.refAllDecls(@This()); + // TODO is there a way around this? When enabled for WASI, we pick up functions + // which generate compile error. Perhaps semantic analyser should skip those + // if running in test mode? + if (builtin.os.tag != .wasi) { + meta.refAllDecls(@This()); + } } diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 0f6cefb787..1592923ba8 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -14,6 +14,21 @@ pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_insta pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); var allocator_mem: [2 * 1024 * 1024]u8 = undefined; +/// This function is intended to be used only in tests. It should be used in any testcase +/// where we intend to test WASI and should be used a replacement for `std.fs.cwd()` in WASI. +pub fn getTestDir() std.fs.Dir { + if (@import("builtin").os.tag == .wasi) { + var preopens = std.fs.wasi.PreopenList.init(allocator); + defer preopens.deinit(); + preopens.populate() catch unreachable; + + const preopen = preopens.find(".") orelse unreachable; + return std.fs.Dir{ .fd = preopen.fd }; + } else { + return std.fs.cwd(); + } +} + /// This function is intended to be used only in tests. It prints diagnostics to stderr /// and then aborts when actual_error_union is not expected_error. pub fn expectError(expected_error: anyerror, actual_error_union: var) void { diff --git a/lib/std/time.zig b/lib/std/time.zig index 4112fb7bda..3c42fa7128 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -19,6 +19,39 @@ pub fn sleep(nanoseconds: u64) void { os.windows.kernel32.Sleep(ms); return; } + if (builtin.os.tag == .wasi) { + const w = std.os.wasi; + const userdata: w.userdata_t = 0x0123_45678; + const clock = w.subscription_clock_t{ + .id = w.CLOCK_MONOTONIC, + .timeout = nanoseconds, + .precision = 0, + .flags = 0, + }; + const in = w.subscription_t{ + .userdata = userdata, + .u = w.subscription_u_t{ + .tag = w.EVENTTYPE_CLOCK, + .u = w.subscription_u_u_t{ + .clock = clock, + }, + }, + }; + + var event: w.event_t = undefined; + var nevents: usize = undefined; + switch (w.poll_oneoff(&in, &event, 1, &nevents)) { + w.ESUCCESS => {}, + else => |err| @panic("unexpected error of poll_oneoff"), + } + + if (nevents == 1 and event.userdata == userdata and event.@"error" == w.ESUCCESS and event.@"type" == w.EVENTTYPE_CLOCK) { + return; + } + + @panic("unexpected result of poll_oneoff"); + } + const s = nanoseconds / ns_per_s; const ns = nanoseconds % ns_per_s; std.os.nanosleep(s, ns); @@ -202,7 +235,7 @@ test "sleep" { test "timestamp" { const ns_per_ms = (ns_per_s / ms_per_s); - const margin = 50; + const margin = ns_per_ms * 50; const time_0 = milliTimestamp(); sleep(ns_per_ms); diff --git a/test/tests.zig b/test/tests.zig index d5fdbd74cf..b538818634 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -50,6 +50,15 @@ const test_targets = blk: { .single_threaded = true, }, + TestTarget{ + .target = .{ + .cpu_arch = .wasm32, + .os_tag = .wasi, + }, + .link_libc = false, + .single_threaded = true, + }, + TestTarget{ .target = .{ .cpu_arch = .x86_64, @@ -463,6 +472,7 @@ pub fn addPkgTests( skip_single_threaded: bool, skip_non_native: bool, skip_libc: bool, + skip_wasi: bool, is_wine_enabled: bool, is_qemu_enabled: bool, glibc_dir: ?[]const u8, @@ -473,6 +483,15 @@ pub fn addPkgTests( if (skip_non_native and !test_target.target.isNative()) continue; + if (skip_wasi) { + if (test_target.target.os_tag) |tag| { + if (tag == .wasi) { + warn("Skipping {} on wasm32-wasi.\n", .{root_src}); + continue; + } + } + } + if (skip_libc and test_target.link_libc) continue; @@ -525,6 +544,7 @@ pub fn addPkgTests( these_tests.overrideZigLibDir("lib"); these_tests.enable_wine = is_wine_enabled; these_tests.enable_qemu = is_qemu_enabled; + these_tests.enable_wasmtime = !skip_wasi; these_tests.glibc_multi_install_dir = glibc_dir; step.dependOn(&these_tests.step); From be796e40a3c5797a304b1981a2a7fd321944d1c0 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 12 May 2020 22:33:16 +0200 Subject: [PATCH 02/13] Run WASI tests on Linux only --- ci/azure/macos_script | 2 +- ci/azure/windows_mingw_script | 2 +- ci/azure/windows_msvc_script.bat | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/azure/macos_script b/ci/azure/macos_script index 7dd4344e39..37c65dab44 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -88,7 +88,7 @@ mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$PREFIX -DCMAKE_INSTALL_PREFIX=$(pwd)/release -DZIG_STATIC=ON make $JOBS install -release/bin/zig build test +release/bin/zig build test -Dskip-wasi if [ "${BUILD_REASON}" != "PullRequest" ]; then mv ../LICENSE release/ diff --git a/ci/azure/windows_mingw_script b/ci/azure/windows_mingw_script index 7883766427..2b261faf81 100644 --- a/ci/azure/windows_mingw_script +++ b/ci/azure/windows_mingw_script @@ -18,4 +18,4 @@ cmake .. -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo $CMAKEFLAGS -DCMA make -j$(nproc) install -./zig build test-behavior -Dskip-non-native -Dskip-release +./zig build test-behavior -Dskip-non-native -Dskip-release -Dskip-wasi diff --git a/ci/azure/windows_msvc_script.bat b/ci/azure/windows_msvc_script.bat index b2063a8e5f..f44b2385dc 100644 --- a/ci/azure/windows_msvc_script.bat +++ b/ci/azure/windows_msvc_script.bat @@ -24,7 +24,7 @@ cd %ZIGBUILDDIR% cmake.exe .. -Thost=x64 -G"Visual Studio 16 2019" -A x64 "-DCMAKE_INSTALL_PREFIX=%ZIGINSTALLDIR%" "-DCMAKE_PREFIX_PATH=%ZIGPREFIXPATH%" -DCMAKE_BUILD_TYPE=Release || exit /b msbuild /maxcpucount /p:Configuration=Release INSTALL.vcxproj || exit /b -"%ZIGINSTALLDIR%\bin\zig.exe" build test || exit /b +"%ZIGINSTALLDIR%\bin\zig.exe" build test -Dskip-wasi || exit /b set "PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem" SET "MSYSTEM=MINGW64" From 14510f412dcc045909f1c49a01d661343e07acaf Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 12 May 2020 23:15:54 +0200 Subject: [PATCH 03/13] Download and unpack wasmtime on Linux --- ci/azure/linux_script | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 2a5d2ef1a8..9dff294583 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -19,6 +19,11 @@ wget https://ziglang.org/deps/$QEMUBASE.tar.xz tar xf $QEMUBASE.tar.xz PATH=$PWD/$QEMUBASE/bin:$PATH +WASMTIME="wasmtime-v0.15.0-x86_64-linux" +wget https://github.com/bytecodealliance/wasmtime/releases/download/v0.15.0/$WASMTIME.tar.xz +tar xf $WASMTIME.tar.xz +PATH=$PWD/$WASMTIME:$PATH + # Make the `zig version` number consistent. # This will affect the cmake command below. git config core.abbrev 9 From bd9a616586dba45e2e1788e4f0b9ce6953c36d1d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Tue, 12 May 2020 23:18:16 +0200 Subject: [PATCH 04/13] Skip WASI tests in drone and on FreeBSD --- ci/drone/linux_script | 2 +- ci/srht/freebsd_script | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/ci/drone/linux_script b/ci/drone/linux_script index ca587eed8f..15fe5a86db 100755 --- a/ci/drone/linux_script +++ b/ci/drone/linux_script @@ -20,7 +20,7 @@ cd build cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local make -j$(nproc) install -./zig build test -Dskip-release -Dskip-non-native +./zig build test -Dskip-release -Dskip-non-native -Dskip-wasi if [ -z "$DRONE_PULL_REQUEST" ]; then mv ../LICENSE "$DISTDIR/" diff --git a/ci/srht/freebsd_script b/ci/srht/freebsd_script index 97b869c9f9..e111ded263 100755 --- a/ci/srht/freebsd_script +++ b/ci/srht/freebsd_script @@ -27,13 +27,13 @@ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$PREFIX "-DCMAKE_INSTALL make $JOBS install release/bin/zig build test-fmt -release/bin/zig build test-behavior +release/bin/zig build test-behavior -Dskip-wasi # This test is disabled because it triggers "out of memory" on the sr.ht CI service. # See https://github.com/ziglang/zig/issues/3210 # release/bin/zig build test-std -release/bin/zig build test-compiler-rt +release/bin/zig build test-compiler-rt -Dskip-wasi release/bin/zig build test-compare-output release/bin/zig build test-standalone release/bin/zig build test-stack-traces From 40812063cca77a84c58c8760ddf0ad9da3356962 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Wed, 13 May 2020 08:08:16 +0200 Subject: [PATCH 05/13] Disable tests requiring Wasmtime be default; require -Denable-wasmtime flag otherwise --- build.zig | 8 ++++---- ci/azure/linux_script | 2 +- ci/azure/macos_script | 2 +- ci/azure/windows_mingw_script | 2 +- ci/azure/windows_msvc_script.bat | 2 +- ci/drone/linux_script | 2 +- ci/srht/freebsd_script | 4 ++-- test/tests.zig | 6 +++--- 8 files changed, 14 insertions(+), 14 deletions(-) diff --git a/build.zig b/build.zig index bc15a31930..9e409fd874 100644 --- a/build.zig +++ b/build.zig @@ -60,7 +60,6 @@ pub fn build(b: *Builder) !void { const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; - const skip_wasi = b.option(bool, "skip-wasi", "Main test suite skips WASI build") orelse false; const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false; const enable_llvm = b.option(bool, "enable-llvm", "Build self-hosted compiler with LLVM backend enabled") orelse false; @@ -87,6 +86,7 @@ pub fn build(b: *Builder) !void { const is_wine_enabled = b.option(bool, "enable-wine", "Use Wine to run cross compiled Windows tests") orelse false; const is_qemu_enabled = b.option(bool, "enable-qemu", "Use QEMU to run cross compiled foreign architecture tests") orelse false; + const is_wasmtime_enabled = b.option(bool, "enable-wasmtime", "Use Wasmtime to enable and run WASI libstd tests") orelse false; const glibc_multi_dir = b.option([]const u8, "enable-foreign-glibc", "Provide directory with glibc installations to run cross compiled tests that link glibc"); const test_stage2_step = b.step("test-stage2", "Run the stage2 compiler tests"); @@ -117,11 +117,11 @@ pub fn build(b: *Builder) !void { fmt_step.dependOn(&fmt_build_zig.step); // TODO for the moment, skip wasm32-wasi until bugs are sorted out. - test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, true, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, false, glibc_multi_dir)); - test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, is_wasmtime_enabled, glibc_multi_dir)); - test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, skip_wasi, is_wine_enabled, is_qemu_enabled, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/special/compiler_rt.zig", "compiler-rt", "Run the compiler_rt tests", modes, true, skip_non_native, true, is_wine_enabled, is_qemu_enabled, is_wasmtime_enabled, glibc_multi_dir)); test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes)); test_step.dependOn(tests.addStandaloneTests(b, test_filter, modes)); diff --git a/ci/azure/linux_script b/ci/azure/linux_script index 9dff294583..2bc9402063 100755 --- a/ci/azure/linux_script +++ b/ci/azure/linux_script @@ -50,7 +50,7 @@ mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release make -j$(nproc) install -./zig build test -Denable-qemu +./zig build test -Denable-qemu -Denable-wasmtime VERSION="$(./zig version)" if [ "${BUILD_REASON}" != "PullRequest" ]; then diff --git a/ci/azure/macos_script b/ci/azure/macos_script index 37c65dab44..7dd4344e39 100755 --- a/ci/azure/macos_script +++ b/ci/azure/macos_script @@ -88,7 +88,7 @@ mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$PREFIX -DCMAKE_INSTALL_PREFIX=$(pwd)/release -DZIG_STATIC=ON make $JOBS install -release/bin/zig build test -Dskip-wasi +release/bin/zig build test if [ "${BUILD_REASON}" != "PullRequest" ]; then mv ../LICENSE release/ diff --git a/ci/azure/windows_mingw_script b/ci/azure/windows_mingw_script index 2b261faf81..7883766427 100644 --- a/ci/azure/windows_mingw_script +++ b/ci/azure/windows_mingw_script @@ -18,4 +18,4 @@ cmake .. -G 'MSYS Makefiles' -DCMAKE_BUILD_TYPE=RelWithDebInfo $CMAKEFLAGS -DCMA make -j$(nproc) install -./zig build test-behavior -Dskip-non-native -Dskip-release -Dskip-wasi +./zig build test-behavior -Dskip-non-native -Dskip-release diff --git a/ci/azure/windows_msvc_script.bat b/ci/azure/windows_msvc_script.bat index f44b2385dc..b2063a8e5f 100644 --- a/ci/azure/windows_msvc_script.bat +++ b/ci/azure/windows_msvc_script.bat @@ -24,7 +24,7 @@ cd %ZIGBUILDDIR% cmake.exe .. -Thost=x64 -G"Visual Studio 16 2019" -A x64 "-DCMAKE_INSTALL_PREFIX=%ZIGINSTALLDIR%" "-DCMAKE_PREFIX_PATH=%ZIGPREFIXPATH%" -DCMAKE_BUILD_TYPE=Release || exit /b msbuild /maxcpucount /p:Configuration=Release INSTALL.vcxproj || exit /b -"%ZIGINSTALLDIR%\bin\zig.exe" build test -Dskip-wasi || exit /b +"%ZIGINSTALLDIR%\bin\zig.exe" build test || exit /b set "PATH=%CD:~0,2%\msys64\usr\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem" SET "MSYSTEM=MINGW64" diff --git a/ci/drone/linux_script b/ci/drone/linux_script index 15fe5a86db..ca587eed8f 100755 --- a/ci/drone/linux_script +++ b/ci/drone/linux_script @@ -20,7 +20,7 @@ cd build cmake .. -DCMAKE_BUILD_TYPE=Release "-DCMAKE_INSTALL_PREFIX=$DISTDIR" -DZIG_STATIC=ON -DCMAKE_PREFIX_PATH=/deps/local make -j$(nproc) install -./zig build test -Dskip-release -Dskip-non-native -Dskip-wasi +./zig build test -Dskip-release -Dskip-non-native if [ -z "$DRONE_PULL_REQUEST" ]; then mv ../LICENSE "$DISTDIR/" diff --git a/ci/srht/freebsd_script b/ci/srht/freebsd_script index e111ded263..97b869c9f9 100755 --- a/ci/srht/freebsd_script +++ b/ci/srht/freebsd_script @@ -27,13 +27,13 @@ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=$PREFIX "-DCMAKE_INSTALL make $JOBS install release/bin/zig build test-fmt -release/bin/zig build test-behavior -Dskip-wasi +release/bin/zig build test-behavior # This test is disabled because it triggers "out of memory" on the sr.ht CI service. # See https://github.com/ziglang/zig/issues/3210 # release/bin/zig build test-std -release/bin/zig build test-compiler-rt -Dskip-wasi +release/bin/zig build test-compiler-rt release/bin/zig build test-compare-output release/bin/zig build test-standalone release/bin/zig build test-stack-traces diff --git a/test/tests.zig b/test/tests.zig index b538818634..3b70d0e36b 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -472,9 +472,9 @@ pub fn addPkgTests( skip_single_threaded: bool, skip_non_native: bool, skip_libc: bool, - skip_wasi: bool, is_wine_enabled: bool, is_qemu_enabled: bool, + is_wasmtime_enabled: bool, glibc_dir: ?[]const u8, ) *build.Step { const step = b.step(b.fmt("test-{}", .{name}), desc); @@ -483,7 +483,7 @@ pub fn addPkgTests( if (skip_non_native and !test_target.target.isNative()) continue; - if (skip_wasi) { + if (!is_wasmtime_enabled) { if (test_target.target.os_tag) |tag| { if (tag == .wasi) { warn("Skipping {} on wasm32-wasi.\n", .{root_src}); @@ -544,7 +544,7 @@ pub fn addPkgTests( these_tests.overrideZigLibDir("lib"); these_tests.enable_wine = is_wine_enabled; these_tests.enable_qemu = is_qemu_enabled; - these_tests.enable_wasmtime = !skip_wasi; + these_tests.enable_wasmtime = is_wasmtime_enabled; these_tests.glibc_multi_install_dir = glibc_dir; step.dependOn(&these_tests.step); From fae4af9e1cf0383a74da808678d9369553bd878b Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 17:01:02 +0200 Subject: [PATCH 06/13] Make mode_t a 0-byte type in WASI --- lib/std/fs/file.zig | 4 ++-- lib/std/os.zig | 4 ++-- lib/std/os/bits/wasi.zig | 44 +++++++++++++++++++++++++++++++++++++--- 3 files changed, 45 insertions(+), 7 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 1546461898..7e6a7dde63 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -30,6 +30,7 @@ pub const File = struct { pub const default_mode = switch (builtin.os.tag) { .windows => 0, + .wasi => 0, else => 0o666, }; @@ -267,11 +268,10 @@ pub const File = struct { const atime = st.atime(); const mtime = st.mtime(); const ctime = st.ctime(); - const m = if (builtin.os.tag == .wasi) 0 else st.mode; return Stat{ .inode = st.ino, .size = @bitCast(u64, st.size), - .mode = m, + .mode = st.mode, .atime = @as(i64, atime.tv_sec) * std.time.ns_per_s + atime.tv_nsec, .mtime = @as(i64, mtime.tv_sec) * std.time.ns_per_s + mtime.tv_nsec, .ctime = @as(i64, ctime.tv_sec) * std.time.ns_per_s + ctime.tv_nsec, diff --git a/lib/std/os.zig b/lib/std/os.zig index b259bb00db..bc8803e3b0 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -2904,9 +2904,9 @@ pub const FStatError = error{ pub fn fstat(fd: fd_t) FStatError!Stat { if (builtin.os.tag == .wasi) { - var stat: Stat = undefined; + var stat: wasi.filestat_t = undefined; switch (wasi.fd_filestat_get(fd, &stat)) { - wasi.ESUCCESS => return stat, + wasi.ESUCCESS => return Stat.fromFilestat(stat), wasi.EINVAL => unreachable, wasi.EBADF => unreachable, // Always a race condition. wasi.ENOMEM => return error.SystemResources, diff --git a/lib/std/os/bits/wasi.zig b/lib/std/os/bits/wasi.zig index 880cf934de..270345c53b 100644 --- a/lib/std/os/bits/wasi.zig +++ b/lib/std/os/bits/wasi.zig @@ -3,11 +3,11 @@ pub const STDIN_FILENO = 0; pub const STDOUT_FILENO = 1; pub const STDERR_FILENO = 2; -pub const mode_t = u32; +pub const mode_t = u0; pub const time_t = i64; // match https://github.com/CraneStation/wasi-libc -pub const timespec = extern struct { +pub const timespec = struct { tv_sec: time_t, tv_nsec: isize, @@ -26,7 +26,45 @@ pub const timespec = extern struct { } }; -pub const Stat = filestat_t; +pub const Stat = struct { + dev: device_t, + ino: inode_t, + mode: mode_t, + filetype: filetype_t, + nlink: linkcount_t, + size: filesize_t, + atim: timespec, + mtim: timespec, + ctim: timespec, + + const Self = @This(); + + pub fn fromFilestat(stat: filestat_t) Self { + return Self{ + .dev = stat.dev, + .ino = stat.ino, + .mode = 0, + .filetype = stat.filetype, + .nlink = stat.nlink, + .size = stat.size, + .atim = stat.atime(), + .mtim = stat.mtime(), + .ctim = stat.ctime(), + }; + } + + pub fn atime(self: Self) timespec { + return self.atim; + } + + pub fn mtime(self: Self) timespec { + return self.mtim; + } + + pub fn ctime(self: Self) timespec { + return self.ctim; + } +}; pub const AT_REMOVEDIR: u32 = 1; // there's no AT_REMOVEDIR in WASI, but we simulate here to match other OSes From 5186711a96197604ac824979027f46a0299d5e12 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 17:03:02 +0200 Subject: [PATCH 07/13] Change to Self from *const Self/*Self where possible --- lib/std/fs/wasi.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/fs/wasi.zig b/lib/std/fs/wasi.zig index 04131b2738..194e1d41a6 100644 --- a/lib/std/fs/wasi.zig +++ b/lib/std/fs/wasi.zig @@ -117,7 +117,7 @@ pub const PreopenList = struct { /// TODO make the function more generic by searching by `PreopenType` union. This will /// be needed in the future when WASI extends its capabilities to resources /// other than preopened directories. - pub fn find(self: *const Self, path: []const u8) ?*const Preopen { + pub fn find(self: Self, path: []const u8) ?*const Preopen { for (self.buffer.items) |preopen| { switch (preopen.@"type") { PreopenType.Dir => |preopen_path| { @@ -129,7 +129,7 @@ pub const PreopenList = struct { } /// Return the inner buffer as read-only slice. - pub fn asSlice(self: *const Self) []const Preopen { + pub fn asSlice(self: Self) []const Preopen { return self.buffer.items; } From f26ab568aaf60543d9fa09b53461458e5cdaddb3 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 17:06:02 +0200 Subject: [PATCH 08/13] Remove obsolete runtime panics from sleep impl --- lib/std/time.zig | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/std/time.zig b/lib/std/time.zig index 3c42fa7128..dd9a521543 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -40,16 +40,8 @@ pub fn sleep(nanoseconds: u64) void { var event: w.event_t = undefined; var nevents: usize = undefined; - switch (w.poll_oneoff(&in, &event, 1, &nevents)) { - w.ESUCCESS => {}, - else => |err| @panic("unexpected error of poll_oneoff"), - } - - if (nevents == 1 and event.userdata == userdata and event.@"error" == w.ESUCCESS and event.@"type" == w.EVENTTYPE_CLOCK) { - return; - } - - @panic("unexpected result of poll_oneoff"); + _ = w.poll_oneoff(&in, &event, 1, &nevents); + return; } const s = nanoseconds / ns_per_s; From 2a59ecd7eca7318e9a66d856b04dd9fa45b2d69d Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 17:10:02 +0200 Subject: [PATCH 09/13] Integrate getTestDir with tmpDir logic --- lib/std/io/test.zig | 47 ++++++++++++++--------- lib/std/os/test.zig | 93 +++++++++++++++++++++++++++------------------ lib/std/testing.zig | 33 ++++++++-------- 3 files changed, 100 insertions(+), 73 deletions(-) diff --git a/lib/std/io/test.zig b/lib/std/io/test.zig index cad3e6eb88..9ed02262b4 100644 --- a/lib/std/io/test.zig +++ b/lib/std/io/test.zig @@ -11,17 +11,18 @@ const mem = std.mem; const fs = std.fs; const File = std.fs.File; -const getTestDir = std.testing.getTestDir; +const tmpDir = std.testing.tmpDir; test "write a file, read it, then delete it" { - const test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); var data: [1024]u8 = undefined; var prng = DefaultPrng.init(1234); prng.random.bytes(data[0..]); const tmp_file_name = "temp_test_file.txt"; { - var file = try test_dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); var buf_stream = io.bufferedOutStream(file.outStream()); @@ -34,7 +35,7 @@ test "write a file, read it, then delete it" { { // Make sure the exclusive flag is honored. - if (test_dir.createFile(tmp_file_name, .{ .exclusive = true })) |file| { + if (tmp.dir.createFile(tmp_file_name, .{ .exclusive = true })) |file| { unreachable; } else |err| { std.debug.assert(err == File.OpenError.PathAlreadyExists); @@ -42,7 +43,7 @@ test "write a file, read it, then delete it" { } { - var file = try test_dir.openFile(tmp_file_name, .{}); + var file = try tmp.dir.openFile(tmp_file_name, .{}); defer file.close(); const file_size = try file.getEndPos(); @@ -58,14 +59,16 @@ test "write a file, read it, then delete it" { expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data)); expect(mem.eql(u8, contents[contents.len - "end".len ..], "end")); } - try test_dir.deleteFile(tmp_file_name); + try tmp.dir.deleteFile(tmp_file_name); } test "BitStreams with File Stream" { - var test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "temp_test_file.txt"; { - var file = try test_dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); var bit_stream = io.bitOutStream(builtin.endian, file.outStream()); @@ -79,7 +82,7 @@ test "BitStreams with File Stream" { try bit_stream.flushBits(); } { - var file = try test_dir.openFile(tmp_file_name, .{}); + var file = try tmp.dir.openFile(tmp_file_name, .{}); defer file.close(); var bit_stream = io.bitInStream(builtin.endian, file.inStream()); @@ -101,16 +104,18 @@ test "BitStreams with File Stream" { expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1)); } - try test_dir.deleteFile(tmp_file_name); + try tmp.dir.deleteFile(tmp_file_name); } test "File seek ops" { - var test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "temp_test_file.txt"; - var file = try test_dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(tmp_file_name, .{}); defer { file.close(); - test_dir.deleteFile(tmp_file_name) catch {}; + tmp.dir.deleteFile(tmp_file_name) catch {}; } try file.writeAll(&([_]u8{0x55} ** 8192)); @@ -133,12 +138,14 @@ test "setEndPos" { // https://github.com/ziglang/zig/issues/5127 if (std.Target.current.cpu.arch == .mips) return error.SkipZigTest; - var test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "temp_test_file.txt"; - var file = try test_dir.createFile(tmp_file_name, .{}); + var file = try tmp.dir.createFile(tmp_file_name, .{}); defer { file.close(); - test_dir.deleteFile(tmp_file_name) catch {}; + tmp.dir.deleteFile(tmp_file_name) catch {}; } // Verify that the file size changes and the file offset is not moved @@ -157,12 +164,14 @@ test "setEndPos" { } test "updateTimes" { - var test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const tmp_file_name = "just_a_temporary_file.txt"; - var file = try test_dir.createFile(tmp_file_name, .{ .read = true }); + var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true }); defer { file.close(); - test_dir.deleteFile(tmp_file_name) catch {}; + tmp.dir.deleteFile(tmp_file_name) catch {}; } var stat_old = try file.stat(); // Set atime and mtime to 5s before diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index 485b089928..cc3b4f5741 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -15,15 +15,18 @@ const a = std.testing.allocator; const builtin = @import("builtin"); const AtomicRmwOp = builtin.AtomicRmwOp; const AtomicOrder = builtin.AtomicOrder; -const getTestDir = std.testing.getTestDir; +const tmpDir = std.testing.tmpDir; +const Dir = std.fs.Dir; test "makePath, put some files in it, deleteTree" { - var test_dir = getTestDir(); - try test_dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); - try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); - try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); - try test_dir.deleteTree("os_test_tmp"); - if (test_dir.openDir("os_test_tmp", .{})) |dir| { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c"); + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "c" ++ fs.path.sep_str ++ "file.txt", "nonsense"); + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "b" ++ fs.path.sep_str ++ "file2.txt", "blah"); + try tmp.dir.deleteTree("os_test_tmp"); + if (tmp.dir.openDir("os_test_tmp", .{})) |dir| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); @@ -33,17 +36,19 @@ test "makePath, put some files in it, deleteTree" { test "access file" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - var test_dir = getTestDir(); - try test_dir.makePath("os_test_tmp"); - if (test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.makePath("os_test_tmp"); + if (tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{})) |ok| { @panic("expected error"); } else |err| { expect(err == error.FileNotFound); } - try test_dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); - try test_dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); - try test_dir.deleteTree("os_test_tmp"); + try tmp.dir.writeFile("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", ""); + try tmp.dir.access("os_test_tmp" ++ fs.path.sep_str ++ "file.txt", .{}); + try tmp.dir.deleteTree("os_test_tmp"); } fn testThreadIdFn(thread_id: *Thread.Id) void { @@ -51,11 +56,13 @@ fn testThreadIdFn(thread_id: *Thread.Id) void { } test "sendfile" { - var test_dir = getTestDir(); - try test_dir.makePath("os_test_tmp"); - defer test_dir.deleteTree("os_test_tmp") catch {}; + var tmp = tmpDir(.{}); + defer tmp.cleanup(); - var dir = try test_dir.openDir("os_test_tmp", .{}); + try tmp.dir.makePath("os_test_tmp"); + defer tmp.dir.deleteTree("os_test_tmp") catch {}; + + var dir = try tmp.dir.openDir("os_test_tmp", .{}); defer dir.close(); const line1 = "line1\n"; @@ -119,23 +126,24 @@ test "fs.copyFile" { const dest_file = "tmp_test_copy_file2.txt"; const dest_file2 = "tmp_test_copy_file3.txt"; - const test_dir = getTestDir(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); - try test_dir.writeFile(src_file, data); - defer test_dir.deleteFile(src_file) catch {}; + try tmp.dir.writeFile(src_file, data); + defer tmp.dir.deleteFile(src_file) catch {}; - try test_dir.copyFile(src_file, test_dir, dest_file, .{}); - defer test_dir.deleteFile(dest_file) catch {}; + try tmp.dir.copyFile(src_file, tmp.dir, dest_file, .{}); + defer tmp.dir.deleteFile(dest_file) catch {}; - try test_dir.copyFile(src_file, test_dir, dest_file2, .{ .override_mode = File.default_mode }); - defer test_dir.deleteFile(dest_file2) catch {}; + try tmp.dir.copyFile(src_file, tmp.dir, dest_file2, .{ .override_mode = File.default_mode }); + defer tmp.dir.deleteFile(dest_file2) catch {}; - try expectFileContents(dest_file, data); - try expectFileContents(dest_file2, data); + try expectFileContents(tmp.dir, dest_file, data); + try expectFileContents(tmp.dir, dest_file2, data); } -fn expectFileContents(file_path: []const u8, data: []const u8) !void { - const contents = try getTestDir().readFileAlloc(testing.allocator, file_path, 1000); +fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void { + const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000); defer testing.allocator.free(contents); testing.expectEqualSlices(u8, data, contents); @@ -199,18 +207,21 @@ test "AtomicFile" { \\ hello! \\ this is a test file ; - var test_dir = getTestDir(); + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + { - var af = try test_dir.atomicFile(test_out_file, .{}); + var af = try tmp.dir.atomicFile(test_out_file, .{}); defer af.deinit(); try af.file.writeAll(test_content); try af.finish(); } - const content = try test_dir.readFileAlloc(testing.allocator, test_out_file, 9999); + const content = try tmp.dir.readFileAlloc(testing.allocator, test_out_file, 9999); defer testing.allocator.free(content); expect(mem.eql(u8, content, test_content)); - try test_dir.deleteFile(test_out_file); + try tmp.dir.deleteFile(test_out_file); } test "thread local storage" { @@ -365,6 +376,9 @@ test "mmap" { if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + // Simple mmap() call with non page-aligned size { const data = try os.mmap( @@ -393,7 +407,7 @@ test "mmap" { // Create a file used for testing mmap() calls with a file descriptor { - const file = try fs.cwd().createFile(test_out_file, .{}); + const file = try tmp.dir.createFile(test_out_file, .{}); defer file.close(); const stream = file.outStream(); @@ -406,7 +420,7 @@ test "mmap" { // Map the whole file { - const file = try fs.cwd().openFile(test_out_file, .{}); + const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); const data = try os.mmap( @@ -430,7 +444,7 @@ test "mmap" { // Map the upper half of the file { - const file = try fs.cwd().openFile(test_out_file, .{}); + const file = try tmp.dir.openFile(test_out_file, .{}); defer file.close(); const data = try os.mmap( @@ -452,7 +466,7 @@ test "mmap" { } } - try fs.cwd().deleteFile(test_out_file); + try tmp.dir.deleteFile(test_out_file); } test "getenv" { @@ -467,12 +481,15 @@ test "fcntl" { if (builtin.os.tag == .windows or builtin.os.tag == .wasi) return error.SkipZigTest; + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const test_out_file = "os_tmp_test"; - const file = try fs.cwd().createFile(test_out_file, .{}); + const file = try tmp.dir.createFile(test_out_file, .{}); defer { file.close(); - fs.cwd().deleteFile(test_out_file) catch {}; + tmp.dir.deleteFile(test_out_file) catch {}; } // Note: The test assumes createFile opens the file with O_CLOEXEC diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 1592923ba8..34bebad043 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -14,21 +14,6 @@ pub var failing_allocator_instance = FailingAllocator.init(&base_allocator_insta pub var base_allocator_instance = std.heap.ThreadSafeFixedBufferAllocator.init(allocator_mem[0..]); var allocator_mem: [2 * 1024 * 1024]u8 = undefined; -/// This function is intended to be used only in tests. It should be used in any testcase -/// where we intend to test WASI and should be used a replacement for `std.fs.cwd()` in WASI. -pub fn getTestDir() std.fs.Dir { - if (@import("builtin").os.tag == .wasi) { - var preopens = std.fs.wasi.PreopenList.init(allocator); - defer preopens.deinit(); - preopens.populate() catch unreachable; - - const preopen = preopens.find(".") orelse unreachable; - return std.fs.Dir{ .fd = preopen.fd }; - } else { - return std.fs.cwd(); - } -} - /// This function is intended to be used only in tests. It prints diagnostics to stderr /// and then aborts when actual_error_union is not expected_error. pub fn expectError(expected_error: anyerror, actual_error_union: var) void { @@ -224,6 +209,21 @@ pub const TmpDir = struct { } }; +fn getCwdOrWasiPreopen() std.fs.Dir { + if (@import("builtin").os.tag == .wasi) { + var preopens = std.fs.wasi.PreopenList.init(allocator); + defer preopens.deinit(); + preopens.populate() catch + @panic("unable to make tmp dir for testing: unable to populate preopens"); + const preopen = preopens.find(".") orelse + @panic("unable to make tmp dir for testing: didn't find '.' in the preopens"); + + return std.fs.Dir{ .fd = preopen.fd }; + } else { + return std.fs.cwd(); + } +} + pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir { var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; std.crypto.randomBytes(&random_bytes) catch @@ -231,7 +231,8 @@ pub fn tmpDir(opts: std.fs.Dir.OpenDirOptions) TmpDir { var sub_path: [TmpDir.sub_path_len]u8 = undefined; std.fs.base64_encoder.encode(&sub_path, &random_bytes); - var cache_dir = std.fs.cwd().makeOpenPath("zig-cache", .{}) catch + var cwd = getCwdOrWasiPreopen(); + var cache_dir = cwd.makeOpenPath("zig-cache", .{}) catch @panic("unable to make tmp dir for testing: unable to make and open zig-cache dir"); defer cache_dir.close(); var parent_dir = cache_dir.makeOpenPath("tmp", .{}) catch From 34f84c36082015bb8aacfcfb27cecf44e8e6eb3a Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 17:45:06 +0200 Subject: [PATCH 10/13] Narrow down behaviour test cases; this removes wasmtime-enabled check in tests --- build.zig | 2 +- test/stage1/behavior.zig | 8 ++++++-- test/stage1/behavior/align.zig | 6 ++++++ test/stage1/behavior/shuffle.zig | 4 ++++ test/stage1/behavior/vector.zig | 4 ++++ test/tests.zig | 9 --------- 6 files changed, 21 insertions(+), 12 deletions(-) diff --git a/build.zig b/build.zig index 9e409fd874..4d71b0fb36 100644 --- a/build.zig +++ b/build.zig @@ -117,7 +117,7 @@ pub fn build(b: *Builder) !void { fmt_step.dependOn(&fmt_build_zig.step); // TODO for the moment, skip wasm32-wasi until bugs are sorted out. - test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, false, glibc_multi_dir)); + test_step.dependOn(tests.addPkgTests(b, test_filter, "test/stage1/behavior.zig", "behavior", "Run the behavior tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, is_wasmtime_enabled, glibc_multi_dir)); test_step.dependOn(tests.addPkgTests(b, test_filter, "lib/std/std.zig", "std", "Run the standard library tests", modes, false, skip_non_native, skip_libc, is_wine_enabled, is_qemu_enabled, is_wasmtime_enabled, glibc_multi_dir)); diff --git a/test/stage1/behavior.zig b/test/stage1/behavior.zig index 61b0c1aa56..cc498df959 100644 --- a/test/stage1/behavior.zig +++ b/test/stage1/behavior.zig @@ -1,9 +1,13 @@ +const builtin = @import("builtin"); + comptime { _ = @import("behavior/align.zig"); _ = @import("behavior/alignof.zig"); _ = @import("behavior/array.zig"); - _ = @import("behavior/asm.zig"); - _ = @import("behavior/async_fn.zig"); + if (builtin.os.tag != .wasi) { + _ = @import("behavior/asm.zig"); + _ = @import("behavior/async_fn.zig"); + } _ = @import("behavior/atomics.zig"); _ = @import("behavior/await_struct.zig"); _ = @import("behavior/bit_shifting.zig"); diff --git a/test/stage1/behavior/align.zig b/test/stage1/behavior/align.zig index e9b7e0f1d6..2d0e8f0982 100644 --- a/test/stage1/behavior/align.zig +++ b/test/stage1/behavior/align.zig @@ -133,6 +133,9 @@ fn alignedBig() align(16) i32 { } test "@alignCast functions" { + // TODO investigate why this fails when cross-compiled to wasm. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + expect(fnExpectsOnly1(simple4) == 0x19); } fn fnExpectsOnly1(ptr: fn () align(1) i32) i32 { @@ -324,6 +327,9 @@ test "align(@alignOf(T)) T does not force resolution of T" { } test "align(N) on functions" { + // TODO investigate why this fails when cross-compiled to wasm. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + expect((@ptrToInt(overaligned_fn) & (0x1000 - 1)) == 0); } fn overaligned_fn() align(0x1000) i32 { diff --git a/test/stage1/behavior/shuffle.zig b/test/stage1/behavior/shuffle.zig index 4281d645e5..3b6412b386 100644 --- a/test/stage1/behavior/shuffle.zig +++ b/test/stage1/behavior/shuffle.zig @@ -1,9 +1,13 @@ const std = @import("std"); +const builtin = @import("builtin"); const mem = std.mem; const expect = std.testing.expect; const Vector = std.meta.Vector; test "@shuffle" { + // TODO investigate why this fails when cross-compiling to wasm. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const S = struct { fn doTheTest() void { var v: Vector(4, i32) = [4]i32{ 2147483647, -2, 30, 40 }; diff --git a/test/stage1/behavior/vector.zig b/test/stage1/behavior/vector.zig index 2638bb42ff..851074e0d1 100644 --- a/test/stage1/behavior/vector.zig +++ b/test/stage1/behavior/vector.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const builtin = @import("builtin"); const mem = std.mem; const math = std.math; const expect = std.testing.expect; @@ -387,6 +388,9 @@ test "vector bitwise not operator" { } test "vector shift operators" { + // TODO investigate why this fails when cross-compiled to wasm. + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const S = struct { fn doTheTestShift(x: var, y: var) void { const N = @typeInfo(@TypeOf(x)).Array.len; diff --git a/test/tests.zig b/test/tests.zig index 3b70d0e36b..74aa9b105a 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -483,15 +483,6 @@ pub fn addPkgTests( if (skip_non_native and !test_target.target.isNative()) continue; - if (!is_wasmtime_enabled) { - if (test_target.target.os_tag) |tag| { - if (tag == .wasi) { - warn("Skipping {} on wasm32-wasi.\n", .{root_src}); - continue; - } - } - } - if (skip_libc and test_target.link_libc) continue; From 3d267bab71052e652c159a953d87df40f8377546 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 21:05:29 +0200 Subject: [PATCH 11/13] Re-enable refAllDecls gen and check in std.zig --- lib/std/build.zig | 4 +++- lib/std/fs/test.zig | 39 ++++++++++++++++++++++++------------- lib/std/std.zig | 7 +------ lib/std/zig/parser_test.zig | 7 +++++++ 4 files changed, 36 insertions(+), 21 deletions(-) diff --git a/lib/std/build.zig b/lib/std/build.zig index 031fc5d171..4fdb64903f 100644 --- a/lib/std/build.zig +++ b/lib/std/build.zig @@ -1061,6 +1061,8 @@ pub const Builder = struct { }; test "builder.findProgram compiles" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); @@ -1706,7 +1708,7 @@ pub const LibExeObjStep = struct { .Enum => |enum_info| { out.print("const {} = enum {{\n", .{@typeName(T)}) catch unreachable; inline for (enum_info.fields) |field| { - out.print(" {},\n", .{ field.name }) catch unreachable; + out.print(" {},\n", .{field.name}) catch unreachable; } out.print("}};\n", .{}) catch unreachable; }, diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 63b9c6e63a..3ca5503c1d 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1,7 +1,9 @@ const std = @import("../std.zig"); const builtin = std.builtin; const fs = std.fs; +const Dir = std.fs.Dir; const File = std.fs.File; +const tmpDir = std.testing.tmpDir; test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; @@ -15,16 +17,18 @@ const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; test "open file with exclusive nonblocking lock twice" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - const dir = fs.cwd(); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + const filename = "file_nonblocking_lock_test.txt"; - const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); defer file1.close(); - const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); std.debug.assert(std.meta.eql(file2, error.WouldBlock)); - dir.deleteFile(filename) catch |err| switch (err) { + tmp.dir.deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -40,9 +44,12 @@ test "open file with lock twice, make sure it wasn't open at the same time" { const filename = "file_lock_test.txt"; + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + var contexts = [_]FileLockTestContext{ - .{ .filename = filename, .create = true, .lock = .Exclusive }, - .{ .filename = filename, .create = true, .lock = .Exclusive }, + .{ .dir = tmp.dir, .filename = filename, .create = true, .lock = .Exclusive }, + .{ .dir = tmp.dir, .filename = filename, .create = true, .lock = .Exclusive }, }; try run_lock_file_test(&contexts); @@ -58,7 +65,7 @@ test "open file with lock twice, make sure it wasn't open at the same time" { std.debug.assert(!contexts[0].overlaps(&contexts[1])); - fs.cwd().deleteFile(filename) catch |err| switch (err) { + tmp.dir.deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -80,12 +87,15 @@ test "create file, lock and read from multiple process at once" { const filename = "file_read_lock_test.txt"; const filedata = "Hello, world!\n"; - try fs.cwd().writeFile(filename, filedata); + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.writeFile(filename, filedata); var contexts = [_]FileLockTestContext{ - .{ .filename = filename, .create = false, .lock = .Shared }, - .{ .filename = filename, .create = false, .lock = .Shared }, - .{ .filename = filename, .create = false, .lock = .Exclusive }, + .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Shared }, + .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Shared }, + .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Exclusive }, }; try run_lock_file_test(&contexts); @@ -108,7 +118,7 @@ test "create file, lock and read from multiple process at once" { std.debug.assert(contexts[0].bytes_read.? == filedata.len); std.debug.assert(contexts[1].bytes_read.? == filedata.len); - fs.cwd().deleteFile(filename) catch |err| switch (err) { + tmp.dir.deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -133,6 +143,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { } const FileLockTestContext = struct { + dir: Dir, filename: []const u8, pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null, @@ -154,12 +165,12 @@ const FileLockTestContext = struct { fn run(ctx: *@This()) void { var file: File = undefined; if (ctx.create) { - file = fs.cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { + file = ctx.dir.createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { ctx.err = err; return; }; } else { - file = fs.cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { + file = ctx.dir.openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { ctx.err = err; return; }; diff --git a/lib/std/std.zig b/lib/std/std.zig index 8fce2248cc..376c200200 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -75,10 +75,5 @@ comptime { } test "" { - // TODO is there a way around this? When enabled for WASI, we pick up functions - // which generate compile error. Perhaps semantic analyser should skip those - // if running in test mode? - if (builtin.os.tag != .wasi) { - meta.refAllDecls(@This()); - } + meta.refAllDecls(@This()); } diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 186a59e74d..0a3a47cf94 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -1,3 +1,5 @@ +const builtin = @import("builtin"); + test "recovery: top level" { try testError( \\test "" {inline} @@ -941,6 +943,9 @@ test "zig fmt: same-line doc comment on variable declaration" { } test "zig fmt: if-else with comment before else" { + // TODO investigate why this fails in wasm. + if (builtin.cpu.arch == .wasm32) return error.SkipZigTest; + try testCanonical( \\comptime { \\ // cexp(finite|nan +- i inf|nan) = nan + i nan @@ -1555,6 +1560,8 @@ test "zig fmt: comment after if before another if" { } test "zig fmt: line comment between if block and else keyword" { + // TODO investigate why this fails in wasm. + if (builtin.cpu.arch == .wasm32) return error.SkipZigTest; try testCanonical( \\test "aoeu" { \\ // cexp(finite|nan +- i inf|nan) = nan + i nan From 57719006bbae3d003f442864339ad7d30a8e05cc Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 21:17:49 +0200 Subject: [PATCH 12/13] Always return false for ANSI escape codes compat in WASI --- lib/std/fs/file.zig | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/std/fs/file.zig b/lib/std/fs/file.zig index 7e6a7dde63..f8cf69cbb8 100644 --- a/lib/std/fs/file.zig +++ b/lib/std/fs/file.zig @@ -143,11 +143,9 @@ pub const File = struct { } if (builtin.os.tag == .wasi) { // WASI sanitizes stdout when fd is a tty so ANSI escape codes - // will not be interpreted as actual cursor commands. - if (self.handle == os.STDOUT_FILENO and self.isTty()) return false; + // will not be interpreted as actual cursor commands, and // stderr is always sanitized. - if (self.handle == os.STDERR_FILENO) return false; - return true; + return false; } if (self.isTty()) { if (self.handle == os.STDOUT_FILENO or self.handle == os.STDERR_FILENO) { From cd8daa533ad7e53a3d071dcb782bc14c6fc72711 Mon Sep 17 00:00:00 2001 From: Jakub Konka Date: Mon, 18 May 2020 22:22:27 +0200 Subject: [PATCH 13/13] Undo accidentally checked-in changes to fs/test.zig --- lib/std/fs/test.zig | 40 ++++++++++++++-------------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 3ca5503c1d..a857c3a7f8 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1,9 +1,7 @@ const std = @import("../std.zig"); const builtin = std.builtin; const fs = std.fs; -const Dir = std.fs.Dir; const File = std.fs.File; -const tmpDir = std.testing.tmpDir; test "openSelfExe" { if (builtin.os.tag == .wasi) return error.SkipZigTest; @@ -17,18 +15,16 @@ const FILE_LOCK_TEST_SLEEP_TIME = 5 * std.time.millisecond; test "open file with exclusive nonblocking lock twice" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - + const dir = fs.cwd(); const filename = "file_nonblocking_lock_test.txt"; - const file1 = try tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const file1 = try dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); defer file1.close(); - const file2 = tmp.dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); + const file2 = dir.createFile(filename, .{ .lock = .Exclusive, .lock_nonblocking = true }); std.debug.assert(std.meta.eql(file2, error.WouldBlock)); - tmp.dir.deleteFile(filename) catch |err| switch (err) { + dir.deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -43,13 +39,9 @@ test "open file with lock twice, make sure it wasn't open at the same time" { } const filename = "file_lock_test.txt"; - - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - var contexts = [_]FileLockTestContext{ - .{ .dir = tmp.dir, .filename = filename, .create = true, .lock = .Exclusive }, - .{ .dir = tmp.dir, .filename = filename, .create = true, .lock = .Exclusive }, + .{ .filename = filename, .create = true, .lock = .Exclusive }, + .{ .filename = filename, .create = true, .lock = .Exclusive }, }; try run_lock_file_test(&contexts); @@ -65,7 +57,7 @@ test "open file with lock twice, make sure it wasn't open at the same time" { std.debug.assert(!contexts[0].overlaps(&contexts[1])); - tmp.dir.deleteFile(filename) catch |err| switch (err) { + fs.cwd().deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -87,15 +79,12 @@ test "create file, lock and read from multiple process at once" { const filename = "file_read_lock_test.txt"; const filedata = "Hello, world!\n"; - var tmp = tmpDir(.{}); - defer tmp.cleanup(); - - try tmp.dir.writeFile(filename, filedata); + try fs.cwd().writeFile(filename, filedata); var contexts = [_]FileLockTestContext{ - .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Shared }, - .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Shared }, - .{ .dir = tmp.dir, .filename = filename, .create = false, .lock = .Exclusive }, + .{ .filename = filename, .create = false, .lock = .Shared }, + .{ .filename = filename, .create = false, .lock = .Shared }, + .{ .filename = filename, .create = false, .lock = .Exclusive }, }; try run_lock_file_test(&contexts); @@ -118,7 +107,7 @@ test "create file, lock and read from multiple process at once" { std.debug.assert(contexts[0].bytes_read.? == filedata.len); std.debug.assert(contexts[1].bytes_read.? == filedata.len); - tmp.dir.deleteFile(filename) catch |err| switch (err) { + fs.cwd().deleteFile(filename) catch |err| switch (err) { error.FileNotFound => {}, else => return err, }; @@ -143,7 +132,6 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { } const FileLockTestContext = struct { - dir: Dir, filename: []const u8, pid: if (builtin.os.tag == .windows) ?void else ?std.os.pid_t = null, @@ -165,12 +153,12 @@ const FileLockTestContext = struct { fn run(ctx: *@This()) void { var file: File = undefined; if (ctx.create) { - file = ctx.dir.createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { + file = fs.cwd().createFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { ctx.err = err; return; }; } else { - file = ctx.dir.openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { + file = fs.cwd().openFile(ctx.filename, .{ .lock = ctx.lock }) catch |err| { ctx.err = err; return; };