test.zig 17 KB

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