test.zig 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. const std = @import("std");
  2. const builtin = @import("builtin");
  3. const testing = std.testing;
  4. const sep = std.fs.path.sep_str;
  5. const path_env_sep = if (builtin.os.tag == .windows) ";" else ":";
  6. const fixdeletetree = @import("fixdeletetree.zig");
  7. var child_env_map: std.BufMap = undefined;
  8. var path_env_ptr: *[]const u8 = undefined;
  9. fn setPathEnv(new_path: []const u8) void {
  10. path_env_ptr.* = new_path;
  11. std.log.info("PATH={s}", .{new_path});
  12. }
  13. pub fn main() !u8 {
  14. std.log.info("running test!", .{});
  15. try fixdeletetree.deleteTree(std.fs.cwd(), "scratch");
  16. try std.fs.cwd().makeDir("scratch");
  17. const bin_dir = "scratch" ++ sep ++ "bin";
  18. try std.fs.cwd().makeDir(bin_dir);
  19. const install_dir = if (builtin.os.tag == .windows) (bin_dir ++ "\\zig") else ("scratch/install");
  20. try std.fs.cwd().makeDir(install_dir);
  21. // NOTE: for now we are incorrectly assuming the install dir is CWD/zig-out
  22. const zigup = "." ++ sep ++ bin_dir ++ sep ++ "zigup" ++ builtin.target.exeFileExt();
  23. try std.fs.cwd().copyFile(
  24. "zig-out" ++ sep ++ "bin" ++ sep ++ "zigup" ++ builtin.target.exeFileExt(),
  25. std.fs.cwd(),
  26. zigup,
  27. .{},
  28. );
  29. const zigup_args = &[_][]const u8 { zigup } ++ (
  30. if (builtin.os.tag == .windows) &[_][]const u8 { } else &[_][]const u8 { "--install-dir", install_dir }
  31. );
  32. var allocator_store = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  33. defer allocator_store.deinit();
  34. const allocator = allocator_store.allocator();
  35. // add our scratch/bin directory to PATH
  36. child_env_map = try std.process.getEnvMap(allocator);
  37. path_env_ptr = bufMapGetEnvPtr(child_env_map, "PATH") orelse {
  38. std.log.err("the PATH environment variable does not exist?", .{});
  39. return 1;
  40. };
  41. const cwd = try std.process.getCwdAlloc(allocator);
  42. const original_path_env = path_env_ptr.*;
  43. {
  44. const scratch_bin_path = try std.fs.path.join(allocator, &.{ cwd, bin_dir });
  45. defer allocator.free(scratch_bin_path);
  46. setPathEnv(try std.mem.concat(allocator, u8, &.{ scratch_bin_path, path_env_sep, original_path_env}));
  47. }
  48. {
  49. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "master"});
  50. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  51. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "master has not been fetched"));
  52. }
  53. {
  54. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"-h"});
  55. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  56. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  57. }
  58. {
  59. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"--help"});
  60. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  61. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  62. }
  63. {
  64. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  65. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  66. try passOrDumpAndThrow(result);
  67. try testing.expect(std.mem.eql(u8, result.stdout, "<no-default>\n"));
  68. }
  69. {
  70. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"fetch-index"});
  71. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  72. try passOrDumpAndThrow(result);
  73. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "master"));
  74. }
  75. {
  76. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "0.5.0"});
  77. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  78. dumpExecResult(result);
  79. switch (result.term) {
  80. .Exited => |code| try testing.expectEqual(@as(u8, 1), code),
  81. else => |term| std.debug.panic("unexpected exit {}", .{term}),
  82. }
  83. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: compiler '0.5.0' is not installed\n"));
  84. }
  85. try runNoCapture(zigup_args ++ &[_][]const u8 {"0.5.0"});
  86. {
  87. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  88. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  89. try passOrDumpAndThrow(result);
  90. dumpExecResult(result);
  91. try testing.expect(std.mem.eql(u8, result.stdout, "0.5.0\n"));
  92. }
  93. {
  94. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"fetch", "0.5.0"});
  95. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  96. try passOrDumpAndThrow(result);
  97. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "already installed"));
  98. }
  99. try runNoCapture(zigup_args ++ &[_][]const u8 {"master"});
  100. try runNoCapture(zigup_args ++ &[_][]const u8 {"0.6.0"});
  101. {
  102. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  103. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  104. try passOrDumpAndThrow(result);
  105. dumpExecResult(result);
  106. try testing.expect(std.mem.eql(u8, result.stdout, "0.6.0\n"));
  107. }
  108. {
  109. const save_path_env = path_env_ptr.*;
  110. defer setPathEnv(save_path_env);
  111. setPathEnv("");
  112. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "master"});
  113. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  114. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, " is not in PATH"));
  115. }
  116. try runNoCapture(zigup_args ++ &[_][]const u8 {"default", "master"});
  117. {
  118. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"list"});
  119. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  120. try passOrDumpAndThrow(result);
  121. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.5.0"));
  122. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.6.0"));
  123. }
  124. try runNoCapture(zigup_args ++ &[_][]const u8 {"default", "0.5.0"});
  125. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  126. {
  127. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"run", "0.6.0", "version"});
  128. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  129. try testing.expectEqualSlices(u8, "0.6.0\n", result.stdout);
  130. }
  131. {
  132. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"run", "doesnotexist", "version"});
  133. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  134. try testing.expectEqualSlices(u8, "error: compiler 'doesnotexist' does not exist, fetch it first with: zigup fetch doesnotexist\n", result.stderr);
  135. }
  136. try runNoCapture(zigup_args ++ &[_][]const u8 {"keep", "0.6.0"});
  137. // doesn't delete anything because we have keepfile and master doens't get deleted
  138. try runNoCapture(zigup_args ++ &[_][]const u8 {"clean"});
  139. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  140. // Just make a directory to trick zigup into thinking there is another compiler so we don't have to wait for it to download/install
  141. try std.fs.cwd().makeDir(install_dir ++ sep ++ "0.9.0");
  142. try testing.expectEqual(@as(u32, 4), try getCompilerCount(install_dir));
  143. try runNoCapture(zigup_args ++ &[_][]const u8 {"clean"});
  144. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  145. {
  146. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"clean", "0.6.0"});
  147. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  148. try passOrDumpAndThrow(result);
  149. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "deleting "));
  150. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "0.6.0"));
  151. }
  152. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  153. {
  154. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"clean"});
  155. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  156. try passOrDumpAndThrow(result);
  157. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "it is master"));
  158. }
  159. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  160. try runNoCapture(zigup_args ++ &[_][]const u8 {"master"});
  161. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  162. {
  163. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"DOESNOTEXST"});
  164. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  165. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "HTTP request failed"));
  166. }
  167. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  168. // verify that we get an error if there is another compiler in the path
  169. {
  170. const bin2_dir = "scratch" ++ sep ++ "bin2";
  171. try std.fs.cwd().makeDir(bin2_dir);
  172. const previous_path = path_env_ptr.*;
  173. const scratch_bin2_path = try std.fs.path.join(allocator, &.{ cwd, bin2_dir });
  174. defer allocator.free(scratch_bin2_path);
  175. {
  176. var file = try std.fs.cwd().createFile(bin2_dir ++ sep ++ "zig" ++ builtin.target.exeFileExt(), .{});
  177. defer file.close();
  178. try file.writer().writeAll("a fake executable");
  179. }
  180. setPathEnv(try std.mem.concat(allocator, u8, &.{ scratch_bin2_path, path_env_sep, previous_path}));
  181. defer setPathEnv(previous_path);
  182. {
  183. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "0.5.0"});
  184. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  185. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, " is lower priority in PATH than "));
  186. }
  187. }
  188. std.log.info("Success", .{});
  189. return 0;
  190. }
  191. fn getCompilerCount(install_dir: []const u8) !u32 {
  192. var dir = try std.fs.cwd().openDir(install_dir, .{.iterate=true});
  193. defer dir.close();
  194. var it = dir.iterate();
  195. var count: u32 = 0;
  196. while (try it.next()) |entry| {
  197. if (entry.kind == .Directory) {
  198. count += 1;
  199. } else {
  200. if (builtin.os.tag == .windows) {
  201. try testing.expect(entry.kind == .File);
  202. } else {
  203. try testing.expect(entry.kind == .SymLink);
  204. }
  205. }
  206. }
  207. return count;
  208. }
  209. fn trailNl(s: []const u8) []const u8 {
  210. return if (s.len == 0 or s[s.len-1] != '\n') "\n" else "";
  211. }
  212. fn dumpExecResult(result: std.ChildProcess.ExecResult) void {
  213. if (result.stdout.len > 0) {
  214. std.debug.print("--- STDOUT ---\n{s}{s}--------------\n", .{result.stdout, trailNl(result.stdout)});
  215. }
  216. if (result.stderr.len > 0) {
  217. std.debug.print("--- STDERR ---\n{s}{s}--------------\n", .{result.stderr, trailNl(result.stderr)});
  218. }
  219. }
  220. fn runNoCapture(argv: []const []const u8) !void {
  221. var arena_store = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  222. defer arena_store.deinit();
  223. const result = try runCaptureOuts(arena_store.allocator(), argv);
  224. dumpExecResult(result);
  225. try passOrThrow(result.term);
  226. }
  227. fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.ChildProcess.ExecResult {
  228. {
  229. const cmd = try std.mem.join(allocator, " ", argv);
  230. defer allocator.free(cmd);
  231. std.log.info("RUN: {s}", .{cmd});
  232. }
  233. return try std.ChildProcess.exec(.{.allocator = allocator, .argv = argv, .env_map = &child_env_map});
  234. }
  235. fn passOrThrow(term: std.ChildProcess.Term) error{ChildProcessFailed}!void {
  236. if (!execResultPassed(term)) {
  237. std.log.err("child process failed with {}", .{term});
  238. return error.ChildProcessFailed;
  239. }
  240. }
  241. fn passOrDumpAndThrow(result: std.ChildProcess.ExecResult) error{ChildProcessFailed}!void {
  242. if (!execResultPassed(result.term)) {
  243. dumpExecResult(result);
  244. std.log.err("child process failed with {}", .{result.term});
  245. return error.ChildProcessFailed;
  246. }
  247. }
  248. fn execResultPassed(term: std.ChildProcess.Term) bool {
  249. switch (term) {
  250. .Exited => |code| return code == 0,
  251. else => return false,
  252. }
  253. }
  254. fn bufMapGetEnvPtr(buf_map: std.BufMap, env_name: []const u8) ?*[]const u8 {
  255. if (builtin.os.tag == .windows) {
  256. var it = buf_map.iterator();
  257. while (it.next()) |kv| {
  258. if (std.ascii.eqlIgnoreCase(env_name, kv.key_ptr.*)) {
  259. return kv.value_ptr;
  260. }
  261. }
  262. }
  263. return buf_map.getPtr(env_name);
  264. }