| author | |
| committer | |
| log | 2d5b2bf1c986d037ef965bf8c9b4d8dfd5967478 |
| tree | e7b0f9f6f509e34edeb2226569b2ae78d34cfb5a |
| parent | 299991019dddb2acd076d4b2698a4fd6a7a6ae94 |
| signature |
* use erase rest of line escape code.
* use `stderr.supportsAnsiEscapeCodes` rather than `isTty`.
* respect `--color off`
* avoid unnecessary recursion
* add `Progress.log`
* disable the progress std lib test since it's noisy and uses
`time.sleep()`.
* enable/integrate progress printing with the default test runner8 files changed, 92 insertions(+), 55 deletions(-)
lib/std/progress.zig+52-36| ... | ... | @@ -26,10 +26,6 @@ pub const Progress = struct { |
| 26 | 26 | /// with each refresh. |
| 27 | 27 | output_buffer: [100]u8 = undefined, |
| 28 | 28 | |
| 29 | /// Keeps track of how many columns in the terminal have been output, so that | |
| 30 | /// we can move the cursor back later. | |
| 31 | columns_written: usize = undefined, | |
| 32 | ||
| 33 | 29 | /// How many nanoseconds between writing updates to the terminal. |
| 34 | 30 | refresh_rate_ns: u64 = 50 * std.time.millisecond, |
| 35 | 31 | |
| ... | ... | @@ -38,6 +34,10 @@ pub const Progress = struct { |
| 38 | 34 | |
| 39 | 35 | done: bool = true, |
| 40 | 36 | |
| 37 | /// Keeps track of how many columns in the terminal have been output, so that | |
| 38 | /// we can move the cursor back later. | |
| 39 | columns_written: usize = undefined, | |
| 40 | ||
| 41 | 41 | /// Represents one unit of progress. Each node can have children nodes, or |
| 42 | 42 | /// one can use integers with `update`. |
| 43 | 43 | pub const Node = struct { |
| ... | ... | @@ -99,8 +99,7 @@ pub const Progress = struct { |
| 99 | 99 | /// API to return Progress rather than accept it as a parameter. |
| 100 | 100 | pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node { |
| 101 | 101 | if (std.io.getStdErr()) |stderr| { |
| 102 | const is_term = stderr.isTty(); | |
| 103 | self.terminal = if (is_term) stderr else null; | |
| 102 | self.terminal = if (stderr.supportsAnsiEscapeCodes()) stderr else null; | |
| 104 | 103 | } else |_| { |
| 105 | 104 | self.terminal = null; |
| 106 | 105 | } |
| ... | ... | @@ -111,8 +110,8 @@ pub const Progress = struct { |
| 111 | 110 | .name = name, |
| 112 | 111 | .estimated_total_items = estimated_total_items, |
| 113 | 112 | }; |
| 114 | self.prev_refresh_timestamp = 0; | |
| 115 | 113 | self.columns_written = 0; |
| 114 | self.prev_refresh_timestamp = 0; | |
| 116 | 115 | self.timer = try std.time.Timer.start(); |
| 117 | 116 | self.done = false; |
| 118 | 117 | return &self.root; |
| ... | ... | @@ -133,20 +132,42 @@ pub const Progress = struct { |
| 133 | 132 | const prev_columns_written = self.columns_written; |
| 134 | 133 | var end: usize = 0; |
| 135 | 134 | if (self.columns_written > 0) { |
| 135 | // restore cursor position | |
| 136 | 136 | end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", self.columns_written) catch unreachable).len; |
| 137 | 137 | self.columns_written = 0; |
| 138 | } | |
| 139 | 138 | |
| 140 | if (!self.done) { | |
| 141 | self.bufWriteNode(self.root, &end); | |
| 142 | self.bufWrite(&end, "..."); | |
| 139 | // clear rest of line | |
| 140 | end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K") catch unreachable).len; | |
| 143 | 141 | } |
| 144 | 142 | |
| 145 | if (prev_columns_written > self.columns_written) { | |
| 146 | const amt = prev_columns_written - self.columns_written; | |
| 147 | std.mem.set(u8, self.output_buffer[end .. end + amt], ' '); | |
| 148 | end += amt; | |
| 149 | end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", amt) catch unreachable).len; | |
| 143 | if (!self.done) { | |
| 144 | var need_ellipse = false; | |
| 145 | var maybe_node: ?*Node = &self.root; | |
| 146 | while (maybe_node) |node| { | |
| 147 | if (need_ellipse) { | |
| 148 | self.bufWrite(&end, "..."); | |
| 149 | } | |
| 150 | need_ellipse = false; | |
| 151 | if (node.name.len != 0 or node.estimated_total_items != null) { | |
| 152 | if (node.name.len != 0) { | |
| 153 | self.bufWrite(&end, "{}", node.name); | |
| 154 | need_ellipse = true; | |
| 155 | } | |
| 156 | if (node.estimated_total_items) |total| { | |
| 157 | if (need_ellipse) self.bufWrite(&end, " "); | |
| 158 | self.bufWrite(&end, "[{}/{}] ", node.completed_items, total); | |
| 159 | need_ellipse = false; | |
| 160 | } else if (node.completed_items != 0) { | |
| 161 | if (need_ellipse) self.bufWrite(&end, " "); | |
| 162 | self.bufWrite(&end, "[{}] ", node.completed_items); | |
| 163 | need_ellipse = false; | |
| 164 | } | |
| 165 | } | |
| 166 | maybe_node = node.recently_updated_child; | |
| 167 | } | |
| 168 | if (need_ellipse) { | |
| 169 | self.bufWrite(&end, "..."); | |
| 170 | } | |
| 150 | 171 | } |
| 151 | 172 | |
| 152 | 173 | _ = file.write(self.output_buffer[0..end]) catch |e| { |
| ... | ... | @@ -156,25 +177,14 @@ pub const Progress = struct { |
| 156 | 177 | self.prev_refresh_timestamp = self.timer.read(); |
| 157 | 178 | } |
| 158 | 179 | |
| 159 | fn bufWriteNode(self: *Progress, node: Node, end: *usize) void { | |
| 160 | if (node.name.len != 0 or node.estimated_total_items != null) { | |
| 161 | if (node.name.len != 0) { | |
| 162 | self.bufWrite(end, "{}", node.name); | |
| 163 | if (node.recently_updated_child != null or node.estimated_total_items != null or | |
| 164 | node.completed_items != 0) | |
| 165 | { | |
| 166 | self.bufWrite(end, "..."); | |
| 167 | } | |
| 168 | } | |
| 169 | if (node.estimated_total_items) |total| { | |
| 170 | self.bufWrite(end, "[{}/{}] ", node.completed_items, total); | |
| 171 | } else if (node.completed_items != 0) { | |
| 172 | self.bufWrite(end, "[{}] ", node.completed_items); | |
| 173 | } | |
| 174 | } | |
| 175 | if (node.recently_updated_child) |child| { | |
| 176 | self.bufWriteNode(child.*, end); | |
| 177 | } | |
| 180 | pub fn log(self: *Progress, comptime format: []const u8, args: ...) void { | |
| 181 | const file = self.terminal orelse return; | |
| 182 | self.refresh(); | |
| 183 | file.outStream().stream.print(format, args) catch { | |
| 184 | self.terminal = null; | |
| 185 | return; | |
| 186 | }; | |
| 187 | self.columns_written = 0; | |
| 178 | 188 | } |
| 179 | 189 | |
| 180 | 190 | fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: ...) void { |
| ... | ... | @@ -200,6 +210,12 @@ pub const Progress = struct { |
| 200 | 210 | }; |
| 201 | 211 | |
| 202 | 212 | test "basic functionality" { |
| 213 | var disable = true; | |
| 214 | if (disable) { | |
| 215 | // This test is disabled because it uses time.sleep() and is therefore slow. It also | |
| 216 | // prints bogus progress data to stderr. | |
| 217 | return error.SkipZigTest; | |
| 218 | } | |
| 203 | 219 | var progress = Progress{}; |
| 204 | 220 | const root_node = try progress.start("", 100); |
| 205 | 221 | defer root_node.end(); |
| ... | ... | @@ -235,7 +251,7 @@ test "basic functionality" { |
| 235 | 251 | var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", null); |
| 236 | 252 | node.activate(); |
| 237 | 253 | std.time.sleep(10 * std.time.millisecond); |
| 238 | progress.maybeRefresh(); | |
| 254 | progress.refresh(); | |
| 239 | 255 | std.time.sleep(10 * std.time.millisecond); |
| 240 | 256 | node.end(); |
| 241 | 257 | } |
lib/std/special/test_runner.zig+15-10| ... | ... | @@ -2,28 +2,33 @@ const std = @import("std"); |
| 2 | 2 | const io = std.io; |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | const test_fn_list = builtin.test_functions; |
| 5 | const warn = std.debug.warn; | |
| 6 | 5 | |
| 7 | pub fn main() !void { | |
| 6 | pub fn main() anyerror!void { | |
| 8 | 7 | var ok_count: usize = 0; |
| 9 | 8 | var skip_count: usize = 0; |
| 10 | for (test_fn_list) |test_fn, i| { | |
| 11 | warn("{}/{} {}...", i + 1, test_fn_list.len, test_fn.name); | |
| 9 | var progress = std.Progress{}; | |
| 10 | const root_node = progress.start("Test", test_fn_list.len) catch |err| switch (err) { | |
| 11 | // TODO still run tests in this case | |
| 12 | error.TimerUnsupported => @panic("timer unsupported"), | |
| 13 | }; | |
| 12 | 14 | |
| 15 | for (test_fn_list) |test_fn, i| { | |
| 16 | var test_node = root_node.start(test_fn.name, null); | |
| 17 | test_node.activate(); | |
| 13 | 18 | if (test_fn.func()) |_| { |
| 14 | 19 | ok_count += 1; |
| 15 | warn("OK\n"); | |
| 20 | test_node.end(); | |
| 16 | 21 | } else |err| switch (err) { |
| 17 | 22 | error.SkipZigTest => { |
| 18 | 23 | skip_count += 1; |
| 19 | warn("SKIP\n"); | |
| 24 | test_node.end(); | |
| 25 | progress.log("{}...SKIP\n", test_fn.name); | |
| 20 | 26 | }, |
| 21 | 27 | else => return err, |
| 22 | 28 | } |
| 23 | 29 | } |
| 24 | if (ok_count == test_fn_list.len) { | |
| 25 | warn("All tests passed.\n"); | |
| 26 | } else { | |
| 27 | warn("{} passed; {} skipped.\n", ok_count, skip_count); | |
| 30 | root_node.end(); | |
| 31 | if (ok_count != test_fn_list.len) { | |
| 32 | progress.log("{} passed; {} skipped.\n", ok_count, skip_count); | |
| 28 | 33 | } |
| 29 | 34 | } |
src-self-hosted/stage1.zig+11-1| ... | ... | @@ -470,13 +470,23 @@ export fn stage2_progress_destroy(progress: *std.Progress) void { |
| 470 | 470 | } |
| 471 | 471 | |
| 472 | 472 | // ABI warning |
| 473 | export fn stage2_progress_start_root(progress: *std.Progress, name_ptr: [*]const u8, name_len: usize, estimated_total_items: usize) *std.Progress.Node { | |
| 473 | export fn stage2_progress_start_root( | |
| 474 | progress: *std.Progress, | |
| 475 | name_ptr: [*]const u8, | |
| 476 | name_len: usize, | |
| 477 | estimated_total_items: usize, | |
| 478 | ) *std.Progress.Node { | |
| 474 | 479 | return progress.start( |
| 475 | 480 | name_ptr[0..name_len], |
| 476 | 481 | if (estimated_total_items == 0) null else estimated_total_items, |
| 477 | 482 | ) catch @panic("timer unsupported"); |
| 478 | 483 | } |
| 479 | 484 | |
| 485 | // ABI warning | |
| 486 | export fn stage2_progress_disable_tty(progress: *std.Progress) void { | |
| 487 | progress.terminal = null; | |
| 488 | } | |
| 489 | ||
| 480 | 490 | // ABI warning |
| 481 | 491 | export fn stage2_progress_start( |
| 482 | 492 | node: *std.Progress.Node, |
src/codegen.cpp+4-4| ... | ... | @@ -10479,11 +10479,11 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c |
| 10479 | 10479 | } |
| 10480 | 10480 | |
| 10481 | 10481 | CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type, |
| 10482 | ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *child_progress_node) | |
| 10482 | ZigLibCInstallation *libc, const char *name, Stage2ProgressNode *parent_progress_node) | |
| 10483 | 10483 | { |
| 10484 | if (!child_progress_node) { | |
| 10485 | child_progress_node = stage2_progress_start(parent_gen->progress_node, name, strlen(name), 0); | |
| 10486 | } | |
| 10484 | Stage2ProgressNode *child_progress_node = stage2_progress_start( | |
| 10485 | parent_progress_node ? parent_progress_node : parent_gen->progress_node, | |
| 10486 | name, strlen(name), 0); | |
| 10487 | 10487 | |
| 10488 | 10488 | CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type, |
| 10489 | 10489 | parent_gen->build_mode, parent_gen->zig_lib_dir, libc, get_stage1_cache_path(), false, child_progress_node); |
src/main.cpp+5-2| ... | ... | @@ -506,8 +506,6 @@ int main(int argc, char **argv) { |
| 506 | 506 | ZigList<const char *> llvm_argv = {0}; |
| 507 | 507 | llvm_argv.append("zig (LLVM option parsing)"); |
| 508 | 508 | |
| 509 | Stage2ProgressNode *root_progress_node = stage2_progress_start_root(stage2_progress_create(), "", 0, 0); | |
| 510 | ||
| 511 | 509 | if (argc >= 2 && strcmp(argv[1], "build") == 0) { |
| 512 | 510 | Buf zig_exe_path_buf = BUF_INIT; |
| 513 | 511 | if ((err = os_self_exe_path(&zig_exe_path_buf))) { |
| ... | ... | @@ -589,6 +587,7 @@ int main(int argc, char **argv) { |
| 589 | 587 | Buf *cache_dir_buf = buf_create_from_str(cache_dir); |
| 590 | 588 | full_cache_dir = os_path_resolve(&cache_dir_buf, 1); |
| 591 | 589 | } |
| 590 | Stage2ProgressNode *root_progress_node = stage2_progress_start_root(stage2_progress_create(), "", 0, 0); | |
| 592 | 591 | |
| 593 | 592 | CodeGen *g = codegen_create(main_pkg_path, build_runner_path, &target, OutTypeExe, |
| 594 | 593 | BuildModeDebug, override_lib_dir, nullptr, &full_cache_dir, false, root_progress_node); |
| ... | ... | @@ -965,6 +964,10 @@ int main(int argc, char **argv) { |
| 965 | 964 | return EXIT_FAILURE; |
| 966 | 965 | } |
| 967 | 966 | |
| 967 | Stage2Progress *progress = stage2_progress_create(); | |
| 968 | Stage2ProgressNode *root_progress_node = stage2_progress_start_root(progress, "", 0, 0); | |
| 969 | if (color == ErrColorOff) stage2_progress_disable_tty(progress); | |
| 970 | ||
| 968 | 971 | init_all_targets(); |
| 969 | 972 | |
| 970 | 973 | ZigTarget target; |
src/userland.cpp+1| ... | ... | @@ -87,3 +87,4 @@ Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node, |
| 87 | 87 | } |
| 88 | 88 | void stage2_progress_end(Stage2ProgressNode *node) {} |
| 89 | 89 | void stage2_progress_complete_one(Stage2ProgressNode *node) {} |
| 90 | void stage2_progress_disable_tty(Stage2Progress *progress) {} |
src/userland.h+2| ... | ... | @@ -163,6 +163,8 @@ struct Stage2ProgressNode; |
| 163 | 163 | // ABI warning |
| 164 | 164 | ZIG_EXTERN_C Stage2Progress *stage2_progress_create(void); |
| 165 | 165 | // ABI warning |
| 166 | ZIG_EXTERN_C void stage2_progress_disable_tty(Stage2Progress *progress); | |
| 167 | // ABI warning | |
| 166 | 168 | ZIG_EXTERN_C void stage2_progress_destroy(Stage2Progress *progress); |
| 167 | 169 | // ABI warning |
| 168 | 170 | ZIG_EXTERN_C Stage2ProgressNode *stage2_progress_start_root(Stage2Progress *progress, |
test/cli.zig+2-2| ... | ... | @@ -87,7 +87,7 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult { |
| 87 | 87 | fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void { |
| 88 | 88 | _ = try exec(dir_path, [_][]const u8{ zig_exe, "init-lib" }); |
| 89 | 89 | const test_result = try exec(dir_path, [_][]const u8{ zig_exe, "build", "test" }); |
| 90 | testing.expect(std.mem.endsWith(u8, test_result.stderr, "All tests passed.\n")); | |
| 90 | testing.expect(std.mem.eql(u8, test_result.stderr, "")); | |
| 91 | 91 | } |
| 92 | 92 | |
| 93 | 93 | fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void { |
| ... | ... | @@ -136,6 +136,6 @@ fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void { |
| 136 | 136 | const output_path = try fs.path.join(a, [_][]const u8{ "does", "not", "exist" }); |
| 137 | 137 | const source_path = try fs.path.join(a, [_][]const u8{ "src", "main.zig" }); |
| 138 | 138 | _ = try exec(dir_path, [_][]const u8{ |
| 139 | zig_exe, "build-exe", source_path, "--output-dir", output_path | |
| 139 | zig_exe, "build-exe", source_path, "--output-dir", output_path, | |
| 140 | 140 | }); |
| 141 | 141 | } |