mirror of
https://github.com/LadybirdBrowser/ladybird
synced 2026-04-25 17:25:08 +02:00
LibTest: Make the testrunner show a nice progress bar test-web-style
The old output format is preserved under non-tty.
This commit is contained in:
committed by
Ali Mohammad Pur
parent
909013b972
commit
107f55581d
Notes:
github-actions[bot]
2026-04-23 16:49:57 +00:00
Author: https://github.com/alimpfard Commit: https://github.com/LadybirdBrowser/ladybird/commit/107f55581d9 Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/8961
@@ -10,18 +10,40 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Array.h>
|
||||||
#include <AK/ByteString.h>
|
#include <AK/ByteString.h>
|
||||||
#include <AK/Format.h>
|
#include <AK/Format.h>
|
||||||
#include <AK/JsonObject.h>
|
#include <AK/JsonObject.h>
|
||||||
#include <AK/JsonValue.h>
|
#include <AK/JsonValue.h>
|
||||||
#include <AK/LexicalPath.h>
|
#include <AK/LexicalPath.h>
|
||||||
#include <AK/QuickSort.h>
|
#include <AK/QuickSort.h>
|
||||||
|
#include <AK/StringBuilder.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibTest/LiveDisplay.h>
|
||||||
#include <LibTest/Results.h>
|
#include <LibTest/Results.h>
|
||||||
#include <LibTest/TestRunnerUtil.h>
|
#include <LibTest/TestRunnerUtil.h>
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
|
||||||
|
#if !defined(AK_OS_WINDOWS)
|
||||||
|
# include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(AK_OS_MACOS) || defined(AK_OS_BSD_GENERIC)
|
||||||
|
# include <stdlib.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Test {
|
namespace Test {
|
||||||
|
|
||||||
|
[[maybe_unused]] inline auto const& s_invocation_cwd = [] {
|
||||||
|
#if !defined(AK_OS_WINDOWS)
|
||||||
|
char buf[PATH_MAX];
|
||||||
|
if (getcwd(buf, sizeof(buf)))
|
||||||
|
return ByteString(buf);
|
||||||
|
#endif
|
||||||
|
return ByteString {};
|
||||||
|
}();
|
||||||
|
|
||||||
class TestRunner {
|
class TestRunner {
|
||||||
public:
|
public:
|
||||||
static TestRunner* the()
|
static TestRunner* the()
|
||||||
@@ -68,6 +90,9 @@ protected:
|
|||||||
virtual void do_run_single_test(ByteString const&, size_t current_test_index, size_t num_tests) = 0;
|
virtual void do_run_single_test(ByteString const&, size_t current_test_index, size_t num_tests) = 0;
|
||||||
virtual Vector<ByteString> const* get_failed_test_names() const { return nullptr; }
|
virtual Vector<ByteString> const* get_failed_test_names() const { return nullptr; }
|
||||||
|
|
||||||
|
void render_live_display(size_t completed, size_t total);
|
||||||
|
bool begin_live_display();
|
||||||
|
|
||||||
ByteString m_test_root;
|
ByteString m_test_root;
|
||||||
bool m_print_times;
|
bool m_print_times;
|
||||||
bool m_print_progress;
|
bool m_print_progress;
|
||||||
@@ -77,6 +102,9 @@ protected:
|
|||||||
double m_total_elapsed_time_in_ms { 0 };
|
double m_total_elapsed_time_in_ms { 0 };
|
||||||
Test::Counts m_counts;
|
Test::Counts m_counts;
|
||||||
Optional<Vector<Test::Suite>> m_suites;
|
Optional<Vector<Test::Suite>> m_suites;
|
||||||
|
|
||||||
|
LiveDisplay m_live_display;
|
||||||
|
ByteString m_current_test_label;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline void cleanup()
|
inline void cleanup()
|
||||||
@@ -92,19 +120,91 @@ inline void cleanup()
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool TestRunner::begin_live_display()
|
||||||
|
{
|
||||||
|
#if defined(AK_OS_WINDOWS)
|
||||||
|
return false;
|
||||||
|
#else
|
||||||
|
# if defined(__GLIBC__)
|
||||||
|
char const* program = program_invocation_short_name;
|
||||||
|
# elif defined(AK_OS_MACOS) || defined(AK_OS_BSD_GENERIC)
|
||||||
|
char const* program = ::getprogname();
|
||||||
|
# else
|
||||||
|
char const* program = nullptr;
|
||||||
|
# endif
|
||||||
|
if (!program || !*program)
|
||||||
|
program = "test-runner";
|
||||||
|
auto const& cwd = s_invocation_cwd;
|
||||||
|
ByteString log_path = cwd.is_empty()
|
||||||
|
? ByteString::formatted("./{}.log", program)
|
||||||
|
: ByteString::formatted("{}/{}.log", cwd, program);
|
||||||
|
|
||||||
|
return m_live_display.begin({ .reserved_lines = 3, .log_file_path = move(log_path) });
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void TestRunner::render_live_display(size_t completed, size_t total)
|
||||||
|
{
|
||||||
|
m_live_display.render([&](LiveDisplay::RenderTarget& t) {
|
||||||
|
t.lines(
|
||||||
|
[&] {
|
||||||
|
if (!m_current_test_label.is_empty())
|
||||||
|
t.label("⏺ "sv, m_current_test_label);
|
||||||
|
else
|
||||||
|
t.label("⏺ (idle)"sv, {}, { .prefix = LiveDisplay::Gray, .text = LiveDisplay::None });
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
t.counter({
|
||||||
|
{ .label = "Pass"sv, .color = LiveDisplay::Green, .value = m_counts.tests_passed },
|
||||||
|
{ .label = "Fail"sv, .color = LiveDisplay::Red, .value = m_counts.tests_failed },
|
||||||
|
{ .label = "Skipped"sv, .color = LiveDisplay::Gray, .value = m_counts.tests_skipped },
|
||||||
|
{ .label = "XFail"sv, .color = LiveDisplay::Yellow, .value = m_counts.tests_expected_failed },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[&] {
|
||||||
|
t.progress_bar(completed, total);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
inline void TestRunner::run(ReadonlySpan<ByteString> test_globs)
|
inline void TestRunner::run(ReadonlySpan<ByteString> test_globs)
|
||||||
{
|
{
|
||||||
size_t progress_counter = 0;
|
|
||||||
auto test_paths = get_test_paths();
|
auto test_paths = get_test_paths();
|
||||||
|
size_t total_tests = 0;
|
||||||
|
for (auto& path : test_paths) {
|
||||||
|
if (any_of(test_globs, [&](auto& glob) { return path.matches(glob); }))
|
||||||
|
++total_tests;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool live_display_enabled = !m_print_json && stdout_is_tty() && begin_live_display();
|
||||||
|
|
||||||
|
if (live_display_enabled)
|
||||||
|
render_live_display(0, total_tests);
|
||||||
|
|
||||||
|
size_t progress_counter = 0;
|
||||||
for (auto& path : test_paths) {
|
for (auto& path : test_paths) {
|
||||||
if (!any_of(test_globs, [&](auto& glob) { return path.matches(glob); }))
|
if (!any_of(test_globs, [&](auto& glob) { return path.matches(glob); }))
|
||||||
continue;
|
continue;
|
||||||
++progress_counter;
|
++progress_counter;
|
||||||
do_run_single_test(path, progress_counter, test_paths.size());
|
|
||||||
|
if (live_display_enabled) {
|
||||||
|
auto label = LexicalPath::relative_path(path, m_test_root);
|
||||||
|
m_current_test_label = label.has_value() ? label.release_value() : path;
|
||||||
|
render_live_display(progress_counter - 1, total_tests);
|
||||||
|
}
|
||||||
|
|
||||||
|
do_run_single_test(path, progress_counter, total_tests);
|
||||||
|
|
||||||
|
if (live_display_enabled)
|
||||||
|
render_live_display(progress_counter, total_tests);
|
||||||
|
|
||||||
if (m_print_progress)
|
if (m_print_progress)
|
||||||
warn("\033]9;{};{};\033\\", progress_counter, test_paths.size());
|
warn("\033]9;{};{};\033\\", progress_counter, total_tests);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ByteString saved_log_path = m_live_display.log_file_path();
|
||||||
|
m_live_display.end();
|
||||||
|
|
||||||
if (m_print_progress)
|
if (m_print_progress)
|
||||||
warn("\033]9;-1;\033\\");
|
warn("\033]9;-1;\033\\");
|
||||||
|
|
||||||
@@ -112,6 +212,9 @@ inline void TestRunner::run(ReadonlySpan<ByteString> test_globs)
|
|||||||
print_test_results();
|
print_test_results();
|
||||||
else
|
else
|
||||||
print_test_results_as_json();
|
print_test_results_as_json();
|
||||||
|
|
||||||
|
if (live_display_enabled && !saved_log_path.is_empty())
|
||||||
|
outln("Full test output: {}", saved_log_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Modifier {
|
enum Modifier {
|
||||||
@@ -129,6 +232,11 @@ enum Modifier {
|
|||||||
|
|
||||||
inline void print_modifiers(Vector<Modifier> modifiers)
|
inline void print_modifiers(Vector<Modifier> modifiers)
|
||||||
{
|
{
|
||||||
|
// Strip ANSI escapes for non-tty output.
|
||||||
|
#if !defined(AK_OS_WINDOWS)
|
||||||
|
if (!isatty(STDOUT_FILENO))
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
for (auto& modifier : modifiers) {
|
for (auto& modifier : modifiers) {
|
||||||
auto code = [&] {
|
auto code = [&] {
|
||||||
switch (modifier) {
|
switch (modifier) {
|
||||||
|
|||||||
Reference in New Issue
Block a user