test.zig 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  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.process.EnvMap = 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. // For some odd reason, the "zig version" output is different on macos
  14. const expected_zig_version_0_7_0 = if (builtin.os.tag == .macos) "0.7.0+9af53f8e0" else "0.7.0";
  15. pub fn main() !u8 {
  16. std.log.info("running test!", .{});
  17. try fixdeletetree.deleteTree(std.fs.cwd(), "scratch");
  18. try std.fs.cwd().makeDir("scratch");
  19. const bin_dir = "scratch" ++ sep ++ "bin";
  20. try std.fs.cwd().makeDir(bin_dir);
  21. const install_dir = if (builtin.os.tag == .windows) (bin_dir ++ "\\zig") else ("scratch/install");
  22. try std.fs.cwd().makeDir(install_dir);
  23. // NOTE: for now we are incorrectly assuming the install dir is CWD/zig-out
  24. const zigup = comptime "." ++ sep ++ bin_dir ++ sep ++ "zigup" ++ builtin.target.exeFileExt();
  25. try std.fs.cwd().copyFile(
  26. comptime "zig-out" ++ sep ++ "bin" ++ sep ++ "zigup" ++ builtin.target.exeFileExt(),
  27. std.fs.cwd(),
  28. zigup,
  29. .{},
  30. );
  31. const zigup_args = &[_][]const u8 { zigup } ++ (
  32. if (builtin.os.tag == .windows) &[_][]const u8 { } else &[_][]const u8 { "--install-dir", install_dir }
  33. );
  34. var allocator_store = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  35. defer allocator_store.deinit();
  36. const allocator = allocator_store.allocator();
  37. const path_link = try std.fs.path.join(allocator, &.{ bin_dir, comptime "zig" ++ builtin.target.exeFileExt() });
  38. defer allocator.free(path_link);
  39. // add our scratch/bin directory to PATH
  40. child_env_map = try std.process.getEnvMap(allocator);
  41. path_env_ptr = child_env_map.getPtr("PATH") orelse {
  42. std.log.err("the PATH environment variable does not exist?", .{});
  43. return 1;
  44. };
  45. const cwd = try std.process.getCwdAlloc(allocator);
  46. const original_path_env = path_env_ptr.*;
  47. {
  48. const scratch_bin_path = try std.fs.path.join(allocator, &.{ cwd, bin_dir });
  49. defer allocator.free(scratch_bin_path);
  50. setPathEnv(try std.mem.concat(allocator, u8, &.{ scratch_bin_path, path_env_sep, original_path_env}));
  51. }
  52. {
  53. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "master"});
  54. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  55. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "master has not been fetched"));
  56. }
  57. {
  58. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"-h"});
  59. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  60. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  61. }
  62. {
  63. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"--help"});
  64. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  65. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  66. }
  67. {
  68. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  69. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  70. try passOrDumpAndThrow(result);
  71. try testing.expect(std.mem.eql(u8, result.stdout, "<no-default>\n"));
  72. }
  73. {
  74. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"fetch-index"});
  75. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  76. try passOrDumpAndThrow(result);
  77. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "master"));
  78. }
  79. {
  80. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "0.7.0"});
  81. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  82. dumpExecResult(result);
  83. switch (result.term) {
  84. .Exited => |code| try testing.expectEqual(@as(u8, 1), code),
  85. else => |term| std.debug.panic("unexpected exit {}", .{term}),
  86. }
  87. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: compiler '0.7.0' is not installed\n"));
  88. }
  89. try runNoCapture(zigup_args ++ &[_][]const u8 {"0.7.0"});
  90. {
  91. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  92. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  93. try passOrDumpAndThrow(result);
  94. dumpExecResult(result);
  95. try testing.expect(std.mem.eql(u8, result.stdout, "0.7.0\n"));
  96. }
  97. {
  98. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"fetch", "0.7.0"});
  99. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  100. try passOrDumpAndThrow(result);
  101. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "already installed"));
  102. }
  103. try runNoCapture(zigup_args ++ &[_][]const u8 {"master"});
  104. try runNoCapture(zigup_args ++ &[_][]const u8 {"0.8.0"});
  105. {
  106. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default"});
  107. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  108. try passOrDumpAndThrow(result);
  109. dumpExecResult(result);
  110. try testing.expect(std.mem.eql(u8, result.stdout, "0.8.0\n"));
  111. }
  112. {
  113. const save_path_env = path_env_ptr.*;
  114. defer setPathEnv(save_path_env);
  115. setPathEnv("");
  116. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "master"});
  117. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  118. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, " is not in PATH"));
  119. }
  120. try runNoCapture(zigup_args ++ &[_][]const u8 {"default", "master"});
  121. {
  122. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"list"});
  123. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  124. try passOrDumpAndThrow(result);
  125. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.7.0"));
  126. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.8.0"));
  127. }
  128. try runNoCapture(zigup_args ++ &[_][]const u8 {"default", "0.7.0"});
  129. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  130. {
  131. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"run", "0.8.0", "version"});
  132. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  133. try testing.expectEqualSlices(u8, "0.8.0\n", result.stdout);
  134. }
  135. {
  136. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"run", "doesnotexist", "version"});
  137. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  138. try testing.expectEqualSlices(u8, "error: compiler 'doesnotexist' does not exist, fetch it first with: zigup fetch doesnotexist\n", result.stderr);
  139. }
  140. try runNoCapture(zigup_args ++ &[_][]const u8 {"keep", "0.8.0"});
  141. // doesn't delete anything because we have keepfile and master doens't get deleted
  142. try runNoCapture(zigup_args ++ &[_][]const u8 {"clean"});
  143. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  144. // 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
  145. try std.fs.cwd().makeDir(install_dir ++ sep ++ "0.9.0");
  146. try testing.expectEqual(@as(u32, 4), try getCompilerCount(install_dir));
  147. try runNoCapture(zigup_args ++ &[_][]const u8 {"clean"});
  148. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  149. {
  150. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"clean", "0.8.0"});
  151. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  152. try passOrDumpAndThrow(result);
  153. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "deleting "));
  154. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "0.8.0"));
  155. }
  156. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  157. {
  158. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"clean"});
  159. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  160. try passOrDumpAndThrow(result);
  161. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "it is master"));
  162. }
  163. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  164. try runNoCapture(zigup_args ++ &[_][]const u8 {"master"});
  165. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  166. {
  167. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"DOESNOTEXST"});
  168. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  169. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "HTTP request failed"));
  170. }
  171. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  172. // verify that we get an error if there is another compiler in the path
  173. {
  174. const bin2_dir = "scratch" ++ sep ++ "bin2";
  175. try std.fs.cwd().makeDir(bin2_dir);
  176. const previous_path = path_env_ptr.*;
  177. const scratch_bin2_path = try std.fs.path.join(allocator, &.{ cwd, bin2_dir });
  178. defer allocator.free(scratch_bin2_path);
  179. {
  180. var file = try std.fs.cwd().createFile(comptime bin2_dir ++ sep ++ "zig" ++ builtin.target.exeFileExt(), .{});
  181. defer file.close();
  182. try file.writer().writeAll("a fake executable");
  183. }
  184. setPathEnv(try std.mem.concat(allocator, u8, &.{ scratch_bin2_path, path_env_sep, previous_path}));
  185. defer setPathEnv(previous_path);
  186. // verify zig isn't currently on 0.7.0 before we set it as the default
  187. try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .not_equal);
  188. {
  189. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8 {"default", "0.7.0"});
  190. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  191. std.log.info("output: {s}", .{result.stderr});
  192. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: zig compiler '"));
  193. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' is higher priority in PATH than the path-link '"));
  194. }
  195. // the path link should still be updated even though it's in a lower path priority.
  196. // Verify zig points to the new defult version we just set.
  197. try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .equal);
  198. }
  199. // verify a dev build
  200. // NOTE: this test will eventually break when these builds are cleaned up,
  201. // we should support downloading from bazel and use that instead since
  202. // it should be more permanent
  203. try runNoCapture(zigup_args ++ &[_][]const u8 { "0.11.0-dev.4263+f821543e4" });
  204. std.log.info("Success", .{});
  205. return 0;
  206. }
  207. fn checkZigVersion(allocator: std.mem.Allocator, zig: []const u8, compare: []const u8, want_equal: enum { not_equal, equal }) !void {
  208. const result = try runCaptureOuts(allocator, &[_][]const u8 {zig, "version" });
  209. defer { allocator.free(result.stdout); allocator.free(result.stderr); }
  210. try passOrDumpAndThrow(result);
  211. const actual_version = std.mem.trimRight(u8, result.stdout, "\r\n");
  212. const actual_equal = std.mem.eql(u8, compare, actual_version);
  213. const expected_equal = switch (want_equal) { .not_equal => false, .equal => true };
  214. if (expected_equal != actual_equal) {
  215. const prefix: []const u8 = if (expected_equal) "" else " NOT";
  216. std.log.info("expected zig version to{s} be '{s}', but is '{s}'", .{prefix, compare, actual_version});
  217. return error.TestUnexpectedResult;
  218. }
  219. }
  220. fn getCompilerCount(install_dir: []const u8) !u32 {
  221. var dir = try std.fs.cwd().openIterableDir(install_dir, .{});
  222. defer dir.close();
  223. var it = dir.iterate();
  224. var count: u32 = 0;
  225. while (try it.next()) |entry| {
  226. if (entry.kind == .directory) {
  227. count += 1;
  228. } else {
  229. if (builtin.os.tag == .windows) {
  230. try testing.expect(entry.kind == .file);
  231. } else {
  232. try testing.expect(entry.kind == .sym_link);
  233. }
  234. }
  235. }
  236. return count;
  237. }
  238. fn trailNl(s: []const u8) []const u8 {
  239. return if (s.len == 0 or s[s.len-1] != '\n') "\n" else "";
  240. }
  241. fn dumpExecResult(result: std.ChildProcess.ExecResult) void {
  242. if (result.stdout.len > 0) {
  243. std.debug.print("--- STDOUT ---\n{s}{s}--------------\n", .{result.stdout, trailNl(result.stdout)});
  244. }
  245. if (result.stderr.len > 0) {
  246. std.debug.print("--- STDERR ---\n{s}{s}--------------\n", .{result.stderr, trailNl(result.stderr)});
  247. }
  248. }
  249. fn runNoCapture(argv: []const []const u8) !void {
  250. var arena_store = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  251. defer arena_store.deinit();
  252. const result = try runCaptureOuts(arena_store.allocator(), argv);
  253. dumpExecResult(result);
  254. try passOrThrow(result.term);
  255. }
  256. fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.ChildProcess.ExecResult {
  257. {
  258. const cmd = try std.mem.join(allocator, " ", argv);
  259. defer allocator.free(cmd);
  260. std.log.info("RUN: {s}", .{cmd});
  261. }
  262. return try std.ChildProcess.exec(.{.allocator = allocator, .argv = argv, .env_map = &child_env_map});
  263. }
  264. fn passOrThrow(term: std.ChildProcess.Term) error{ChildProcessFailed}!void {
  265. if (!execResultPassed(term)) {
  266. std.log.err("child process failed with {}", .{term});
  267. return error.ChildProcessFailed;
  268. }
  269. }
  270. fn passOrDumpAndThrow(result: std.ChildProcess.ExecResult) error{ChildProcessFailed}!void {
  271. if (!execResultPassed(result.term)) {
  272. dumpExecResult(result);
  273. std.log.err("child process failed with {}", .{result.term});
  274. return error.ChildProcessFailed;
  275. }
  276. }
  277. fn execResultPassed(term: std.ChildProcess.Term) bool {
  278. switch (term) {
  279. .Exited => |code| return code == 0,
  280. else => return false,
  281. }
  282. }