authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-17 21:46:41-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-10-17 21:55:49-04:00
log2d5b2bf1c986d037ef965bf8c9b4d8dfd5967478
treee7b0f9f6f509e34edeb2226569b2ae78d34cfb5a
parent299991019dddb2acd076d4b2698a4fd6a7a6ae94
signaturelock-open Commit is signed but in an unrecognized format.

improve progress reporting

* 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 runner

8 files changed, 92 insertions(+), 55 deletions(-)

lib/std/progress.zig+52-36
...@@ -26,10 +26,6 @@ pub const Progress = struct {...@@ -26,10 +26,6 @@ pub const Progress = struct {
26 /// with each refresh.26 /// with each refresh.
27 output_buffer: [100]u8 = undefined,27 output_buffer: [100]u8 = undefined,
2828
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 /// How many nanoseconds between writing updates to the terminal.29 /// How many nanoseconds between writing updates to the terminal.
34 refresh_rate_ns: u64 = 50 * std.time.millisecond,30 refresh_rate_ns: u64 = 50 * std.time.millisecond,
3531
...@@ -38,6 +34,10 @@ pub const Progress = struct {...@@ -38,6 +34,10 @@ pub const Progress = struct {
3834
39 done: bool = true,35 done: bool = true,
4036
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 /// Represents one unit of progress. Each node can have children nodes, or41 /// Represents one unit of progress. Each node can have children nodes, or
42 /// one can use integers with `update`.42 /// one can use integers with `update`.
43 pub const Node = struct {43 pub const Node = struct {
...@@ -99,8 +99,7 @@ pub const Progress = struct {...@@ -99,8 +99,7 @@ pub const Progress = struct {
99 /// API to return Progress rather than accept it as a parameter.99 /// API to return Progress rather than accept it as a parameter.
100 pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node {100 pub fn start(self: *Progress, name: []const u8, estimated_total_items: ?usize) !*Node {
101 if (std.io.getStdErr()) |stderr| {101 if (std.io.getStdErr()) |stderr| {
102 const is_term = stderr.isTty();102 self.terminal = if (stderr.supportsAnsiEscapeCodes()) stderr else null;
103 self.terminal = if (is_term) stderr else null;
104 } else |_| {103 } else |_| {
105 self.terminal = null;104 self.terminal = null;
106 }105 }
...@@ -111,8 +110,8 @@ pub const Progress = struct {...@@ -111,8 +110,8 @@ pub const Progress = struct {
111 .name = name,110 .name = name,
112 .estimated_total_items = estimated_total_items,111 .estimated_total_items = estimated_total_items,
113 };112 };
114 self.prev_refresh_timestamp = 0;
115 self.columns_written = 0;113 self.columns_written = 0;
114 self.prev_refresh_timestamp = 0;
116 self.timer = try std.time.Timer.start();115 self.timer = try std.time.Timer.start();
117 self.done = false;116 self.done = false;
118 return &self.root;117 return &self.root;
...@@ -133,20 +132,42 @@ pub const Progress = struct {...@@ -133,20 +132,42 @@ pub const Progress = struct {
133 const prev_columns_written = self.columns_written;132 const prev_columns_written = self.columns_written;
134 var end: usize = 0;133 var end: usize = 0;
135 if (self.columns_written > 0) {134 if (self.columns_written > 0) {
135 // restore cursor position
136 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", self.columns_written) catch unreachable).len;136 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", self.columns_written) catch unreachable).len;
137 self.columns_written = 0;137 self.columns_written = 0;
138 }
139138
140 if (!self.done) {139 // clear rest of line
141 self.bufWriteNode(self.root, &end);140 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K") catch unreachable).len;
142 self.bufWrite(&end, "...");
143 }141 }
144142
145 if (prev_columns_written > self.columns_written) {143 if (!self.done) {
146 const amt = prev_columns_written - self.columns_written;144 var need_ellipse = false;
147 std.mem.set(u8, self.output_buffer[end .. end + amt], ' ');145 var maybe_node: ?*Node = &self.root;
148 end += amt;146 while (maybe_node) |node| {
149 end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{}D", amt) catch unreachable).len;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 }
151172
152 _ = file.write(self.output_buffer[0..end]) catch |e| {173 _ = file.write(self.output_buffer[0..end]) catch |e| {
...@@ -156,25 +177,14 @@ pub const Progress = struct {...@@ -156,25 +177,14 @@ pub const Progress = struct {
156 self.prev_refresh_timestamp = self.timer.read();177 self.prev_refresh_timestamp = self.timer.read();
157 }178 }
158179
159 fn bufWriteNode(self: *Progress, node: Node, end: *usize) void {180 pub fn log(self: *Progress, comptime format: []const u8, args: ...) void {
160 if (node.name.len != 0 or node.estimated_total_items != null) {181 const file = self.terminal orelse return;
161 if (node.name.len != 0) {182 self.refresh();
162 self.bufWrite(end, "{}", node.name);183 file.outStream().stream.print(format, args) catch {
163 if (node.recently_updated_child != null or node.estimated_total_items != null or184 self.terminal = null;
164 node.completed_items != 0)185 return;
165 {186 };
166 self.bufWrite(end, "...");187 self.columns_written = 0;
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 }
178 }188 }
179189
180 fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: ...) void {190 fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: ...) void {
...@@ -200,6 +210,12 @@ pub const Progress = struct {...@@ -200,6 +210,12 @@ pub const Progress = struct {
200};210};
201211
202test "basic functionality" {212test "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 var progress = Progress{};219 var progress = Progress{};
204 const root_node = try progress.start("", 100);220 const root_node = try progress.start("", 100);
205 defer root_node.end();221 defer root_node.end();
...@@ -235,7 +251,7 @@ test "basic functionality" {...@@ -235,7 +251,7 @@ test "basic functionality" {
235 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);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 node.activate();252 node.activate();
237 std.time.sleep(10 * std.time.millisecond);253 std.time.sleep(10 * std.time.millisecond);
238 progress.maybeRefresh();254 progress.refresh();
239 std.time.sleep(10 * std.time.millisecond);255 std.time.sleep(10 * std.time.millisecond);
240 node.end();256 node.end();
241 }257 }
lib/std/special/test_runner.zig+15-10
...@@ -2,28 +2,33 @@ const std = @import("std");...@@ -2,28 +2,33 @@ const std = @import("std");
2const io = std.io;2const io = std.io;
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const test_fn_list = builtin.test_functions;4const test_fn_list = builtin.test_functions;
5const warn = std.debug.warn;
65
7pub fn main() !void {6pub fn main() anyerror!void {
8 var ok_count: usize = 0;7 var ok_count: usize = 0;
9 var skip_count: usize = 0;8 var skip_count: usize = 0;
10 for (test_fn_list) |test_fn, i| {9 var progress = std.Progress{};
11 warn("{}/{} {}...", i + 1, test_fn_list.len, test_fn.name);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 };
1214
15 for (test_fn_list) |test_fn, i| {
16 var test_node = root_node.start(test_fn.name, null);
17 test_node.activate();
13 if (test_fn.func()) |_| {18 if (test_fn.func()) |_| {
14 ok_count += 1;19 ok_count += 1;
15 warn("OK\n");20 test_node.end();
16 } else |err| switch (err) {21 } else |err| switch (err) {
17 error.SkipZigTest => {22 error.SkipZigTest => {
18 skip_count += 1;23 skip_count += 1;
19 warn("SKIP\n");24 test_node.end();
25 progress.log("{}...SKIP\n", test_fn.name);
20 },26 },
21 else => return err,27 else => return err,
22 }28 }
23 }29 }
24 if (ok_count == test_fn_list.len) {30 root_node.end();
25 warn("All tests passed.\n");31 if (ok_count != test_fn_list.len) {
26 } else {32 progress.log("{} passed; {} skipped.\n", ok_count, skip_count);
27 warn("{} 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,13 +470,23 @@ export fn stage2_progress_destroy(progress: *std.Progress) void {
470}470}
471471
472// ABI warning472// ABI warning
473export fn stage2_progress_start_root(progress: *std.Progress, name_ptr: [*]const u8, name_len: usize, estimated_total_items: usize) *std.Progress.Node {473export 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 return progress.start(479 return progress.start(
475 name_ptr[0..name_len],480 name_ptr[0..name_len],
476 if (estimated_total_items == 0) null else estimated_total_items,481 if (estimated_total_items == 0) null else estimated_total_items,
477 ) catch @panic("timer unsupported");482 ) catch @panic("timer unsupported");
478}483}
479484
485// ABI warning
486export fn stage2_progress_disable_tty(progress: *std.Progress) void {
487 progress.terminal = null;
488}
489
480// ABI warning490// ABI warning
481export fn stage2_progress_start(491export fn stage2_progress_start(
482 node: *std.Progress.Node,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,11 +10479,11 @@ ZigPackage *codegen_create_package(CodeGen *g, const char *root_src_dir, const c
10479}10479}
1048010480
10481CodeGen *create_child_codegen(CodeGen *parent_gen, Buf *root_src_path, OutType out_type,10481CodeGen *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) {10484 Stage2ProgressNode *child_progress_node = stage2_progress_start(
10485 child_progress_node = stage2_progress_start(parent_gen->progress_node, name, strlen(name), 0);10485 parent_progress_node ? parent_progress_node : parent_gen->progress_node,
10486 }10486 name, strlen(name), 0);
1048710487
10488 CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,10488 CodeGen *child_gen = codegen_create(nullptr, root_src_path, parent_gen->zig_target, out_type,
10489 parent_gen->build_mode, parent_gen->zig_lib_dir, libc, get_stage1_cache_path(), false, child_progress_node);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,8 +506,6 @@ int main(int argc, char **argv) {
506 ZigList<const char *> llvm_argv = {0};506 ZigList<const char *> llvm_argv = {0};
507 llvm_argv.append("zig (LLVM option parsing)");507 llvm_argv.append("zig (LLVM option parsing)");
508508
509 Stage2ProgressNode *root_progress_node = stage2_progress_start_root(stage2_progress_create(), "", 0, 0);
510
511 if (argc >= 2 && strcmp(argv[1], "build") == 0) {509 if (argc >= 2 && strcmp(argv[1], "build") == 0) {
512 Buf zig_exe_path_buf = BUF_INIT;510 Buf zig_exe_path_buf = BUF_INIT;
513 if ((err = os_self_exe_path(&zig_exe_path_buf))) {511 if ((err = os_self_exe_path(&zig_exe_path_buf))) {
...@@ -589,6 +587,7 @@ int main(int argc, char **argv) {...@@ -589,6 +587,7 @@ int main(int argc, char **argv) {
589 Buf *cache_dir_buf = buf_create_from_str(cache_dir);587 Buf *cache_dir_buf = buf_create_from_str(cache_dir);
590 full_cache_dir = os_path_resolve(&cache_dir_buf, 1);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);
592591
593 CodeGen *g = codegen_create(main_pkg_path, build_runner_path, &target, OutTypeExe,592 CodeGen *g = codegen_create(main_pkg_path, build_runner_path, &target, OutTypeExe,
594 BuildModeDebug, override_lib_dir, nullptr, &full_cache_dir, false, root_progress_node);593 BuildModeDebug, override_lib_dir, nullptr, &full_cache_dir, false, root_progress_node);
...@@ -965,6 +964,10 @@ int main(int argc, char **argv) {...@@ -965,6 +964,10 @@ int main(int argc, char **argv) {
965 return EXIT_FAILURE;964 return EXIT_FAILURE;
966 }965 }
967966
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 init_all_targets();971 init_all_targets();
969972
970 ZigTarget target;973 ZigTarget target;
src/userland.cpp+1
...@@ -87,3 +87,4 @@ Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node,...@@ -87,3 +87,4 @@ Stage2ProgressNode *stage2_progress_start(Stage2ProgressNode *node,
87}87}
88void stage2_progress_end(Stage2ProgressNode *node) {}88void stage2_progress_end(Stage2ProgressNode *node) {}
89void stage2_progress_complete_one(Stage2ProgressNode *node) {}89void stage2_progress_complete_one(Stage2ProgressNode *node) {}
90void stage2_progress_disable_tty(Stage2Progress *progress) {}
src/userland.h+2
...@@ -163,6 +163,8 @@ struct Stage2ProgressNode;...@@ -163,6 +163,8 @@ struct Stage2ProgressNode;
163// ABI warning163// ABI warning
164ZIG_EXTERN_C Stage2Progress *stage2_progress_create(void);164ZIG_EXTERN_C Stage2Progress *stage2_progress_create(void);
165// ABI warning165// ABI warning
166ZIG_EXTERN_C void stage2_progress_disable_tty(Stage2Progress *progress);
167// ABI warning
166ZIG_EXTERN_C void stage2_progress_destroy(Stage2Progress *progress);168ZIG_EXTERN_C void stage2_progress_destroy(Stage2Progress *progress);
167// ABI warning169// ABI warning
168ZIG_EXTERN_C Stage2ProgressNode *stage2_progress_start_root(Stage2Progress *progress,170ZIG_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,7 +87,7 @@ fn exec(cwd: []const u8, argv: []const []const u8) !ChildProcess.ExecResult {
87fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {87fn testZigInitLib(zig_exe: []const u8, dir_path: []const u8) !void {
88 _ = try exec(dir_path, [_][]const u8{ zig_exe, "init-lib" });88 _ = try exec(dir_path, [_][]const u8{ zig_exe, "init-lib" });
89 const test_result = try exec(dir_path, [_][]const u8{ zig_exe, "build", "test" });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}
9292
93fn testZigInitExe(zig_exe: []const u8, dir_path: []const u8) !void {93fn 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,6 +136,6 @@ fn testMissingOutputPath(zig_exe: []const u8, dir_path: []const u8) !void {
136 const output_path = try fs.path.join(a, [_][]const u8{ "does", "not", "exist" });136 const output_path = try fs.path.join(a, [_][]const u8{ "does", "not", "exist" });
137 const source_path = try fs.path.join(a, [_][]const u8{ "src", "main.zig" });137 const source_path = try fs.path.join(a, [_][]const u8{ "src", "main.zig" });
138 _ = try exec(dir_path, [_][]const u8{138 _ = try exec(dir_path, [_][]const u8{
139 zig_exe, "build-exe", source_path, "--output-dir", output_path139 zig_exe, "build-exe", source_path, "--output-dir", output_path,
140 });140 });
141}141}