| ... | @@ -1,30 +1,23 @@ | ... | @@ -1,30 +1,23 @@ |
| 1 | //! This is a non-allocating, non-fallible, and thread-safe API for printing | 1 | //! This API non-allocating, non-fallible, and thread-safe. |
| 2 | //! progress indicators to the terminal. | | |
| 3 | //! The tradeoff is that users of this API must provide the storage | 2 | //! The tradeoff is that users of this API must provide the storage |
| 4 | //! for each `Progress.Node`. | 3 | //! for each `Progress.Node`. |
| 5 | //! | 4 | //! |
| 6 | //! This library purposefully keeps its output simple and is ASCII-compatible. | | |
| 7 | //! | | |
| 8 | //! Initialize the struct directly, overriding these fields as desired: | 5 | //! Initialize the struct directly, overriding these fields as desired: |
| 9 | //! * `refresh_rate_ms` | 6 | //! * `refresh_rate_ms` |
| 10 | //! * `initial_delay_ms` | 7 | //! * `initial_delay_ms` |
| 11 | //! * `dont_print_on_dumb` | | |
| 12 | //! * `max_width` | | |
| 13 | | 8 | |
| 14 | const std = @import("std"); | 9 | const std = @import("std"); |
| 15 | const builtin = @import("builtin"); | 10 | const builtin = @import("builtin"); |
| 16 | const windows = std.os.windows; | 11 | const windows = std.os.windows; |
| 17 | const testing = std.testing; | 12 | const testing = std.testing; |
| 18 | const assert = std.debug.assert; | 13 | const assert = std.debug.assert; |
| 19 | const os = std.os; | | |
| 20 | const time = std.time; | | |
| 21 | const Progress = @This(); | 14 | const Progress = @This(); |
| 22 | | 15 | |
| 23 | /// `null` if the current node (and its children) should | 16 | /// `null` if the current node (and its children) should |
| 24 | /// not print on update() | 17 | /// not print on update() |
| 25 | terminal: ?std.fs.File = undefined, | 18 | terminal: ?std.fs.File = undefined, |
| 26 | | 19 | |
| 27 | /// Is this a Windows API terminal (note: this is not the same as being run on Windows | 20 | /// Is this a windows API terminal (note: this is not the same as being run on windows |
| 28 | /// because other terminals exist like MSYS/git-bash) | 21 | /// because other terminals exist like MSYS/git-bash) |
| 29 | is_windows_terminal: bool = false, | 22 | is_windows_terminal: bool = false, |
| 30 | | 23 | |
| ... | @@ -42,31 +35,21 @@ root: Node = undefined, | ... | @@ -42,31 +35,21 @@ root: Node = undefined, |
| 42 | | 35 | |
| 43 | /// Keeps track of how much time has passed since the beginning. | 36 | /// Keeps track of how much time has passed since the beginning. |
| 44 | /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`. | 37 | /// Used to compare with `initial_delay_ms` and `refresh_rate_ms`. |
| 45 | timer: ?time.Timer = null, | 38 | timer: ?std.time.Timer = null, |
| 46 | | 39 | |
| 47 | /// When the previous refresh was written to the terminal. | 40 | /// When the previous refresh was written to the terminal. |
| 48 | /// Used to compare with `refresh_rate_ms`. | 41 | /// Used to compare with `refresh_rate_ms`. |
| 49 | prev_refresh_timestamp: u64 = undefined, | 42 | prev_refresh_timestamp: u64 = undefined, |
| 50 | | 43 | |
| 51 | /// This is the maximum number of bytes that can be written to the terminal each refresh. | 44 | /// This buffer represents the maximum number of bytes written to the terminal |
| 52 | /// Anything larger than this is truncated. | 45 | /// with each refresh. |
| 53 | // we can bump this up if we need to | 46 | output_buffer: [100]u8 = undefined, |
| 54 | output_buffer: [256]u8 = undefined, | | |
| 55 | output_buffer_slice: []u8 = undefined, | | |
| 56 | | | |
| 57 | /// This is the maximum number of bytes written to the terminal with each refresh. | | |
| 58 | /// | | |
| 59 | /// It is recommended to leave this as `null` so that `start` can automatically decide an | | |
| 60 | /// optimal width for the terminal. | | |
| 61 | /// | | |
| 62 | /// Note that this will be clamped to at least 4 and output will appear malformed if it is < 4. | | |
| 63 | max_width: ?usize = null, | | |
| 64 | | 47 | |
| 65 | /// How many nanoseconds between writing updates to the terminal. | 48 | /// How many nanoseconds between writing updates to the terminal. |
| 66 | refresh_rate_ns: u64 = 50 * time.ns_per_ms, | 49 | refresh_rate_ns: u64 = 50 * std.time.ns_per_ms, |
| 67 | | 50 | |
| 68 | /// How many nanoseconds to keep the output hidden. | 51 | /// How many nanoseconds to keep the output hidden |
| 69 | initial_delay_ns: u64 = 500 * time.ns_per_ms, | 52 | initial_delay_ns: u64 = 500 * std.time.ns_per_ms, |
| 70 | | 53 | |
| 71 | done: bool = true, | 54 | done: bool = true, |
| 72 | | 55 | |
| ... | @@ -79,14 +62,11 @@ update_mutex: std.Thread.Mutex = .{}, | ... | @@ -79,14 +62,11 @@ update_mutex: std.Thread.Mutex = .{}, |
| 79 | /// we can move the cursor back later. | 62 | /// we can move the cursor back later. |
| 80 | columns_written: usize = undefined, | 63 | columns_written: usize = undefined, |
| 81 | | 64 | |
| 82 | const truncation_suffix = "... "; | | |
| 83 | | | |
| 84 | /// Represents one unit of progress. Each node can have children nodes, or | 65 | /// Represents one unit of progress. Each node can have children nodes, or |
| 85 | /// one can use integers with `update`. | 66 | /// one can use integers with `update`. |
| 86 | pub const Node = struct { | 67 | pub const Node = struct { |
| 87 | context: *Progress, | 68 | context: *Progress, |
| 88 | parent: ?*Node, | 69 | parent: ?*Node, |
| 89 | /// The name that will be displayed for this node. | | |
| 90 | name: []const u8, | 70 | name: []const u8, |
| 91 | /// Must be handled atomically to be thread-safe. | 71 | /// Must be handled atomically to be thread-safe. |
| 92 | recently_updated_child: ?*Node = null, | 72 | recently_updated_child: ?*Node = null, |
| ... | @@ -159,13 +139,9 @@ pub const Node = struct { | ... | @@ -159,13 +139,9 @@ pub const Node = struct { |
| 159 | | 139 | |
| 160 | /// Create a new progress node. | 140 | /// Create a new progress node. |
| 161 | /// Call `Node.end` when done. | 141 | /// Call `Node.end` when done. |
| | 142 | /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this |
| | 143 | /// API to return Progress rather than accept it as a parameter. |
| 162 | /// `estimated_total_items` value of 0 means unknown. | 144 | /// `estimated_total_items` value of 0 means unknown. |
| 163 | /// | | |
| 164 | /// Note that as soon as work is started and progress output is printed, | | |
| 165 | /// `std.Progress` expects you to lean back and wait and not resize the terminal. | | |
| 166 | /// Resizing the terminal during progress output may result in malformed output. | | |
| 167 | // TODO: solve https://github.com/ziglang/zig/issues/2765 and then change this | | |
| 168 | // API to return Progress rather than accept it as a parameter. | | |
| 169 | pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *Node { | 145 | pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *Node { |
| 170 | const stderr = std.io.getStdErr(); | 146 | const stderr = std.io.getStdErr(); |
| 171 | self.terminal = null; | 147 | self.terminal = null; |
| ... | @@ -179,7 +155,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N | ... | @@ -179,7 +155,6 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N |
| 179 | // we are in a "dumb" terminal like in acme or writing to a file | 155 | // we are in a "dumb" terminal like in acme or writing to a file |
| 180 | self.terminal = stderr; | 156 | self.terminal = stderr; |
| 181 | } | 157 | } |
| 182 | self.calculateMaxWidth(); | | |
| 183 | self.root = Node{ | 158 | self.root = Node{ |
| 184 | .context = self, | 159 | .context = self, |
| 185 | .parent = null, | 160 | .parent = null, |
| ... | @@ -189,83 +164,11 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N | ... | @@ -189,83 +164,11 @@ pub fn start(self: *Progress, name: []const u8, estimated_total_items: usize) *N |
| 189 | }; | 164 | }; |
| 190 | self.columns_written = 0; | 165 | self.columns_written = 0; |
| 191 | self.prev_refresh_timestamp = 0; | 166 | self.prev_refresh_timestamp = 0; |
| 192 | self.timer = time.Timer.start() catch null; | 167 | self.timer = std.time.Timer.start() catch null; |
| 193 | self.done = false; | 168 | self.done = false; |
| 194 | return &self.root; | 169 | return &self.root; |
| 195 | } | 170 | } |
| 196 | | 171 | |
| 197 | fn calculateMaxWidth(self: *Progress) void { | | |
| 198 | if (self.max_width == null) { | | |
| 199 | if (self.terminal) |terminal| { | | |
| 200 | // choose an optimal width and account for progress output that could have been printed | | |
| 201 | // before us by another `std.Progress` instance | | |
| 202 | const terminal_width = self.getTerminalWidth(terminal.handle) catch 100; | | |
| 203 | const chars_already_printed = self.getTerminalCursorColumn(terminal) catch 0; | | |
| 204 | self.max_width = terminal_width - chars_already_printed; | | |
| 205 | } else { | | |
| 206 | self.max_width = 100; | | |
| 207 | } | | |
| 208 | } | | |
| 209 | self.max_width = std.math.clamp( | | |
| 210 | self.max_width.?, | | |
| 211 | truncation_suffix.len, // make sure we can at least truncate | | |
| 212 | self.output_buffer.len - 1, | | |
| 213 | ); | | |
| 214 | } | | |
| 215 | | | |
| 216 | fn getTerminalWidth(self: Progress, file_handle: os.fd_t) !u16 { | | |
| 217 | if (builtin.os.tag == .linux) { | | |
| 218 | // TODO: figure out how to get this working on FreeBSD, macOS etc. too. | | |
| 219 | // they too should have capabilities to figure out the cursor column. | | |
| 220 | var winsize: os.linux.winsize = undefined; | | |
| 221 | switch (os.errno(os.linux.ioctl(file_handle, os.linux.T.IOCGWINSZ, @ptrToInt(&winsize)))) { | | |
| 222 | .SUCCESS => return winsize.ws_col, | | |
| 223 | else => return error.Unexpected, | | |
| 224 | } | | |
| 225 | } else if (builtin.os.tag == .windows) { | | |
| 226 | std.debug.assert(self.is_windows_terminal); | | |
| 227 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | | |
| 228 | if (windows.kernel32.GetConsoleScreenBufferInfo(file_handle, &info) != windows.TRUE) | | |
| 229 | return error.Unexpected; | | |
| 230 | return @intCast(u16, info.dwSize.X); | | |
| 231 | } else { | | |
| 232 | return error.Unsupported; | | |
| 233 | } | | |
| 234 | } | | |
| 235 | | | |
| 236 | fn getTerminalCursorColumn(self: Progress, file: std.fs.File) !u16 { | | |
| 237 | // TODO: figure out how to get this working on FreeBSD, macOS etc. too. | | |
| 238 | // they too should have termios or capabilities to figure out the terminal width. | | |
| 239 | if (builtin.os.tag == .linux and self.supports_ansi_escape_codes) { | | |
| 240 | // First, disable echo and enable non-canonical mode | | |
| 241 | // (so that no enter press required for us to read the output of the escape sequence below) | | |
| 242 | const original_termios = try os.tcgetattr(file.handle); | | |
| 243 | var new_termios = original_termios; | | |
| 244 | new_termios.lflag &= ~(os.linux.ECHO | os.linux.ICANON); | | |
| 245 | try os.tcsetattr(file.handle, .NOW, new_termios); | | |
| 246 | defer os.tcsetattr(file.handle, .NOW, original_termios) catch { | | |
| 247 | // Sorry for ruining your terminal | | |
| 248 | }; | | |
| 249 | | | |
| 250 | try file.writeAll("\x1b[6n"); | | |
| 251 | var buf: ["\x1b[65536;65536R".len]u8 = undefined; | | |
| 252 | const output = try file.reader().readUntilDelimiter(&buf, 'R'); | | |
| 253 | var splitter = std.mem.split(u8, output, ";"); | | |
| 254 | _ = splitter.next().?; // skip first half | | |
| 255 | const column_half = splitter.next() orelse return error.UnexpectedEnd; | | |
| 256 | const column = try std.fmt.parseUnsigned(u16, column_half, 10); | | |
| 257 | return column - 1; // it's one-based | | |
| 258 | } else if (builtin.os.tag == .windows) { | | |
| 259 | std.debug.assert(self.is_windows_terminal); | | |
| 260 | var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; | | |
| 261 | if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.TRUE) | | |
| 262 | return error.Unexpected; | | |
| 263 | return @intCast(u16, info.dwCursorPosition.X); | | |
| 264 | } else { | | |
| 265 | return error.Unsupported; | | |
| 266 | } | | |
| 267 | } | | |
| 268 | | | |
| 269 | /// Updates the terminal if enough time has passed since last update. Thread-safe. | 172 | /// Updates the terminal if enough time has passed since last update. Thread-safe. |
| 270 | pub fn maybeRefresh(self: *Progress) void { | 173 | pub fn maybeRefresh(self: *Progress) void { |
| 271 | if (self.timer) |*timer| { | 174 | if (self.timer) |*timer| { |
| ... | @@ -295,16 +198,14 @@ fn refreshWithHeldLock(self: *Progress) void { | ... | @@ -295,16 +198,14 @@ fn refreshWithHeldLock(self: *Progress) void { |
| 295 | | 198 | |
| 296 | const file = self.terminal orelse return; | 199 | const file = self.terminal orelse return; |
| 297 | | 200 | |
| 298 | // prepare for printing unprintable characters | | |
| 299 | self.output_buffer_slice = &self.output_buffer; | | |
| 300 | | | |
| 301 | var end: usize = 0; | 201 | var end: usize = 0; |
| 302 | if (self.columns_written > 0) { | 202 | if (self.columns_written > 0) { |
| 303 | // restore the cursor position by moving the cursor | 203 | // restore the cursor position by moving the cursor |
| 304 | // `columns_written` cells to the left, then clear the rest of the line | 204 | // `columns_written` cells to the left, then clear the rest of the |
| | 205 | // line |
| 305 | if (self.supports_ansi_escape_codes) { | 206 | if (self.supports_ansi_escape_codes) { |
| 306 | end += (std.fmt.bufPrint(self.output_buffer_slice[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len; | 207 | end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[{d}D", .{self.columns_written}) catch unreachable).len; |
| 307 | end += (std.fmt.bufPrint(self.output_buffer_slice[end..], "\x1b[0K", .{}) catch unreachable).len; | 208 | end += (std.fmt.bufPrint(self.output_buffer[end..], "\x1b[0K", .{}) catch unreachable).len; |
| 308 | } else if (builtin.os.tag == .windows) winapi: { | 209 | } else if (builtin.os.tag == .windows) winapi: { |
| 309 | std.debug.assert(self.is_windows_terminal); | 210 | std.debug.assert(self.is_windows_terminal); |
| 310 | | 211 | |
| ... | @@ -346,53 +247,47 @@ fn refreshWithHeldLock(self: *Progress) void { | ... | @@ -346,53 +247,47 @@ fn refreshWithHeldLock(self: *Progress) void { |
| 346 | unreachable; | 247 | unreachable; |
| 347 | } else { | 248 | } else { |
| 348 | // we are in a "dumb" terminal like in acme or writing to a file | 249 | // we are in a "dumb" terminal like in acme or writing to a file |
| 349 | self.output_buffer_slice[end] = '\n'; | 250 | self.output_buffer[end] = '\n'; |
| 350 | end += 1; | 251 | end += 1; |
| 351 | } | 252 | } |
| 352 | | 253 | |
| 353 | self.columns_written = 0; | 254 | self.columns_written = 0; |
| 354 | } | 255 | } |
| 355 | | 256 | |
| 356 | // from here on we will write printable characters. we also make sure the unprintable characters | | |
| 357 | // we possibly wrote previously don't affect whether we truncate the line in `bufWrite`. | | |
| 358 | const unprintables = end; | | |
| 359 | end = 0; | | |
| 360 | self.output_buffer_slice = self.output_buffer[unprintables..@min(self.output_buffer.len, unprintables + self.max_width.?)]; | | |
| 361 | | | |
| 362 | if (!self.done) { | 257 | if (!self.done) { |
| 363 | var need_ellipsis = false; | 258 | var need_ellipse = false; |
| 364 | var maybe_node: ?*Node = &self.root; | 259 | var maybe_node: ?*Node = &self.root; |
| 365 | while (maybe_node) |node| { | 260 | while (maybe_node) |node| { |
| 366 | if (need_ellipsis) { | 261 | if (need_ellipse) { |
| 367 | self.bufWrite(&end, "... ", .{}); | 262 | self.bufWrite(&end, "... ", .{}); |
| 368 | } | 263 | } |
| 369 | need_ellipsis = false; | 264 | need_ellipse = false; |
| 370 | const estimated_total_items = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic); | 265 | const eti = @atomicLoad(usize, &node.unprotected_estimated_total_items, .Monotonic); |
| 371 | const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic); | 266 | const completed_items = @atomicLoad(usize, &node.unprotected_completed_items, .Monotonic); |
| 372 | const current_item = completed_items + 1; | 267 | const current_item = completed_items + 1; |
| 373 | if (node.name.len != 0 or estimated_total_items > 0) { | 268 | if (node.name.len != 0 or eti > 0) { |
| 374 | if (node.name.len != 0) { | 269 | if (node.name.len != 0) { |
| 375 | self.bufWrite(&end, "{s}", .{node.name}); | 270 | self.bufWrite(&end, "{s}", .{node.name}); |
| 376 | need_ellipsis = true; | 271 | need_ellipse = true; |
| 377 | } | 272 | } |
| 378 | if (estimated_total_items > 0) { | 273 | if (eti > 0) { |
| 379 | if (need_ellipsis) self.bufWrite(&end, " ", .{}); | 274 | if (need_ellipse) self.bufWrite(&end, " ", .{}); |
| 380 | self.bufWrite(&end, "[{d}/{d}] ", .{ current_item, estimated_total_items }); | 275 | self.bufWrite(&end, "[{d}/{d}] ", .{ current_item, eti }); |
| 381 | need_ellipsis = false; | 276 | need_ellipse = false; |
| 382 | } else if (completed_items != 0) { | 277 | } else if (completed_items != 0) { |
| 383 | if (need_ellipsis) self.bufWrite(&end, " ", .{}); | 278 | if (need_ellipse) self.bufWrite(&end, " ", .{}); |
| 384 | self.bufWrite(&end, "[{d}] ", .{current_item}); | 279 | self.bufWrite(&end, "[{d}] ", .{current_item}); |
| 385 | need_ellipsis = false; | 280 | need_ellipse = false; |
| 386 | } | 281 | } |
| 387 | } | 282 | } |
| 388 | maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Acquire); | 283 | maybe_node = @atomicLoad(?*Node, &node.recently_updated_child, .Acquire); |
| 389 | } | 284 | } |
| 390 | if (need_ellipsis) { | 285 | if (need_ellipse) { |
| 391 | self.bufWrite(&end, "... ", .{}); | 286 | self.bufWrite(&end, "... ", .{}); |
| 392 | } | 287 | } |
| 393 | } | 288 | } |
| 394 | | 289 | |
| 395 | _ = file.write(self.output_buffer[0 .. end + unprintables]) catch { | 290 | _ = file.write(self.output_buffer[0..end]) catch { |
| 396 | // Stop trying to write to this file once it errors. | 291 | // Stop trying to write to this file once it errors. |
| 397 | self.terminal = null; | 292 | self.terminal = null; |
| 398 | }; | 293 | }; |
| ... | @@ -415,113 +310,32 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { | ... | @@ -415,113 +310,32 @@ pub fn log(self: *Progress, comptime format: []const u8, args: anytype) void { |
| 415 | } | 310 | } |
| 416 | | 311 | |
| 417 | fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { | 312 | fn bufWrite(self: *Progress, end: *usize, comptime format: []const u8, args: anytype) void { |
| 418 | if (std.fmt.bufPrint(self.output_buffer_slice[end.*..], format, args)) |written| { | 313 | if (std.fmt.bufPrint(self.output_buffer[end.*..], format, args)) |written| { |
| 419 | const amt = written.len; | 314 | const amt = written.len; |
| 420 | end.* += amt; | 315 | end.* += amt; |
| 421 | self.columns_written += amt; | 316 | self.columns_written += amt; |
| 422 | } else |err| switch (err) { | 317 | } else |err| switch (err) { |
| 423 | error.NoSpaceLeft => { | 318 | error.NoSpaceLeft => { |
| 424 | // truncate the line with a suffix. | 319 | self.columns_written += self.output_buffer.len - end.*; |
| 425 | // for example if we have "hello world" (len=11) and 10 is the limit, | 320 | end.* = self.output_buffer.len; |
| 426 | // it would become "hello w... " | 321 | const suffix = "... "; |
| 427 | self.columns_written += self.output_buffer_slice.len - end.*; | 322 | std.mem.copy(u8, self.output_buffer[self.output_buffer.len - suffix.len ..], suffix); |
| 428 | end.* = self.output_buffer_slice.len; | | |
| 429 | std.mem.copy( | | |
| 430 | u8, | | |
| 431 | self.output_buffer_slice[self.output_buffer_slice.len - truncation_suffix.len ..], | | |
| 432 | truncation_suffix, | | |
| 433 | ); | | |
| 434 | }, | 323 | }, |
| 435 | } | 324 | } |
| 436 | } | 325 | } |
| 437 | | 326 | |
| 438 | // By default these tests are disabled because they use time.sleep() | | |
| 439 | // and are therefore slow. They also prints bogus progress data to stderr. | | |
| 440 | const skip_tests = true; | | |
| 441 | | | |
| 442 | test "behavior on buffer overflow" { | | |
| 443 | if (skip_tests) | | |
| 444 | return error.SkipZigTest; | | |
| 445 | | | |
| 446 | // uncomment this to move the cursor | | |
| 447 | //std.debug.print("{s}", .{"A" ** 300}); | | |
| 448 | | | |
| 449 | var progress = Progress{}; | | |
| 450 | | | |
| 451 | const long_string = "A" ** 300; | | |
| 452 | var node = progress.start(long_string, 0); | | |
| 453 | | | |
| 454 | const speed_factor = time.ns_per_s / 4; | | |
| 455 | | | |
| 456 | time.sleep(speed_factor); | | |
| 457 | node.activate(); | | |
| 458 | time.sleep(speed_factor); | | |
| 459 | node.end(); | | |
| 460 | } | | |
| 461 | | | |
| 462 | test "multiple tasks with long names" { | | |
| 463 | if (skip_tests) | | |
| 464 | return error.SkipZigTest; | | |
| 465 | | | |
| 466 | var progress = Progress{}; | | |
| 467 | | | |
| 468 | const tasks = [_][]const u8{ | | |
| 469 | "A" ** 99, | | |
| 470 | "A" ** 100, | | |
| 471 | "A" ** 101, | | |
| 472 | "A" ** 102, | | |
| 473 | "A" ** 103, | | |
| 474 | }; | | |
| 475 | | | |
| 476 | const speed_factor = time.ns_per_s / 6; | | |
| 477 | | | |
| 478 | for (tasks) |task| { | | |
| 479 | var node = progress.start(task, 3); | | |
| 480 | time.sleep(speed_factor); | | |
| 481 | node.activate(); | | |
| 482 | | | |
| 483 | time.sleep(speed_factor); | | |
| 484 | node.completeOne(); | | |
| 485 | time.sleep(speed_factor); | | |
| 486 | node.completeOne(); | | |
| 487 | time.sleep(speed_factor); | | |
| 488 | node.completeOne(); | | |
| 489 | | | |
| 490 | node.end(); | | |
| 491 | } | | |
| 492 | } | | |
| 493 | | | |
| 494 | test "very short max width" { | | |
| 495 | if (skip_tests) | | |
| 496 | return error.SkipZigTest; | | |
| 497 | | | |
| 498 | var progress = Progress{ .max_width = 4 }; | | |
| 499 | | | |
| 500 | const task = "A" ** 300; | | |
| 501 | | | |
| 502 | const speed_factor = time.ns_per_s / 2; | | |
| 503 | | | |
| 504 | var node = progress.start(task, 3); | | |
| 505 | time.sleep(speed_factor); | | |
| 506 | node.activate(); | | |
| 507 | | | |
| 508 | time.sleep(speed_factor); | | |
| 509 | node.completeOne(); | | |
| 510 | time.sleep(speed_factor); | | |
| 511 | node.completeOne(); | | |
| 512 | | | |
| 513 | node.end(); | | |
| 514 | } | | |
| 515 | | | |
| 516 | test "basic functionality" { | 327 | test "basic functionality" { |
| 517 | if (skip_tests) | 328 | var disable = true; |
| | 329 | if (disable) { |
| | 330 | // This test is disabled because it uses time.sleep() and is therefore slow. It also |
| | 331 | // prints bogus progress data to stderr. |
| 518 | return error.SkipZigTest; | 332 | return error.SkipZigTest; |
| 519 | | 333 | } |
| 520 | var progress = Progress{}; | 334 | var progress = Progress{}; |
| 521 | const root_node = progress.start("", 100); | 335 | const root_node = progress.start("", 100); |
| 522 | defer root_node.end(); | 336 | defer root_node.end(); |
| 523 | | 337 | |
| 524 | const speed_factor = time.ns_per_ms; | 338 | const speed_factor = std.time.ns_per_ms; |
| 525 | | 339 | |
| 526 | const sub_task_names = [_][]const u8{ | 340 | const sub_task_names = [_][]const u8{ |
| 527 | "reticulating splines", | 341 | "reticulating splines", |
| ... | @@ -538,24 +352,24 @@ test "basic functionality" { | ... | @@ -538,24 +352,24 @@ test "basic functionality" { |
| 538 | next_sub_task = (next_sub_task + 1) % sub_task_names.len; | 352 | next_sub_task = (next_sub_task + 1) % sub_task_names.len; |
| 539 | | 353 | |
| 540 | node.completeOne(); | 354 | node.completeOne(); |
| 541 | time.sleep(5 * speed_factor); | 355 | std.time.sleep(5 * speed_factor); |
| 542 | node.completeOne(); | 356 | node.completeOne(); |
| 543 | node.completeOne(); | 357 | node.completeOne(); |
| 544 | time.sleep(5 * speed_factor); | 358 | std.time.sleep(5 * speed_factor); |
| 545 | node.completeOne(); | 359 | node.completeOne(); |
| 546 | node.completeOne(); | 360 | node.completeOne(); |
| 547 | time.sleep(5 * speed_factor); | 361 | std.time.sleep(5 * speed_factor); |
| 548 | | 362 | |
| 549 | node.end(); | 363 | node.end(); |
| 550 | | 364 | |
| 551 | time.sleep(5 * speed_factor); | 365 | std.time.sleep(5 * speed_factor); |
| 552 | } | 366 | } |
| 553 | { | 367 | { |
| 554 | var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0); | 368 | var node = root_node.start("this is a really long name designed to activate the truncation code. let's find out if it works", 0); |
| 555 | node.activate(); | 369 | node.activate(); |
| 556 | time.sleep(10 * speed_factor); | 370 | std.time.sleep(10 * speed_factor); |
| 557 | progress.refresh(); | 371 | progress.refresh(); |
| 558 | time.sleep(10 * speed_factor); | 372 | std.time.sleep(10 * speed_factor); |
| 559 | node.end(); | 373 | node.end(); |
| 560 | } | 374 | } |
| 561 | } | 375 | } |