| author | |
| committer | |
| log | e3f36d0d81b85c213d03ffcd090ce19bfcbb3fbc |
| tree | 5459b5986e3ed68c0a3810c857f9bae4c2d2c6b7 |
| parent | 4f8aa8213d6314cba7ea21c5b13a5b9acbac3ad7 |
5 files changed, 208 insertions(+), 91 deletions(-)
test/standalone/env_vars/main.zig+2| ... | ... | @@ -3,6 +3,8 @@ const builtin = @import("builtin"); |
| 3 | 3 | |
| 4 | 4 | // Note: the environment variables under test are set by the build.zig |
| 5 | 5 | pub fn main() !void { |
| 6 | @setEvalBranchQuota(10000); | |
| 7 | ||
| 6 | 8 | var gpa: std.heap.GeneralPurposeAllocator(.{}) = .init; |
| 7 | 9 | defer _ = gpa.deinit(); |
| 8 | 10 | const allocator = gpa.allocator(); |
test/standalone/windows_argv/build.zig+1-1| ... | ... | @@ -67,7 +67,7 @@ pub fn build(b: *std.Build) !void { |
| 67 | 67 | |
| 68 | 68 | // Only target the MSVC ABI if MSVC/Windows SDK is available |
| 69 | 69 | const has_msvc = has_msvc: { |
| 70 | const sdk = std.zig.WindowsSdk.find(b.allocator, builtin.cpu.arch) catch |err| switch (err) { | |
| 70 | const sdk = std.zig.WindowsSdk.find(b.allocator, b.graph.io, builtin.cpu.arch) catch |err| switch (err) { | |
| 71 | 71 | error.OutOfMemory => @panic("oom"), |
| 72 | 72 | else => break :has_msvc false, |
| 73 | 73 | }; |
test/standalone/windows_bat_args/fuzz.zig+41-2| ... | ... | @@ -10,6 +10,7 @@ pub fn main() anyerror!void { |
| 10 | 10 | const gpa = debug_alloc_inst.allocator(); |
| 11 | 11 | |
| 12 | 12 | var threaded: Io.Threaded = .init(gpa, .{}); |
| 13 | defer threaded.deinit(); | |
| 13 | 14 | const io = threaded.io(); |
| 14 | 15 | |
| 15 | 16 | var it = try std.process.argsWithAllocator(gpa); |
| ... | ... | @@ -41,8 +42,8 @@ pub fn main() anyerror!void { |
| 41 | 42 | std.debug.print("rand seed: {}\n", .{seed}); |
| 42 | 43 | } |
| 43 | 44 | |
| 44 | var tmp = std.testing.tmpDir(.{}); | |
| 45 | defer tmp.cleanup(); | |
| 45 | var tmp = tmpDir(io, .{}); | |
| 46 | defer tmp.cleanup(io); | |
| 46 | 47 | |
| 47 | 48 | try std.process.setCurrentDir(io, tmp.dir); |
| 48 | 49 | defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; |
| ... | ... | @@ -167,3 +168,41 @@ fn randomArg(gpa: Allocator, rand: std.Random) ![]const u8 { |
| 167 | 168 | |
| 168 | 169 | return buf.toOwnedSlice(gpa); |
| 169 | 170 | } |
| 171 | ||
| 172 | pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { | |
| 173 | var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; | |
| 174 | std.crypto.random.bytes(&random_bytes); | |
| 175 | var sub_path: [TmpDir.sub_path_len]u8 = undefined; | |
| 176 | _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); | |
| 177 | ||
| 178 | const cwd = Io.Dir.cwd(); | |
| 179 | var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch | |
| 180 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); | |
| 181 | defer cache_dir.close(io); | |
| 182 | const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch | |
| 183 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); | |
| 184 | const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch | |
| 185 | @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); | |
| 186 | ||
| 187 | return .{ | |
| 188 | .dir = dir, | |
| 189 | .parent_dir = parent_dir, | |
| 190 | .sub_path = sub_path, | |
| 191 | }; | |
| 192 | } | |
| 193 | ||
| 194 | pub const TmpDir = struct { | |
| 195 | dir: Io.Dir, | |
| 196 | parent_dir: Io.Dir, | |
| 197 | sub_path: [sub_path_len]u8, | |
| 198 | ||
| 199 | const random_bytes_count = 12; | |
| 200 | const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); | |
| 201 | ||
| 202 | pub fn cleanup(self: *TmpDir, io: Io) void { | |
| 203 | self.dir.close(io); | |
| 204 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; | |
| 205 | self.parent_dir.close(io); | |
| 206 | self.* = undefined; | |
| 207 | } | |
| 208 | }; |
test/standalone/windows_bat_args/test.zig+86-48| ... | ... | @@ -15,8 +15,8 @@ pub fn main() anyerror!void { |
| 15 | 15 | _ = it.next() orelse unreachable; // skip binary name |
| 16 | 16 | const child_exe_path_orig = it.next() orelse unreachable; |
| 17 | 17 | |
| 18 | var tmp = std.testing.tmpDir(.{}); | |
| 19 | defer tmp.cleanup(); | |
| 18 | var tmp = tmpDir(io, .{}); | |
| 19 | defer tmp.cleanup(io); | |
| 20 | 20 | |
| 21 | 21 | try std.process.setCurrentDir(io, tmp.dir); |
| 22 | 22 | defer std.process.setCurrentDir(io, tmp.parent_dir) catch {}; |
| ... | ... | @@ -47,41 +47,41 @@ pub fn main() anyerror!void { |
| 47 | 47 | buf.shrinkRetainingCapacity(preamble_len); |
| 48 | 48 | |
| 49 | 49 | // Test cases are from https://github.com/rust-lang/rust/blob/master/tests/ui/std/windows-bat-args.rs |
| 50 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\x00"}); | |
| 51 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\n"}); | |
| 52 | try testExecError(error.InvalidBatchScriptArg, gpa, &.{"\r"}); | |
| 53 | try testExec(gpa, &.{ "a", "b" }, null); | |
| 54 | try testExec(gpa, &.{ "c is for cat", "d is for dog" }, null); | |
| 55 | try testExec(gpa, &.{ "\"", " \"" }, null); | |
| 56 | try testExec(gpa, &.{ "\\", "\\" }, null); | |
| 57 | try testExec(gpa, &.{">file.txt"}, null); | |
| 58 | try testExec(gpa, &.{"whoami.exe"}, null); | |
| 59 | try testExec(gpa, &.{"&a.exe"}, null); | |
| 60 | try testExec(gpa, &.{"&echo hello "}, null); | |
| 61 | try testExec(gpa, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 62 | try testExec(gpa, &.{"!TMP!"}, null); | |
| 63 | try testExec(gpa, &.{"key=value"}, null); | |
| 64 | try testExec(gpa, &.{"\"key=value\""}, null); | |
| 65 | try testExec(gpa, &.{"key = value"}, null); | |
| 66 | try testExec(gpa, &.{"key=[\"value\"]"}, null); | |
| 67 | try testExec(gpa, &.{ "", "a=b" }, null); | |
| 68 | try testExec(gpa, &.{"key=\"foo bar\""}, null); | |
| 69 | try testExec(gpa, &.{"key=[\"my_value]"}, null); | |
| 70 | try testExec(gpa, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 71 | try testExec(gpa, &.{"key\\=value"}, null); | |
| 72 | try testExec(gpa, &.{"key=\"&whoami\""}, null); | |
| 73 | try testExec(gpa, &.{"key=\"value\"=5"}, null); | |
| 74 | try testExec(gpa, &.{"key=[\">file.txt\"]"}, null); | |
| 75 | try testExec(gpa, &.{"%hello"}, null); | |
| 76 | try testExec(gpa, &.{"%PATH%"}, null); | |
| 77 | try testExec(gpa, &.{"%%cd:~,%"}, null); | |
| 78 | try testExec(gpa, &.{"%PATH%PATH%"}, null); | |
| 79 | try testExec(gpa, &.{"\">file.txt"}, null); | |
| 80 | try testExec(gpa, &.{"abc\"&echo hello"}, null); | |
| 81 | try testExec(gpa, &.{"123\">file.txt"}, null); | |
| 82 | try testExec(gpa, &.{"\"&echo hello&whoami.exe"}, null); | |
| 83 | try testExec(gpa, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 84 | try testExec(gpa, &.{"&whoami.exe"}, null); | |
| 50 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\x00"}); | |
| 51 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\n"}); | |
| 52 | try testExecError(error.InvalidBatchScriptArg, gpa, io, &.{"\r"}); | |
| 53 | try testExec(gpa, io, &.{ "a", "b" }, null); | |
| 54 | try testExec(gpa, io, &.{ "c is for cat", "d is for dog" }, null); | |
| 55 | try testExec(gpa, io, &.{ "\"", " \"" }, null); | |
| 56 | try testExec(gpa, io, &.{ "\\", "\\" }, null); | |
| 57 | try testExec(gpa, io, &.{">file.txt"}, null); | |
| 58 | try testExec(gpa, io, &.{"whoami.exe"}, null); | |
| 59 | try testExec(gpa, io, &.{"&a.exe"}, null); | |
| 60 | try testExec(gpa, io, &.{"&echo hello "}, null); | |
| 61 | try testExec(gpa, io, &.{ "&echo hello", "&whoami", ">file.txt" }, null); | |
| 62 | try testExec(gpa, io, &.{"!TMP!"}, null); | |
| 63 | try testExec(gpa, io, &.{"key=value"}, null); | |
| 64 | try testExec(gpa, io, &.{"\"key=value\""}, null); | |
| 65 | try testExec(gpa, io, &.{"key = value"}, null); | |
| 66 | try testExec(gpa, io, &.{"key=[\"value\"]"}, null); | |
| 67 | try testExec(gpa, io, &.{ "", "a=b" }, null); | |
| 68 | try testExec(gpa, io, &.{"key=\"foo bar\""}, null); | |
| 69 | try testExec(gpa, io, &.{"key=[\"my_value]"}, null); | |
| 70 | try testExec(gpa, io, &.{"key=[\"my_value\",\"other-value\"]"}, null); | |
| 71 | try testExec(gpa, io, &.{"key\\=value"}, null); | |
| 72 | try testExec(gpa, io, &.{"key=\"&whoami\""}, null); | |
| 73 | try testExec(gpa, io, &.{"key=\"value\"=5"}, null); | |
| 74 | try testExec(gpa, io, &.{"key=[\">file.txt\"]"}, null); | |
| 75 | try testExec(gpa, io, &.{"%hello"}, null); | |
| 76 | try testExec(gpa, io, &.{"%PATH%"}, null); | |
| 77 | try testExec(gpa, io, &.{"%%cd:~,%"}, null); | |
| 78 | try testExec(gpa, io, &.{"%PATH%PATH%"}, null); | |
| 79 | try testExec(gpa, io, &.{"\">file.txt"}, null); | |
| 80 | try testExec(gpa, io, &.{"abc\"&echo hello"}, null); | |
| 81 | try testExec(gpa, io, &.{"123\">file.txt"}, null); | |
| 82 | try testExec(gpa, io, &.{"\"&echo hello&whoami.exe"}, null); | |
| 83 | try testExec(gpa, io, &.{ "\"hello^\"world\"", "hello &echo oh no >file.txt" }, null); | |
| 84 | try testExec(gpa, io, &.{"&whoami.exe"}, null); | |
| 85 | 85 | |
| 86 | 86 | // Ensure that trailing space and . characters can't lead to unexpected bat/cmd script execution. |
| 87 | 87 | // In many Windows APIs (including CreateProcess), trailing space and . characters are stripped |
| ... | ... | @@ -99,14 +99,14 @@ pub fn main() anyerror!void { |
| 99 | 99 | // > "args1.bat .. " |
| 100 | 100 | // '"args1.bat .. "' is not recognized as an internal or external command, |
| 101 | 101 | // operable program or batch file. |
| 102 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, "args1.bat .. ", &.{"abc"}, null)); | |
| 102 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, "args1.bat .. ", &.{"abc"}, null)); | |
| 103 | 103 | const absolute_with_trailing = blk: { |
| 104 | const absolute_path = try Io.Dir.realPathFileAbsoluteAlloc(io, "args1.bat", gpa); | |
| 104 | const absolute_path = try Io.Dir.cwd().realPathFileAlloc(io, "args1.bat", gpa); | |
| 105 | 105 | defer gpa.free(absolute_path); |
| 106 | 106 | break :blk try std.mem.concat(gpa, u8, &.{ absolute_path, " .. " }); |
| 107 | 107 | }; |
| 108 | 108 | defer gpa.free(absolute_with_trailing); |
| 109 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, absolute_with_trailing, &.{"abc"}, null)); | |
| 109 | try std.testing.expectError(error.FileNotFound, testExecBat(gpa, io, absolute_with_trailing, &.{"abc"}, null)); | |
| 110 | 110 | |
| 111 | 111 | var env = env: { |
| 112 | 112 | var env = try std.process.getEnvMap(gpa); |
| ... | ... | @@ -120,20 +120,20 @@ pub fn main() anyerror!void { |
| 120 | 120 | break :env env; |
| 121 | 121 | }; |
| 122 | 122 | defer env.deinit(); |
| 123 | try testExec(gpa, &.{"%FOO%"}, &env); | |
| 123 | try testExec(gpa, io, &.{"%FOO%"}, &env); | |
| 124 | 124 | |
| 125 | 125 | // Ensure that none of the `>file.txt`s have caused file.txt to be created |
| 126 | try std.testing.expectError(error.FileNotFound, tmp.dir.access("file.txt", .{})); | |
| 126 | try std.testing.expectError(error.FileNotFound, tmp.dir.access(io, "file.txt", .{})); | |
| 127 | 127 | } |
| 128 | 128 | |
| 129 | fn testExecError(err: anyerror, gpa: Allocator, args: []const []const u8) !void { | |
| 130 | return std.testing.expectError(err, testExec(gpa, args, null)); | |
| 129 | fn testExecError(err: anyerror, gpa: Allocator, io: Io, args: []const []const u8) !void { | |
| 130 | return std.testing.expectError(err, testExec(gpa, io, args, null)); | |
| 131 | 131 | } |
| 132 | 132 | |
| 133 | fn testExec(gpa: Allocator, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 134 | try testExecBat(gpa, "args1.bat", args, env); | |
| 135 | try testExecBat(gpa, "args2.bat", args, env); | |
| 136 | try testExecBat(gpa, "args3.bat", args, env); | |
| 133 | fn testExec(gpa: Allocator, io: Io, args: []const []const u8, env: ?*std.process.EnvMap) !void { | |
| 134 | try testExecBat(gpa, io, "args1.bat", args, env); | |
| 135 | try testExecBat(gpa, io, "args2.bat", args, env); | |
| 136 | try testExecBat(gpa, io, "args3.bat", args, env); | |
| 137 | 137 | } |
| 138 | 138 | |
| 139 | 139 | fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8, env: ?*std.process.EnvMap) !void { |
| ... | ... | @@ -164,3 +164,41 @@ fn testExecBat(gpa: Allocator, io: Io, bat: []const u8, args: []const []const u8 |
| 164 | 164 | i += 1; |
| 165 | 165 | } |
| 166 | 166 | } |
| 167 | ||
| 168 | pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { | |
| 169 | var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; | |
| 170 | std.crypto.random.bytes(&random_bytes); | |
| 171 | var sub_path: [TmpDir.sub_path_len]u8 = undefined; | |
| 172 | _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); | |
| 173 | ||
| 174 | const cwd = Io.Dir.cwd(); | |
| 175 | var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch | |
| 176 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); | |
| 177 | defer cache_dir.close(io); | |
| 178 | const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch | |
| 179 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); | |
| 180 | const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch | |
| 181 | @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); | |
| 182 | ||
| 183 | return .{ | |
| 184 | .dir = dir, | |
| 185 | .parent_dir = parent_dir, | |
| 186 | .sub_path = sub_path, | |
| 187 | }; | |
| 188 | } | |
| 189 | ||
| 190 | pub const TmpDir = struct { | |
| 191 | dir: Io.Dir, | |
| 192 | parent_dir: Io.Dir, | |
| 193 | sub_path: [sub_path_len]u8, | |
| 194 | ||
| 195 | const random_bytes_count = 12; | |
| 196 | const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); | |
| 197 | ||
| 198 | pub fn cleanup(self: *TmpDir, io: Io) void { | |
| 199 | self.dir.close(io); | |
| 200 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; | |
| 201 | self.parent_dir.close(io); | |
| 202 | self.* = undefined; | |
| 203 | } | |
| 204 | }; |
test/standalone/windows_spawn/main.zig+78-40| ... | ... | @@ -19,8 +19,8 @@ pub fn main() anyerror!void { |
| 19 | 19 | _ = it.next() orelse unreachable; // skip binary name |
| 20 | 20 | const hello_exe_cache_path = it.next() orelse unreachable; |
| 21 | 21 | |
| 22 | var tmp = std.testing.tmpDir(.{}); | |
| 23 | defer tmp.cleanup(); | |
| 22 | var tmp = tmpDir(io, .{}); | |
| 23 | defer tmp.cleanup(io); | |
| 24 | 24 | |
| 25 | 25 | const tmp_absolute_path = try tmp.dir.realPathFileAlloc(io, ".", gpa); |
| 26 | 26 | defer gpa.free(tmp_absolute_path); |
| ... | ... | @@ -44,10 +44,10 @@ pub fn main() anyerror!void { |
| 44 | 44 | ) == windows.TRUE); |
| 45 | 45 | |
| 46 | 46 | // No PATH, so it should fail to find anything not in the cwd |
| 47 | try testExecError(error.FileNotFound, gpa, "something_missing"); | |
| 47 | try testExecError(error.FileNotFound, gpa, io, "something_missing"); | |
| 48 | 48 | |
| 49 | 49 | // make sure we don't get error.BadPath traversing out of cwd with a relative path |
| 50 | try testExecError(error.FileNotFound, gpa, "..\\.\\.\\.\\\\..\\more_missing"); | |
| 50 | try testExecError(error.FileNotFound, gpa, io, "..\\.\\.\\.\\\\..\\more_missing"); | |
| 51 | 51 | |
| 52 | 52 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| 53 | 53 | utf16Literal("PATH"), |
| ... | ... | @@ -55,12 +55,12 @@ pub fn main() anyerror!void { |
| 55 | 55 | ) == windows.TRUE); |
| 56 | 56 | |
| 57 | 57 | // Move hello.exe into the tmp dir which is now added to the path |
| 58 | try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", .{}); | |
| 58 | try Io.Dir.cwd().copyFile(hello_exe_cache_path, tmp.dir, "hello.exe", io, .{}); | |
| 59 | 59 | |
| 60 | 60 | // with extension should find the .exe (case insensitive) |
| 61 | try testExec(gpa, "HeLLo.exe", "hello from exe\n"); | |
| 61 | try testExec(gpa, io, "HeLLo.exe", "hello from exe\n"); | |
| 62 | 62 | // without extension should find the .exe (case insensitive) |
| 63 | try testExec(gpa, "heLLo", "hello from exe\n"); | |
| 63 | try testExec(gpa, io, "heLLo", "hello from exe\n"); | |
| 64 | 64 | // with invalid cwd |
| 65 | 65 | try std.testing.expectError(error.FileNotFound, testExecWithCwd(gpa, io, "hello.exe", "missing_dir", "")); |
| 66 | 66 | |
| ... | ... | @@ -70,33 +70,33 @@ pub fn main() anyerror!void { |
| 70 | 70 | try tmp.dir.writeFile(io, .{ .sub_path = "hello.cmd", .data = "@echo hello from cmd" }); |
| 71 | 71 | |
| 72 | 72 | // with extension should find the .bat (case insensitive) |
| 73 | try testExec(gpa, "heLLo.bat", "hello from bat\r\n"); | |
| 73 | try testExec(gpa, io, "heLLo.bat", "hello from bat\r\n"); | |
| 74 | 74 | // with extension should find the .cmd (case insensitive) |
| 75 | try testExec(gpa, "heLLo.cmd", "hello from cmd\r\n"); | |
| 75 | try testExec(gpa, io, "heLLo.cmd", "hello from cmd\r\n"); | |
| 76 | 76 | // without extension should find the .exe (since its first in PATHEXT) |
| 77 | try testExec(gpa, "heLLo", "hello from exe\n"); | |
| 77 | try testExec(gpa, io, "heLLo", "hello from exe\n"); | |
| 78 | 78 | |
| 79 | 79 | // now rename the exe to not have an extension |
| 80 | try renameExe(tmp.dir, "hello.exe", "hello"); | |
| 80 | try renameExe(tmp.dir, io, "hello.exe", "hello"); | |
| 81 | 81 | |
| 82 | 82 | // with extension should now fail |
| 83 | try testExecError(error.FileNotFound, gpa, "hello.exe"); | |
| 83 | try testExecError(error.FileNotFound, gpa, io, "hello.exe"); | |
| 84 | 84 | // without extension should succeed (case insensitive) |
| 85 | try testExec(gpa, "heLLo", "hello from exe\n"); | |
| 85 | try testExec(gpa, io, "heLLo", "hello from exe\n"); | |
| 86 | 86 | |
| 87 | 87 | try tmp.dir.createDir(io, "something", .default_dir); |
| 88 | try renameExe(tmp.dir, "hello", "something/hello.exe"); | |
| 88 | try renameExe(tmp.dir, io, "hello", "something/hello.exe"); | |
| 89 | 89 | |
| 90 | 90 | const relative_path_no_ext = try std.fs.path.join(gpa, &.{ tmp_relative_path, "something/hello" }); |
| 91 | 91 | defer gpa.free(relative_path_no_ext); |
| 92 | 92 | |
| 93 | 93 | // Giving a full relative path to something/hello should work |
| 94 | try testExec(gpa, relative_path_no_ext, "hello from exe\n"); | |
| 94 | try testExec(gpa, io, relative_path_no_ext, "hello from exe\n"); | |
| 95 | 95 | // But commands with path separators get excluded from PATH searching, so this will fail |
| 96 | try testExecError(error.FileNotFound, gpa, "something/hello"); | |
| 96 | try testExecError(error.FileNotFound, gpa, io, "something/hello"); | |
| 97 | 97 | |
| 98 | 98 | // Now that .BAT is the first PATHEXT that should be found, this should succeed |
| 99 | try testExec(gpa, "heLLo", "hello from bat\r\n"); | |
| 99 | try testExec(gpa, io, "heLLo", "hello from bat\r\n"); | |
| 100 | 100 | |
| 101 | 101 | // Add a hello.exe that is not a valid executable |
| 102 | 102 | try tmp.dir.writeFile(io, .{ .sub_path = "hello.exe", .data = "invalid" }); |
| ... | ... | @@ -105,26 +105,26 @@ pub fn main() anyerror!void { |
| 105 | 105 | // case for .EXE extensions, where if they ever try to get executed but they are |
| 106 | 106 | // invalid, that gets treated as a fatal error wherever they are found and InvalidExe |
| 107 | 107 | // is returned immediately. |
| 108 | try testExecError(error.InvalidExe, gpa, "hello.exe"); | |
| 108 | try testExecError(error.InvalidExe, gpa, io, "hello.exe"); | |
| 109 | 109 | // Same thing applies to the command with no extension--even though there is a |
| 110 | 110 | // hello.bat that could be executed, it should stop after it tries executing |
| 111 | 111 | // hello.exe and getting InvalidExe. |
| 112 | try testExecError(error.InvalidExe, gpa, "hello"); | |
| 112 | try testExecError(error.InvalidExe, gpa, io, "hello"); | |
| 113 | 113 | |
| 114 | 114 | // If we now rename hello.exe to have no extension, it will behave differently |
| 115 | try renameExe(tmp.dir, "hello.exe", "hello"); | |
| 115 | try renameExe(tmp.dir, io, "hello.exe", "hello"); | |
| 116 | 116 | |
| 117 | 117 | // Now, trying to execute it without an extension should treat InvalidExe as recoverable |
| 118 | 118 | // and skip over it and find hello.bat and execute that |
| 119 | try testExec(gpa, "hello", "hello from bat\r\n"); | |
| 119 | try testExec(gpa, io, "hello", "hello from bat\r\n"); | |
| 120 | 120 | |
| 121 | 121 | // If we rename the invalid exe to something else |
| 122 | try renameExe(tmp.dir, "hello", "goodbye"); | |
| 122 | try renameExe(tmp.dir, io, "hello", "goodbye"); | |
| 123 | 123 | // Then we should now get FileNotFound when trying to execute 'goodbye', |
| 124 | 124 | // since that is what the original error will be after searching for 'goodbye' |
| 125 | 125 | // in the cwd. It will try to execute 'goodbye' from the PATH but the InvalidExe error |
| 126 | 126 | // should be ignored in this case. |
| 127 | try testExecError(error.FileNotFound, gpa, "goodbye"); | |
| 127 | try testExecError(error.FileNotFound, gpa, io, "goodbye"); | |
| 128 | 128 | |
| 129 | 129 | // Now let's set the tmp dir as the cwd and set the path only include the "something" sub dir |
| 130 | 130 | try std.process.setCurrentDir(io, tmp.dir); |
| ... | ... | @@ -139,26 +139,26 @@ pub fn main() anyerror!void { |
| 139 | 139 | |
| 140 | 140 | // Now trying to execute goodbye should give error.InvalidExe since it's the original |
| 141 | 141 | // error that we got when trying within the cwd |
| 142 | try testExecError(error.InvalidExe, gpa, "goodbye"); | |
| 142 | try testExecError(error.InvalidExe, gpa, io, "goodbye"); | |
| 143 | 143 | |
| 144 | 144 | // hello should still find the .bat |
| 145 | try testExec(gpa, "hello", "hello from bat\r\n"); | |
| 145 | try testExec(gpa, io, "hello", "hello from bat\r\n"); | |
| 146 | 146 | |
| 147 | 147 | // If we rename something/hello.exe to something/goodbye.exe |
| 148 | try renameExe(tmp.dir, "something/hello.exe", "something/goodbye.exe"); | |
| 148 | try renameExe(tmp.dir, io, "something/hello.exe", "something/goodbye.exe"); | |
| 149 | 149 | // And try to execute goodbye, then the one in something should be found |
| 150 | 150 | // since the one in cwd is an invalid executable |
| 151 | try testExec(gpa, "goodbye", "hello from exe\n"); | |
| 151 | try testExec(gpa, io, "goodbye", "hello from exe\n"); | |
| 152 | 152 | |
| 153 | 153 | // If we use an absolute path to execute the invalid goodbye |
| 154 | 154 | const goodbye_abs_path = try std.mem.join(gpa, "\\", &.{ tmp_absolute_path, "goodbye" }); |
| 155 | 155 | defer gpa.free(goodbye_abs_path); |
| 156 | 156 | // then the PATH should not be searched and we should get InvalidExe |
| 157 | try testExecError(error.InvalidExe, gpa, goodbye_abs_path); | |
| 157 | try testExecError(error.InvalidExe, gpa, io, goodbye_abs_path); | |
| 158 | 158 | |
| 159 | 159 | // If we try to exec but provide a cwd that is an absolute path, the PATH |
| 160 | 160 | // should still be searched and the goodbye.exe in something should be found. |
| 161 | try testExecWithCwd(gpa, "goodbye", tmp_absolute_path, "hello from exe\n"); | |
| 161 | try testExecWithCwd(gpa, io, "goodbye", tmp_absolute_path, "hello from exe\n"); | |
| 162 | 162 | |
| 163 | 163 | // introduce some extra path separators into the path which is dealt with inside the spawn call. |
| 164 | 164 | const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\")); |
| ... | ... | @@ -177,20 +177,20 @@ pub fn main() anyerror!void { |
| 177 | 177 | null, |
| 178 | 178 | ) == windows.TRUE); |
| 179 | 179 | |
| 180 | try testExecWithCwd(gpa, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); | |
| 180 | try testExecWithCwd(gpa, io, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); | |
| 181 | 181 | |
| 182 | 182 | // normalization should also work if the non-normalized path is found in the PATH var. |
| 183 | 183 | std.debug.assert(windows.kernel32.SetEnvironmentVariableW( |
| 184 | 184 | utf16Literal("PATH"), |
| 185 | 185 | denormed_something_subdir_abs_path, |
| 186 | 186 | ) == windows.TRUE); |
| 187 | try testExec(gpa, "goodbye", "hello from exe\n"); | |
| 187 | try testExec(gpa, io, "goodbye", "hello from exe\n"); | |
| 188 | 188 | |
| 189 | 189 | // now make sure we can launch executables "outside" of the cwd |
| 190 | var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); | |
| 190 | var subdir_cwd = try tmp.dir.openDir(io, denormed_something_subdir_wtf8, .{}); | |
| 191 | 191 | defer subdir_cwd.close(io); |
| 192 | 192 | |
| 193 | try renameExe(tmp.dir, "something/goodbye.exe", "hello.exe"); | |
| 193 | try renameExe(tmp.dir, io, "something/goodbye.exe", "hello.exe"); | |
| 194 | 194 | try std.process.setCurrentDir(io, subdir_cwd); |
| 195 | 195 | |
| 196 | 196 | // clear the PATH again |
| ... | ... | @@ -200,15 +200,15 @@ pub fn main() anyerror!void { |
| 200 | 200 | ) == windows.TRUE); |
| 201 | 201 | |
| 202 | 202 | // while we're at it make sure non-windows separators work fine |
| 203 | try testExec(gpa, "../hello", "hello from exe\n"); | |
| 203 | try testExec(gpa, io, "../hello", "hello from exe\n"); | |
| 204 | 204 | } |
| 205 | 205 | |
| 206 | fn testExecError(err: anyerror, gpa: Allocator, command: []const u8) !void { | |
| 207 | return std.testing.expectError(err, testExec(gpa, command, "")); | |
| 206 | fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !void { | |
| 207 | return std.testing.expectError(err, testExec(gpa, io, command, "")); | |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | fn testExec(gpa: Allocator, command: []const u8, expected_stdout: []const u8) !void { | |
| 211 | return testExecWithCwd(gpa, command, null, expected_stdout); | |
| 210 | fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void { | |
| 211 | return testExecWithCwd(gpa, io, command, null, expected_stdout); | |
| 212 | 212 | } |
| 213 | 213 | |
| 214 | 214 | fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { |
| ... | ... | @@ -223,9 +223,9 @@ fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8 |
| 223 | 223 | try std.testing.expectEqualStrings(expected_stdout, result.stdout); |
| 224 | 224 | } |
| 225 | 225 | |
| 226 | fn renameExe(dir: Io.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !void { | |
| 226 | fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { | |
| 227 | 227 | var attempt: u5 = 0; |
| 228 | while (true) break dir.rename(old_sub_path, new_sub_path) catch |err| switch (err) { | |
| 228 | while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { | |
| 229 | 229 | error.AccessDenied => { |
| 230 | 230 | if (attempt == 13) return error.AccessDenied; |
| 231 | 231 | // give the kernel a chance to finish closing the executable handle |
| ... | ... | @@ -236,3 +236,41 @@ fn renameExe(dir: Io.Dir, old_sub_path: []const u8, new_sub_path: []const u8) !v |
| 236 | 236 | else => |e| return e, |
| 237 | 237 | }; |
| 238 | 238 | } |
| 239 | ||
| 240 | pub fn tmpDir(io: Io, opts: Io.Dir.OpenOptions) TmpDir { | |
| 241 | var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; | |
| 242 | std.crypto.random.bytes(&random_bytes); | |
| 243 | var sub_path: [TmpDir.sub_path_len]u8 = undefined; | |
| 244 | _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); | |
| 245 | ||
| 246 | const cwd = Io.Dir.cwd(); | |
| 247 | var cache_dir = cwd.createDirPathOpen(io, ".zig-cache", .{}) catch | |
| 248 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache dir"); | |
| 249 | defer cache_dir.close(io); | |
| 250 | const parent_dir = cache_dir.createDirPathOpen(io, "tmp", .{}) catch | |
| 251 | @panic("unable to make tmp dir for testing: unable to make and open .zig-cache/tmp dir"); | |
| 252 | const dir = parent_dir.createDirPathOpen(io, &sub_path, .{ .open_options = opts }) catch | |
| 253 | @panic("unable to make tmp dir for testing: unable to make and open the tmp dir"); | |
| 254 | ||
| 255 | return .{ | |
| 256 | .dir = dir, | |
| 257 | .parent_dir = parent_dir, | |
| 258 | .sub_path = sub_path, | |
| 259 | }; | |
| 260 | } | |
| 261 | ||
| 262 | pub const TmpDir = struct { | |
| 263 | dir: Io.Dir, | |
| 264 | parent_dir: Io.Dir, | |
| 265 | sub_path: [sub_path_len]u8, | |
| 266 | ||
| 267 | const random_bytes_count = 12; | |
| 268 | const sub_path_len = std.fs.base64_encoder.calcSize(random_bytes_count); | |
| 269 | ||
| 270 | pub fn cleanup(self: *TmpDir, io: Io) void { | |
| 271 | self.dir.close(io); | |
| 272 | self.parent_dir.deleteTree(io, &self.sub_path) catch {}; | |
| 273 | self.parent_dir.close(io); | |
| 274 | self.* = undefined; | |
| 275 | } | |
| 276 | }; |