LibWeb+LibTest: Move the progress bar/status printing stuff to LibTest
This commit is contained in:
parent
ce7b69ff31
commit
909013b972
3 changed files with 456 additions and 96 deletions
378
Libraries/LibTest/LiveDisplay.h
Normal file
378
Libraries/LibTest/LiveDisplay.h
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present, the Ladybird developers.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Assertions.h>
|
||||
#include <AK/ByteString.h>
|
||||
#include <AK/Format.h>
|
||||
#include <AK/Noncopyable.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/Types.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
# include <sys/ioctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace Test {
|
||||
|
||||
inline bool stdout_is_tty()
|
||||
{
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
return false;
|
||||
#else
|
||||
return isatty(STDOUT_FILENO);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline size_t query_terminal_width(int fd, size_t fallback = 80)
|
||||
{
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
struct winsize ws;
|
||||
if (ioctl(fd, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0)
|
||||
return ws.ws_col;
|
||||
#endif
|
||||
(void)fd;
|
||||
return fallback;
|
||||
}
|
||||
|
||||
class LiveDisplay {
|
||||
AK_MAKE_NONCOPYABLE(LiveDisplay);
|
||||
AK_MAKE_NONMOVABLE(LiveDisplay);
|
||||
|
||||
public:
|
||||
enum Color : u8 {
|
||||
None,
|
||||
Red,
|
||||
Green,
|
||||
Yellow,
|
||||
Blue,
|
||||
Magenta,
|
||||
Cyan,
|
||||
Gray,
|
||||
};
|
||||
|
||||
struct LabelColor {
|
||||
Color prefix;
|
||||
Color text;
|
||||
};
|
||||
|
||||
struct Counter {
|
||||
StringView label;
|
||||
Color color;
|
||||
size_t value;
|
||||
};
|
||||
|
||||
struct Options {
|
||||
size_t reserved_lines { 3 };
|
||||
// If non-empty, stdout and stderr are redirected to this file for the lifetime of the live display, and the display itself is written to the terminal.
|
||||
ByteString log_file_path;
|
||||
};
|
||||
|
||||
class RenderTarget {
|
||||
AK_MAKE_NONCOPYABLE(RenderTarget);
|
||||
AK_MAKE_NONMOVABLE(RenderTarget);
|
||||
friend class LiveDisplay;
|
||||
|
||||
public:
|
||||
StringBuilder& builder() { return m_builder; }
|
||||
size_t terminal_width() const { return m_terminal_width; }
|
||||
|
||||
template<typename Callback>
|
||||
void line(Callback&& callback)
|
||||
{
|
||||
m_builder.append("\033[2K"sv);
|
||||
callback();
|
||||
m_builder.append('\n');
|
||||
}
|
||||
|
||||
template<typename... Callbacks>
|
||||
void lines(Callbacks&&... callback)
|
||||
{
|
||||
(line(forward<Callbacks>(callback)), ...);
|
||||
}
|
||||
|
||||
void label(StringView prefix, StringView text, LabelColor color = { Yellow, None })
|
||||
{
|
||||
m_builder.append(LiveDisplay::ansi_on(color.prefix));
|
||||
m_builder.append(prefix);
|
||||
m_builder.append(LiveDisplay::ansi_reset(color.prefix));
|
||||
|
||||
size_t available = m_terminal_width > prefix.length() ? m_terminal_width - prefix.length() : 10;
|
||||
|
||||
m_builder.append(LiveDisplay::ansi_on(color.text));
|
||||
if (text.length() > available && available > 3) {
|
||||
m_builder.append("..."sv);
|
||||
m_builder.append(text.substring_view(text.length() - available + 3));
|
||||
} else {
|
||||
m_builder.append(text);
|
||||
}
|
||||
m_builder.append(LiveDisplay::ansi_reset(color.text));
|
||||
}
|
||||
|
||||
template<auto N>
|
||||
void counter(Counter const (&counters)[N])
|
||||
{
|
||||
bool first = true;
|
||||
for (auto const& c : counters) {
|
||||
if (!first)
|
||||
m_builder.append(", "sv);
|
||||
first = false;
|
||||
m_builder.append(LiveDisplay::ansi_bold_on(c.color));
|
||||
m_builder.appendff("{}:", c.label);
|
||||
m_builder.append("\033[0m"sv);
|
||||
m_builder.appendff(" {}", c.value);
|
||||
}
|
||||
}
|
||||
|
||||
void progress_bar(size_t completed, size_t total, StringView suffix = {})
|
||||
{
|
||||
auto counter_begin = m_builder.length();
|
||||
m_builder.appendff("{}/{} ", completed, total);
|
||||
if (!suffix.is_empty()) {
|
||||
m_builder.append(suffix);
|
||||
m_builder.append(' ');
|
||||
}
|
||||
size_t counter_length = m_builder.length() - counter_begin;
|
||||
size_t bar_width = m_terminal_width > counter_length + 3 ? m_terminal_width - counter_length - 3 : 20;
|
||||
size_t filled = total > 0 ? (completed * bar_width) / total : 0;
|
||||
size_t empty = bar_width > filled ? bar_width - filled : 0;
|
||||
|
||||
m_builder.append("\033[32m["sv);
|
||||
for (size_t j = 0; j < filled; ++j)
|
||||
m_builder.append("█"sv);
|
||||
if (empty > 0 && filled < bar_width) {
|
||||
m_builder.append("\033[33m▓\033[0m\033[90m"sv);
|
||||
for (size_t j = 1; j < empty; ++j)
|
||||
m_builder.append("░"sv);
|
||||
}
|
||||
m_builder.append("\033[32m]\033[0m"sv);
|
||||
}
|
||||
|
||||
private:
|
||||
RenderTarget(StringBuilder& builder, size_t terminal_width)
|
||||
: m_builder(builder)
|
||||
, m_terminal_width(terminal_width)
|
||||
{
|
||||
}
|
||||
|
||||
StringBuilder& m_builder;
|
||||
size_t m_terminal_width;
|
||||
};
|
||||
|
||||
LiveDisplay() = default;
|
||||
~LiveDisplay() { end(); }
|
||||
|
||||
bool begin(Options options)
|
||||
{
|
||||
#if defined(AK_OS_WINDOWS)
|
||||
(void)options;
|
||||
return false;
|
||||
#else
|
||||
if (m_active)
|
||||
return false;
|
||||
|
||||
m_reserved_lines = options.reserved_lines;
|
||||
m_log_file_path = move(options.log_file_path);
|
||||
|
||||
if (!m_log_file_path.is_empty()) {
|
||||
int log_fd = ::open(m_log_file_path.characters(), O_WRONLY | O_CREAT | O_TRUNC, 0644);
|
||||
if (log_fd < 0)
|
||||
return false;
|
||||
|
||||
m_saved_stdout_fd = dup(STDOUT_FILENO);
|
||||
m_saved_stderr_fd = dup(STDERR_FILENO);
|
||||
if (m_saved_stdout_fd < 0 || m_saved_stderr_fd < 0) {
|
||||
close(log_fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
(void)fflush(stdout);
|
||||
(void)fflush(stderr);
|
||||
(void)dup2(log_fd, STDOUT_FILENO);
|
||||
(void)dup2(log_fd, STDERR_FILENO);
|
||||
close(log_fd);
|
||||
|
||||
int display_fd = dup(m_saved_stdout_fd);
|
||||
if (display_fd < 0)
|
||||
return false;
|
||||
m_output = fdopen(display_fd, "w");
|
||||
if (!m_output) {
|
||||
close(display_fd);
|
||||
return false;
|
||||
}
|
||||
(void)setvbuf(m_output, nullptr, _IONBF, 0);
|
||||
} else {
|
||||
m_output = stdout;
|
||||
}
|
||||
|
||||
refresh_terminal_width();
|
||||
|
||||
for (size_t i = 0; i < m_reserved_lines; ++i)
|
||||
(void)fputc('\n', m_output);
|
||||
(void)fflush(m_output);
|
||||
|
||||
m_active = true;
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void end()
|
||||
{
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
if (!m_active)
|
||||
return;
|
||||
|
||||
clear();
|
||||
|
||||
bool redirected = m_saved_stdout_fd >= 0 || m_saved_stderr_fd >= 0;
|
||||
if (redirected) {
|
||||
if (m_output) {
|
||||
(void)fclose(m_output);
|
||||
m_output = nullptr;
|
||||
}
|
||||
(void)fflush(stdout);
|
||||
(void)fflush(stderr);
|
||||
if (m_saved_stdout_fd >= 0) {
|
||||
(void)dup2(m_saved_stdout_fd, STDOUT_FILENO);
|
||||
close(m_saved_stdout_fd);
|
||||
m_saved_stdout_fd = -1;
|
||||
}
|
||||
if (m_saved_stderr_fd >= 0) {
|
||||
(void)dup2(m_saved_stderr_fd, STDERR_FILENO);
|
||||
close(m_saved_stderr_fd);
|
||||
m_saved_stderr_fd = -1;
|
||||
}
|
||||
(void)setvbuf(stdout, nullptr, _IOLBF, 0);
|
||||
(void)setvbuf(stderr, nullptr, _IONBF, 0);
|
||||
} else {
|
||||
m_output = nullptr;
|
||||
}
|
||||
m_active = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool is_active() const { return m_active; }
|
||||
FILE* output() const { return m_output; }
|
||||
size_t terminal_width() const { return m_terminal_width; }
|
||||
size_t reserved_lines() const { return m_reserved_lines; }
|
||||
void set_reserved_lines(size_t n) { m_reserved_lines = n; }
|
||||
ByteString const& log_file_path() const { return m_log_file_path; }
|
||||
bool redirected_stdio() const { return m_saved_stdout_fd >= 0; }
|
||||
|
||||
void refresh_terminal_width()
|
||||
{
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
int fd = m_output ? fileno(m_output) : STDOUT_FILENO;
|
||||
#else
|
||||
int fd = 0; // Not actually used.
|
||||
#endif
|
||||
m_terminal_width = query_terminal_width(fd);
|
||||
}
|
||||
|
||||
// Erase the reserved display area, leaving the cursor at the top of it.
|
||||
void clear()
|
||||
{
|
||||
if (!m_active || !m_output)
|
||||
return;
|
||||
StringBuilder builder;
|
||||
for (size_t i = 0; i < m_reserved_lines; ++i)
|
||||
builder.append("\033[A\r\033[2K"sv);
|
||||
write(builder.string_view());
|
||||
}
|
||||
|
||||
template<typename Callback>
|
||||
void render(Callback&& callback)
|
||||
{
|
||||
if (!m_active || !m_output)
|
||||
return;
|
||||
|
||||
StringBuilder builder;
|
||||
for (size_t i = 0; i < m_reserved_lines; ++i)
|
||||
builder.append("\033[A"sv);
|
||||
builder.append("\r"sv);
|
||||
|
||||
RenderTarget target { builder, m_terminal_width };
|
||||
callback(target);
|
||||
|
||||
write(builder.string_view());
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr StringView ansi_on(Color c)
|
||||
{
|
||||
switch (c) {
|
||||
case None:
|
||||
return ""sv;
|
||||
case Red:
|
||||
return "\033[31m"sv;
|
||||
case Green:
|
||||
return "\033[32m"sv;
|
||||
case Yellow:
|
||||
return "\033[33m"sv;
|
||||
case Blue:
|
||||
return "\033[34m"sv;
|
||||
case Magenta:
|
||||
return "\033[35m"sv;
|
||||
case Cyan:
|
||||
return "\033[36m"sv;
|
||||
case Gray:
|
||||
return "\033[90m"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr StringView ansi_bold_on(Color c)
|
||||
{
|
||||
switch (c) {
|
||||
case None:
|
||||
return "\033[1m"sv;
|
||||
case Red:
|
||||
return "\033[1;31m"sv;
|
||||
case Green:
|
||||
return "\033[1;32m"sv;
|
||||
case Yellow:
|
||||
return "\033[1;33m"sv;
|
||||
case Blue:
|
||||
return "\033[1;34m"sv;
|
||||
case Magenta:
|
||||
return "\033[1;35m"sv;
|
||||
case Cyan:
|
||||
return "\033[1;36m"sv;
|
||||
case Gray:
|
||||
return "\033[1;90m"sv;
|
||||
}
|
||||
VERIFY_NOT_REACHED();
|
||||
}
|
||||
|
||||
static constexpr StringView ansi_reset(Color c)
|
||||
{
|
||||
return c == None ? ""sv : "\033[0m"sv;
|
||||
}
|
||||
|
||||
void write(StringView data)
|
||||
{
|
||||
(void)fwrite(data.characters_without_null_termination(), 1, data.length(), m_output);
|
||||
(void)fflush(m_output);
|
||||
}
|
||||
|
||||
bool m_active { false };
|
||||
size_t m_reserved_lines { 0 };
|
||||
size_t m_terminal_width { 80 };
|
||||
FILE* m_output { nullptr };
|
||||
int m_saved_stdout_fd { -1 };
|
||||
#if !defined(AK_OS_WINDOWS)
|
||||
int m_saved_stderr_fd { -1 };
|
||||
#endif
|
||||
ByteString m_log_file_path;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -8,27 +8,30 @@
|
|||
#include "Application.h"
|
||||
|
||||
#include <AK/Enumerate.h>
|
||||
#include <AK/Math.h>
|
||||
#include <AK/QuickSort.h>
|
||||
#include <AK/SaturatingMath.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <LibCore/EventLoop.h>
|
||||
#include <LibCore/File.h>
|
||||
#include <LibCore/System.h>
|
||||
#include <LibCore/Timer.h>
|
||||
#include <LibDiff/Format.h>
|
||||
#include <LibDiff/Generator.h>
|
||||
#include <LibGfx/Size.h>
|
||||
#include <LibTest/LiveDisplay.h>
|
||||
|
||||
#ifndef AK_OS_WINDOWS
|
||||
# include <signal.h>
|
||||
# include <sys/ioctl.h>
|
||||
# include <unistd.h>
|
||||
#endif
|
||||
|
||||
namespace TestWeb {
|
||||
|
||||
static constexpr size_t LIVE_DISPLAY_TERMINAL_HEADROOM = 4; // allow for external cruft like tmux panels
|
||||
static constexpr size_t LIVE_DISPLAY_STATUS_LINES = 4; // 2 empty + 1 for status + 1 for progress bar
|
||||
static size_t s_display_rows = 80;
|
||||
static size_t s_display_columns = 24;
|
||||
static size_t s_display_rows = 24;
|
||||
|
||||
static ::Test::LiveDisplay s_live_display;
|
||||
|
||||
static size_t count_digits(size_t value);
|
||||
|
||||
Display& Display::the()
|
||||
|
|
@ -40,33 +43,39 @@ Display& Display::the()
|
|||
void Display::begin_run()
|
||||
{
|
||||
auto& app = Application::the();
|
||||
is_tty = Core::System::isatty(STDOUT_FILENO).value_or(false);
|
||||
is_live_display_active = !app.quiet && is_tty && app.verbosity < Application::VERBOSITY_LEVEL_LOG_TEST_OUTPUT;
|
||||
is_tty = ::Test::stdout_is_tty();
|
||||
bool const want_live_display = !app.quiet && is_tty && app.verbosity < Application::VERBOSITY_LEVEL_LOG_TEST_OUTPUT;
|
||||
|
||||
outln("Running {} tests...", total_tests());
|
||||
|
||||
if (!want_live_display)
|
||||
return;
|
||||
|
||||
size_t terminal_rows = 24;
|
||||
#ifndef AK_OS_WINDOWS
|
||||
struct winsize ws;
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_row > 0)
|
||||
terminal_rows = ws.ws_row;
|
||||
#endif
|
||||
s_display_rows = AK::clamp(
|
||||
AK::saturating_sub(terminal_rows, LIVE_DISPLAY_TERMINAL_HEADROOM),
|
||||
LIVE_DISPLAY_STATUS_LINES + 1,
|
||||
view_states().size() + LIVE_DISPLAY_STATUS_LINES);
|
||||
|
||||
is_live_display_active = s_live_display.begin({ .reserved_lines = s_display_rows, .log_file_path = {} });
|
||||
if (!is_live_display_active)
|
||||
return;
|
||||
|
||||
#ifndef AK_OS_WINDOWS
|
||||
struct winsize ws;
|
||||
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
|
||||
s_display_rows = ws.ws_row > 0 ? ws.ws_row : 24;
|
||||
s_display_columns = ws.ws_col > 0 ? ws.ws_col : 80;
|
||||
}
|
||||
Core::EventLoop::register_signal(SIGWINCH, [](int) {
|
||||
Core::EventLoop::current().deferred_invoke([] {
|
||||
s_live_display.refresh_terminal_width();
|
||||
});
|
||||
});
|
||||
#endif
|
||||
s_display_rows = AK::clamp(
|
||||
AK::saturating_sub(s_display_rows, LIVE_DISPLAY_TERMINAL_HEADROOM),
|
||||
LIVE_DISPLAY_STATUS_LINES + 1,
|
||||
view_states().size() + LIVE_DISPLAY_STATUS_LINES);
|
||||
|
||||
display_timer = Core::Timer::create_repeating(1000, [this] { render_live_display(); });
|
||||
display_timer->start();
|
||||
|
||||
for (size_t i = 0; i < s_display_rows; i++) {
|
||||
outln();
|
||||
}
|
||||
(void)fflush(stdout);
|
||||
}
|
||||
|
||||
void Display::on_test_started(size_t view_index, Test const& test, pid_t pid)
|
||||
|
|
@ -214,83 +223,60 @@ void Display::print_failure_diff(URL::URL const& url, Test const& test,
|
|||
|
||||
void Display::render_live_display() const
|
||||
{
|
||||
if (!is_tty || !is_live_display_active)
|
||||
if (!s_live_display.is_active())
|
||||
return;
|
||||
|
||||
auto now = UnixDateTime::now();
|
||||
StringBuilder output;
|
||||
|
||||
for (size_t i = 0; i < s_display_rows; ++i)
|
||||
output.append("\033[A"sv);
|
||||
output.append("\r"sv);
|
||||
s_live_display.render([&](::Test::LiveDisplay::RenderTarget& t) {
|
||||
size_t const reserved = s_live_display.reserved_lines();
|
||||
size_t num_view_lines = reserved > LIVE_DISPLAY_STATUS_LINES ? reserved - LIVE_DISPLAY_STATUS_LINES : 0;
|
||||
bool const need_hidden_line = num_view_lines > 1 && num_view_lines < view_states().size();
|
||||
if (need_hidden_line)
|
||||
num_view_lines--;
|
||||
|
||||
size_t num_view_lines = s_display_rows - LIVE_DISPLAY_STATUS_LINES;
|
||||
if (num_view_lines > 1 && num_view_lines < view_states().size())
|
||||
num_view_lines--; // "Hidden views" line
|
||||
|
||||
for (size_t i = 0; i < num_view_lines; ++i) {
|
||||
output.append("\033[2K"sv);
|
||||
|
||||
if (i < view_states().size()) {
|
||||
auto const& state = view_states()[i];
|
||||
if (state.active && state.pid > 0) {
|
||||
auto duration = (now - state.start_time).to_truncated_seconds();
|
||||
auto prefix = ByteString::formatted("\033[33m⏺\033[0m {} ({}s): ", state.pid, duration);
|
||||
size_t const prefix_visible_length = ByteString::formatted("⏺ {} ({}s): ", state.pid, duration).length();
|
||||
size_t const available_width = s_display_columns > prefix_visible_length
|
||||
? s_display_columns - prefix_visible_length
|
||||
: 10;
|
||||
ByteString name = state.test_name;
|
||||
if (name.length() > available_width && available_width > 3)
|
||||
name = ByteString::formatted("...{}", name.substring_view(name.length() - available_width + 3));
|
||||
|
||||
output.appendff("{}{}", prefix, name);
|
||||
} else {
|
||||
output.append("\033[90m⏺ (idle)\033[0m"sv);
|
||||
}
|
||||
for (size_t i = 0; i < num_view_lines; ++i) {
|
||||
t.line([&] {
|
||||
if (i >= view_states().size())
|
||||
return;
|
||||
auto const& state = view_states()[i];
|
||||
if (state.active && state.pid > 0) {
|
||||
auto duration = (now - state.start_time).to_truncated_seconds();
|
||||
auto prefix = ByteString::formatted("⏺ {} ({}s): ", state.pid, duration);
|
||||
t.label(prefix, state.test_name);
|
||||
} else {
|
||||
t.label("⏺ (idle)"sv, {}, { .prefix = ::Test::LiveDisplay::Gray, .text = ::Test::LiveDisplay::None });
|
||||
}
|
||||
});
|
||||
}
|
||||
output.append("\n"sv);
|
||||
}
|
||||
if (num_view_lines < view_states().size()) {
|
||||
output.append("\033[2K\033[90m... "sv);
|
||||
output.appendff("{} more views hidden\033[0m", view_states().size() - num_view_lines);
|
||||
output.append("\n"sv);
|
||||
}
|
||||
output.append("\033[2K\n\033[2K"sv);
|
||||
output.appendff("\033[1;32mPass:\033[0m {}, ", pass_count);
|
||||
output.appendff("\033[1;31mFail:\033[0m {}, ", fail_count);
|
||||
output.appendff("\033[1;90mSkipped:\033[0m {}, ", skipped_count);
|
||||
output.appendff("\033[1;33mTimeout:\033[0m {}, ", timeout_count);
|
||||
output.appendff("\033[1;35mCrashed:\033[0m {}", crashed_count);
|
||||
output.append("\n\033[2K\n\033[2K"sv);
|
||||
|
||||
if (total_tests() > 0) {
|
||||
auto counter_start = output.length();
|
||||
output.appendff("{}/{} ", completed_tests, total_tests());
|
||||
if (Application::the().repeat_count > 1)
|
||||
output.appendff("run {}/{} ", current_run, Application::the().repeat_count);
|
||||
|
||||
auto const counter_length = output.length() - counter_start;
|
||||
size_t const bar_width = s_display_columns > counter_length + 3 ? s_display_columns - counter_length - 3 : 20;
|
||||
size_t const filled = (completed_tests * bar_width) / total_tests();
|
||||
size_t const empty = bar_width - filled;
|
||||
|
||||
output.append("\033[32m["sv);
|
||||
for (size_t j = 0; j < filled; ++j) {
|
||||
output.append("█"sv);
|
||||
if (need_hidden_line) {
|
||||
t.line([&] {
|
||||
auto label = ByteString::formatted("... {} more views hidden", view_states().size() - num_view_lines);
|
||||
t.label(label, {}, { .prefix = ::Test::LiveDisplay::Gray, .text = ::Test::LiveDisplay::None });
|
||||
});
|
||||
}
|
||||
if (empty > 0 && filled < bar_width) {
|
||||
output.append("\033[33m▓\033[0m\033[90m"sv);
|
||||
for (size_t j = 1; j < empty; ++j) {
|
||||
output.append("░"sv);
|
||||
}
|
||||
}
|
||||
output.append("\033[32m]\033[0m"sv);
|
||||
}
|
||||
output.append("\n"sv);
|
||||
|
||||
out("{}", output.string_view());
|
||||
(void)fflush(stdout);
|
||||
t.lines(
|
||||
[] {},
|
||||
[&] {
|
||||
t.counter({
|
||||
{ .label = "Pass"sv, .color = ::Test::LiveDisplay::Green, .value = pass_count },
|
||||
{ .label = "Fail"sv, .color = ::Test::LiveDisplay::Red, .value = fail_count },
|
||||
{ .label = "Skipped"sv, .color = ::Test::LiveDisplay::Gray, .value = skipped_count },
|
||||
{ .label = "Timeout"sv, .color = ::Test::LiveDisplay::Yellow, .value = timeout_count },
|
||||
{ .label = "Crashed"sv, .color = ::Test::LiveDisplay::Magenta, .value = crashed_count },
|
||||
});
|
||||
},
|
||||
[] {},
|
||||
[&] {
|
||||
if (total_tests() == 0)
|
||||
return;
|
||||
ByteString suffix;
|
||||
if (Application::the().repeat_count > 1)
|
||||
suffix = ByteString::formatted("run {}/{}", current_run, Application::the().repeat_count);
|
||||
t.progress_bar(completed_tests, total_tests(), suffix);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
void Display::clear_live_display()
|
||||
|
|
@ -301,12 +287,7 @@ void Display::clear_live_display()
|
|||
display_timer->stop();
|
||||
display_timer = nullptr;
|
||||
}
|
||||
for (size_t i = 0; i < s_display_rows; ++i) {
|
||||
out("\033[A\033[2K"sv);
|
||||
}
|
||||
out("\r"sv);
|
||||
(void)fflush(stdout);
|
||||
|
||||
s_live_display.end();
|
||||
is_live_display_active = false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ namespace TestWeb {
|
|||
|
||||
static Vector<ViewDisplayState> s_view_display_states;
|
||||
static Vector<Function<void()>> s_view_run_next_test;
|
||||
|
||||
static RefPtr<Core::Promise<Empty>> s_all_tests_complete;
|
||||
static Vector<ByteString> s_skipped_tests;
|
||||
static Vector<ByteString> s_loaded_from_http_server;
|
||||
|
|
|
|||
Loading…
Reference in a new issue