test.zig 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  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. var allocator_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  17. //defer allocator_instance.deinit();
  18. const allocator = allocator_instance.allocator();
  19. const all_cmdline_args = try std.process.argsAlloc(allocator);
  20. if (all_cmdline_args.len <= 1) {
  21. try std.io.getStdErr().writer().print("Usage: test ZIGUP_EXE TEST_DIR\n", .{});
  22. return 0xff;
  23. }
  24. const cmdline_args = all_cmdline_args[1..];
  25. if (cmdline_args.len != 2) {
  26. std.log.err("expected 1 cmdline arg but got {}", .{cmdline_args.len});
  27. return 0xff;
  28. }
  29. const zigup_src_exe = cmdline_args[0];
  30. const test_dir = cmdline_args[1];
  31. std.log.info("run zigup tests", .{});
  32. std.log.info("zigup exe '{s}'", .{zigup_src_exe});
  33. std.log.info("test directory '{s}'", .{test_dir});
  34. if (!std.fs.path.isAbsolute(test_dir)) {
  35. std.log.err("currently the test requires an absolute test directory path", .{});
  36. return 0xff;
  37. }
  38. try fixdeletetree.deleteTree(std.fs.cwd(), test_dir);
  39. try std.fs.cwd().makePath(test_dir);
  40. const bin_dir = try std.fs.path.join(allocator, &.{ test_dir, "bin" });
  41. try std.fs.cwd().makeDir(bin_dir);
  42. const install_sub_path = if (builtin.os.tag == .windows) "bin\\zig" else "install";
  43. const install_dir = try std.fs.path.join(allocator, &.{ test_dir, install_sub_path });
  44. try std.fs.cwd().makeDir(install_dir);
  45. const zigup = try std.fs.path.join(allocator, &.{
  46. test_dir,
  47. "bin",
  48. "zigup" ++ comptime builtin.target.exeFileExt(),
  49. });
  50. try std.fs.cwd().copyFile(
  51. zigup_src_exe,
  52. std.fs.cwd(),
  53. zigup,
  54. .{},
  55. );
  56. if (builtin.os.tag == .windows) {
  57. const zigup_src_pdb = try std.mem.concat(allocator, u8, &.{
  58. zigup_src_exe[0 .. zigup_src_exe.len - 4],
  59. ".pdb",
  60. });
  61. defer allocator.free(zigup_src_pdb);
  62. const zigup_pdb = try std.fs.path.join(allocator, &.{ test_dir, "bin\\zigup.pdb" });
  63. defer allocator.free(zigup_pdb);
  64. try std.fs.cwd().copyFile(zigup_src_pdb, std.fs.cwd(), zigup_pdb, .{});
  65. }
  66. const install_args = if (builtin.os.tag == .windows) [_][]const u8{} else [_][]const u8{
  67. "--install-dir", install_dir,
  68. };
  69. const zigup_args = &[_][]const u8{zigup} ++ install_args;
  70. const path_link = try std.fs.path.join(allocator, &.{ bin_dir, comptime "zig" ++ builtin.target.exeFileExt() });
  71. defer allocator.free(path_link);
  72. // add our scratch/bin directory to PATH
  73. child_env_map = try std.process.getEnvMap(allocator);
  74. path_env_ptr = child_env_map.getPtr("PATH") orelse {
  75. std.log.err("the PATH environment variable does not exist?", .{});
  76. return 1;
  77. };
  78. const original_path_env = path_env_ptr.*;
  79. setPathEnv(try std.mem.concat(allocator, u8, &.{ bin_dir, path_env_sep, original_path_env }));
  80. {
  81. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "master" });
  82. defer {
  83. allocator.free(result.stdout);
  84. allocator.free(result.stderr);
  85. }
  86. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "master has not been fetched"));
  87. }
  88. {
  89. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"-h"});
  90. defer {
  91. allocator.free(result.stdout);
  92. allocator.free(result.stderr);
  93. }
  94. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  95. }
  96. {
  97. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"--help"});
  98. defer {
  99. allocator.free(result.stdout);
  100. allocator.free(result.stderr);
  101. }
  102. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "Usage"));
  103. }
  104. {
  105. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"});
  106. defer {
  107. allocator.free(result.stdout);
  108. allocator.free(result.stderr);
  109. }
  110. try passOrDumpAndThrow(result);
  111. try testing.expect(std.mem.eql(u8, result.stdout, "<no-default>\n"));
  112. }
  113. {
  114. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"fetch-index"});
  115. defer {
  116. allocator.free(result.stdout);
  117. allocator.free(result.stderr);
  118. }
  119. try passOrDumpAndThrow(result);
  120. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "master"));
  121. }
  122. {
  123. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "0.7.0" });
  124. defer {
  125. allocator.free(result.stdout);
  126. allocator.free(result.stderr);
  127. }
  128. dumpExecResult(result);
  129. switch (result.term) {
  130. .Exited => |code| try testing.expectEqual(@as(u8, 1), code),
  131. else => |term| std.debug.panic("unexpected exit {}", .{term}),
  132. }
  133. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: compiler '0.7.0' is not installed\n"));
  134. }
  135. try runNoCapture(zigup_args ++ &[_][]const u8{"0.7.0"});
  136. {
  137. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"});
  138. defer {
  139. allocator.free(result.stdout);
  140. allocator.free(result.stderr);
  141. }
  142. try passOrDumpAndThrow(result);
  143. dumpExecResult(result);
  144. try testing.expect(std.mem.eql(u8, result.stdout, "0.7.0\n"));
  145. }
  146. // verify we print a nice error message if we can't update the symlink
  147. // because it's a directory
  148. {
  149. const zig_exe_link = try std.fs.path.join(allocator, &.{ bin_dir, "zig" ++ comptime builtin.target.exeFileExt() });
  150. defer allocator.free(zig_exe_link);
  151. if (std.fs.cwd().access(zig_exe_link, .{})) {
  152. try std.fs.cwd().deleteFile(zig_exe_link);
  153. } else |err| switch (err) {
  154. error.FileNotFound => {},
  155. else => |e| return e,
  156. }
  157. try std.fs.cwd().makeDir(zig_exe_link);
  158. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "0.7.0" });
  159. defer {
  160. allocator.free(result.stdout);
  161. allocator.free(result.stderr);
  162. }
  163. dumpExecResult(result);
  164. switch (result.term) {
  165. .Exited => |code| try testing.expectEqual(@as(u8, 1), code),
  166. else => |term| std.debug.panic("unexpected exit {}", .{term}),
  167. }
  168. if (builtin.os.tag == .windows) {
  169. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "unable to create the exe link, the path '"));
  170. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' is a directory"));
  171. } else {
  172. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "unable to update/overwrite the 'zig' PATH symlink, the file '"));
  173. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' already exists and is not a symlink"));
  174. }
  175. try std.fs.cwd().deleteDir(zig_exe_link);
  176. }
  177. {
  178. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "fetch", "0.7.0" });
  179. defer {
  180. allocator.free(result.stdout);
  181. allocator.free(result.stderr);
  182. }
  183. try passOrDumpAndThrow(result);
  184. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "already installed"));
  185. }
  186. try runNoCapture(zigup_args ++ &[_][]const u8{"master"});
  187. try runNoCapture(zigup_args ++ &[_][]const u8{"0.8.0"});
  188. {
  189. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"default"});
  190. defer {
  191. allocator.free(result.stdout);
  192. allocator.free(result.stderr);
  193. }
  194. try passOrDumpAndThrow(result);
  195. dumpExecResult(result);
  196. try testing.expect(std.mem.eql(u8, result.stdout, "0.8.0\n"));
  197. }
  198. {
  199. const save_path_env = path_env_ptr.*;
  200. defer setPathEnv(save_path_env);
  201. setPathEnv("");
  202. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "master" });
  203. defer {
  204. allocator.free(result.stdout);
  205. allocator.free(result.stderr);
  206. }
  207. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, " is not in PATH"));
  208. }
  209. try runNoCapture(zigup_args ++ &[_][]const u8{ "default", "master" });
  210. {
  211. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"list"});
  212. defer {
  213. allocator.free(result.stdout);
  214. allocator.free(result.stderr);
  215. }
  216. try passOrDumpAndThrow(result);
  217. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.7.0"));
  218. try testing.expect(std.mem.containsAtLeast(u8, result.stdout, 1, "0.8.0"));
  219. }
  220. try runNoCapture(zigup_args ++ &[_][]const u8{ "default", "0.7.0" });
  221. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  222. {
  223. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "run", "0.8.0", "version" });
  224. defer {
  225. allocator.free(result.stdout);
  226. allocator.free(result.stderr);
  227. }
  228. try testing.expectEqualSlices(u8, "0.8.0\n", result.stdout);
  229. }
  230. {
  231. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "run", "doesnotexist", "version" });
  232. defer {
  233. allocator.free(result.stdout);
  234. allocator.free(result.stderr);
  235. }
  236. try testing.expectEqualSlices(u8, "error: compiler 'doesnotexist' does not exist, fetch it first with: zigup fetch doesnotexist\n", result.stderr);
  237. }
  238. try runNoCapture(zigup_args ++ &[_][]const u8{ "keep", "0.8.0" });
  239. // doesn't delete anything because we have keepfile and master doens't get deleted
  240. try runNoCapture(zigup_args ++ &[_][]const u8{"clean"});
  241. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  242. // 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
  243. try makeDir(test_dir, install_sub_path ++ sep ++ "0.9.0");
  244. try testing.expectEqual(@as(u32, 4), try getCompilerCount(install_dir));
  245. try runNoCapture(zigup_args ++ &[_][]const u8{"clean"});
  246. try testing.expectEqual(@as(u32, 3), try getCompilerCount(install_dir));
  247. {
  248. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "clean", "0.8.0" });
  249. defer {
  250. allocator.free(result.stdout);
  251. allocator.free(result.stderr);
  252. }
  253. try passOrDumpAndThrow(result);
  254. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "deleting "));
  255. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "0.8.0"));
  256. }
  257. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  258. {
  259. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"clean"});
  260. defer {
  261. allocator.free(result.stdout);
  262. allocator.free(result.stderr);
  263. }
  264. try passOrDumpAndThrow(result);
  265. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "it is master"));
  266. }
  267. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  268. try runNoCapture(zigup_args ++ &[_][]const u8{"master"});
  269. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  270. {
  271. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{"DOESNOTEXST"});
  272. defer {
  273. allocator.free(result.stdout);
  274. allocator.free(result.stderr);
  275. }
  276. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "download"));
  277. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "failed"));
  278. }
  279. try testing.expectEqual(@as(u32, 2), try getCompilerCount(install_dir));
  280. // verify that we get an error if there is another compiler in the path
  281. {
  282. const bin2_dir = try std.fs.path.join(allocator, &.{ test_dir, "bin2" });
  283. defer allocator.free(bin2_dir);
  284. try std.fs.cwd().makeDir(bin2_dir);
  285. const previous_path = path_env_ptr.*;
  286. {
  287. const fake_zig = try std.fs.path.join(allocator, &.{
  288. bin2_dir,
  289. "zig" ++ comptime builtin.target.exeFileExt(),
  290. });
  291. defer allocator.free(fake_zig);
  292. var file = try std.fs.cwd().createFile(fake_zig, .{});
  293. defer file.close();
  294. try file.writer().writeAll("a fake executable");
  295. }
  296. setPathEnv(try std.mem.concat(allocator, u8, &.{ bin2_dir, path_env_sep, previous_path }));
  297. defer setPathEnv(previous_path);
  298. // verify zig isn't currently on 0.7.0 before we set it as the default
  299. try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .not_equal);
  300. {
  301. const result = try runCaptureOuts(allocator, zigup_args ++ &[_][]const u8{ "default", "0.7.0" });
  302. defer {
  303. allocator.free(result.stdout);
  304. allocator.free(result.stderr);
  305. }
  306. std.log.info("output: {s}", .{result.stderr});
  307. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "error: zig compiler '"));
  308. try testing.expect(std.mem.containsAtLeast(u8, result.stderr, 1, "' is higher priority in PATH than the path-link '"));
  309. }
  310. // the path link should still be updated even though it's in a lower path priority.
  311. // Verify zig points to the new defult version we just set.
  312. try checkZigVersion(allocator, path_link, expected_zig_version_0_7_0, .equal);
  313. }
  314. // verify a dev build
  315. // NOTE: this test will eventually break when these builds are cleaned up,
  316. // we should support downloading from bazel and use that instead since
  317. // it should be more permanent
  318. try runNoCapture(zigup_args ++ &[_][]const u8{"0.14.0-dev.2465+70de2f3a7"});
  319. std.log.info("Success", .{});
  320. return 0;
  321. }
  322. fn makeDir(dir_path: []const u8, sub_path: []const u8) !void {
  323. var dir = try std.fs.cwd().openDir(dir_path, .{});
  324. defer dir.close();
  325. try dir.makeDir(sub_path);
  326. }
  327. fn checkZigVersion(allocator: std.mem.Allocator, zig: []const u8, compare: []const u8, want_equal: enum { not_equal, equal }) !void {
  328. const result = try runCaptureOuts(allocator, &[_][]const u8{ zig, "version" });
  329. defer {
  330. allocator.free(result.stdout);
  331. allocator.free(result.stderr);
  332. }
  333. try passOrDumpAndThrow(result);
  334. const actual_version = std.mem.trimRight(u8, result.stdout, "\r\n");
  335. const actual_equal = std.mem.eql(u8, compare, actual_version);
  336. const expected_equal = switch (want_equal) {
  337. .not_equal => false,
  338. .equal => true,
  339. };
  340. if (expected_equal != actual_equal) {
  341. const prefix: []const u8 = if (expected_equal) "" else " NOT";
  342. std.log.info("expected zig version to{s} be '{s}', but is '{s}'", .{ prefix, compare, actual_version });
  343. return error.TestUnexpectedResult;
  344. }
  345. }
  346. fn getCompilerCount(install_dir: []const u8) !u32 {
  347. var dir = try std.fs.cwd().openDir(install_dir, .{ .iterate = true });
  348. defer dir.close();
  349. var it = dir.iterate();
  350. var count: u32 = 0;
  351. while (try it.next()) |entry| {
  352. if (entry.kind == .directory) {
  353. count += 1;
  354. } else {
  355. if (builtin.os.tag == .windows) {
  356. try testing.expect(entry.kind == .file);
  357. } else {
  358. try testing.expect(entry.kind == .sym_link);
  359. }
  360. }
  361. }
  362. return count;
  363. }
  364. fn trailNl(s: []const u8) []const u8 {
  365. return if (s.len == 0 or s[s.len - 1] != '\n') "\n" else "";
  366. }
  367. fn dumpExecResult(result: std.process.Child.RunResult) void {
  368. if (result.stdout.len > 0) {
  369. std.debug.print("--- STDOUT ---\n{s}{s}--------------\n", .{ result.stdout, trailNl(result.stdout) });
  370. }
  371. if (result.stderr.len > 0) {
  372. std.debug.print("--- STDERR ---\n{s}{s}--------------\n", .{ result.stderr, trailNl(result.stderr) });
  373. }
  374. }
  375. fn runNoCapture(argv: []const []const u8) !void {
  376. var arena_store = std.heap.ArenaAllocator.init(std.heap.page_allocator);
  377. defer arena_store.deinit();
  378. const result = try runCaptureOuts(arena_store.allocator(), argv);
  379. dumpExecResult(result);
  380. try passOrThrow(result.term);
  381. }
  382. fn runCaptureOuts(allocator: std.mem.Allocator, argv: []const []const u8) !std.process.Child.RunResult {
  383. {
  384. const cmd = try std.mem.join(allocator, " ", argv);
  385. defer allocator.free(cmd);
  386. std.log.info("RUN: {s}", .{cmd});
  387. }
  388. return try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .env_map = &child_env_map });
  389. }
  390. fn passOrThrow(term: std.process.Child.Term) error{ChildProcessFailed}!void {
  391. if (!execResultPassed(term)) {
  392. std.log.err("child process failed with {}", .{term});
  393. return error.ChildProcessFailed;
  394. }
  395. }
  396. fn passOrDumpAndThrow(result: std.process.Child.RunResult) error{ChildProcessFailed}!void {
  397. if (!execResultPassed(result.term)) {
  398. dumpExecResult(result);
  399. std.log.err("child process failed with {}", .{result.term});
  400. return error.ChildProcessFailed;
  401. }
  402. }
  403. fn execResultPassed(term: std.process.Child.Term) bool {
  404. switch (term) {
  405. .Exited => |code| return code == 0,
  406. else => return false,
  407. }
  408. }