2020-07-03 21:36:58 +00:00
|
|
|
|
/*
|
2021-04-22 23:53:07 +00:00
|
|
|
|
* Copyright (c) 2020, Matthew Olsson <mattco@serenityos.org>
|
2021-04-22 20:51:19 +00:00
|
|
|
|
* Copyright (c) 2020-2021, Linus Groh <linusg@serenityos.org>
|
2020-07-03 21:36:58 +00:00
|
|
|
|
*
|
2021-04-22 08:24:48 +00:00
|
|
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
2020-07-03 21:36:58 +00:00
|
|
|
|
*/
|
|
|
|
|
|
2020-11-15 12:11:21 +00:00
|
|
|
|
#include <AK/ByteBuffer.h>
|
2020-07-03 21:36:58 +00:00
|
|
|
|
#include <AK/JsonObject.h>
|
2020-07-05 14:50:08 +00:00
|
|
|
|
#include <AK/JsonValue.h>
|
2020-10-18 20:44:11 +00:00
|
|
|
|
#include <AK/LexicalPath.h>
|
2020-07-06 18:28:20 +00:00
|
|
|
|
#include <AK/QuickSort.h>
|
2020-07-04 20:23:38 +00:00
|
|
|
|
#include <LibCore/ArgsParser.h>
|
2020-07-06 18:28:20 +00:00
|
|
|
|
#include <LibCore/DirIterator.h>
|
2020-07-05 14:50:08 +00:00
|
|
|
|
#include <LibCore/File.h>
|
2020-07-03 21:36:58 +00:00
|
|
|
|
#include <LibJS/Interpreter.h>
|
|
|
|
|
#include <LibJS/Lexer.h>
|
|
|
|
|
#include <LibJS/Parser.h>
|
|
|
|
|
#include <LibJS/Runtime/Array.h>
|
|
|
|
|
#include <LibJS/Runtime/GlobalObject.h>
|
2020-07-04 18:37:50 +00:00
|
|
|
|
#include <LibJS/Runtime/JSONObject.h>
|
2021-02-28 21:09:13 +00:00
|
|
|
|
#include <LibTest/Results.h>
|
2020-08-22 19:31:37 +00:00
|
|
|
|
#include <signal.h>
|
2020-07-03 21:36:58 +00:00
|
|
|
|
#include <stdlib.h>
|
2020-07-05 14:50:08 +00:00
|
|
|
|
#include <sys/time.h>
|
2021-03-12 16:29:37 +00:00
|
|
|
|
#include <unistd.h>
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
#define TOP_LEVEL_TEST_NAME "__$$TOP_LEVEL$$__"
|
|
|
|
|
|
2020-09-20 17:24:44 +00:00
|
|
|
|
RefPtr<JS::VM> vm;
|
|
|
|
|
|
2020-09-08 15:59:13 +00:00
|
|
|
|
static bool collect_on_every_allocation = false;
|
2020-09-09 19:06:11 +00:00
|
|
|
|
static String currently_running_test;
|
2020-09-08 15:59:13 +00:00
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
struct ParserError {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
JS::Parser::Error error;
|
|
|
|
|
String hint;
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
struct JSFileResult {
|
|
|
|
|
String name;
|
|
|
|
|
Optional<ParserError> error {};
|
2020-07-04 20:23:38 +00:00
|
|
|
|
double time_taken { 0 };
|
2020-07-04 19:57:12 +00:00
|
|
|
|
// A failed test takes precedence over a skipped test, which both have
|
|
|
|
|
// precedence over a passed test
|
2021-02-28 21:09:13 +00:00
|
|
|
|
Test::Result most_severe_test_result { Test::Result::Pass };
|
|
|
|
|
Vector<Test::Suite> suites {};
|
2020-07-05 03:23:46 +00:00
|
|
|
|
Vector<String> logged_messages {};
|
2020-07-04 18:37:50 +00:00
|
|
|
|
};
|
|
|
|
|
|
2021-03-17 15:52:26 +00:00
|
|
|
|
class TestRunnerGlobalObject final : public JS::GlobalObject {
|
|
|
|
|
JS_OBJECT(TestRunnerGlobalObject, JS::GlobalObject);
|
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
public:
|
|
|
|
|
TestRunnerGlobalObject();
|
|
|
|
|
virtual ~TestRunnerGlobalObject() override;
|
|
|
|
|
|
2021-03-17 15:52:26 +00:00
|
|
|
|
virtual void initialize_global_object() override;
|
2020-07-06 18:28:20 +00:00
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
JS_DECLARE_NATIVE_FUNCTION(is_strict_mode);
|
2020-11-11 22:12:51 +00:00
|
|
|
|
JS_DECLARE_NATIVE_FUNCTION(can_parse_source);
|
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 20:13:29 +00:00
|
|
|
|
JS_DECLARE_NATIVE_FUNCTION(run_queued_promise_jobs);
|
2020-07-06 18:28:20 +00:00
|
|
|
|
};
|
2020-07-04 18:37:50 +00:00
|
|
|
|
|
|
|
|
|
class TestRunner {
|
|
|
|
|
public:
|
2020-09-09 19:06:11 +00:00
|
|
|
|
static TestRunner* the()
|
|
|
|
|
{
|
|
|
|
|
return s_the;
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-01 17:56:53 +00:00
|
|
|
|
TestRunner(String test_root, bool print_times, bool print_progress)
|
2020-07-04 18:37:50 +00:00
|
|
|
|
: m_test_root(move(test_root))
|
2020-07-04 20:23:38 +00:00
|
|
|
|
, m_print_times(print_times)
|
2021-03-01 17:56:53 +00:00
|
|
|
|
, m_print_progress(print_progress)
|
2020-07-04 18:37:50 +00:00
|
|
|
|
{
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(!s_the);
|
2020-09-09 19:06:11 +00:00
|
|
|
|
s_the = this;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-12 13:13:12 +00:00
|
|
|
|
virtual ~TestRunner() { s_the = nullptr; };
|
2021-04-15 17:43:29 +00:00
|
|
|
|
|
2021-05-12 13:13:12 +00:00
|
|
|
|
Test::Counts run();
|
2020-07-04 18:37:50 +00:00
|
|
|
|
|
2021-03-23 22:20:45 +00:00
|
|
|
|
const Test::Counts& counts() const { return m_counts; }
|
2020-09-09 19:06:11 +00:00
|
|
|
|
|
2021-03-01 17:56:53 +00:00
|
|
|
|
bool is_printing_progress() const { return m_print_progress; }
|
|
|
|
|
|
2020-10-18 20:44:11 +00:00
|
|
|
|
protected:
|
2020-09-09 19:06:11 +00:00
|
|
|
|
static TestRunner* s_the;
|
|
|
|
|
|
2020-10-18 20:44:11 +00:00
|
|
|
|
virtual Vector<String> get_test_paths() const;
|
|
|
|
|
virtual JSFileResult run_file_test(const String& test_path);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
void print_file_result(const JSFileResult& file_result) const;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
void print_test_results() const;
|
|
|
|
|
|
|
|
|
|
String m_test_root;
|
2020-07-04 20:23:38 +00:00
|
|
|
|
bool m_print_times;
|
2021-03-01 17:56:53 +00:00
|
|
|
|
bool m_print_progress;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
|
2020-07-04 20:23:38 +00:00
|
|
|
|
double m_total_elapsed_time_in_ms { 0 };
|
2021-03-23 22:20:45 +00:00
|
|
|
|
Test::Counts m_counts;
|
2020-07-04 20:45:20 +00:00
|
|
|
|
|
|
|
|
|
RefPtr<JS::Program> m_test_program;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
};
|
|
|
|
|
|
2020-09-09 19:06:11 +00:00
|
|
|
|
TestRunner* TestRunner::s_the = nullptr;
|
|
|
|
|
|
2020-07-05 17:47:40 +00:00
|
|
|
|
TestRunnerGlobalObject::TestRunnerGlobalObject()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TestRunnerGlobalObject::~TestRunnerGlobalObject()
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 15:52:26 +00:00
|
|
|
|
void TestRunnerGlobalObject::initialize_global_object()
|
2020-07-05 17:47:40 +00:00
|
|
|
|
{
|
2021-03-17 15:52:26 +00:00
|
|
|
|
Base::initialize_global_object();
|
2020-10-13 21:49:19 +00:00
|
|
|
|
static FlyString global_property_name { "global" };
|
|
|
|
|
static FlyString is_strict_mode_property_name { "isStrictMode" };
|
2020-11-11 22:12:51 +00:00
|
|
|
|
static FlyString can_parse_source_property_name { "canParseSource" };
|
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 20:13:29 +00:00
|
|
|
|
static FlyString run_queued_promise_jobs_property_name { "runQueuedPromiseJobs" };
|
2020-10-13 21:49:19 +00:00
|
|
|
|
define_property(global_property_name, this, JS::Attribute::Enumerable);
|
|
|
|
|
define_native_function(is_strict_mode_property_name, is_strict_mode);
|
2020-11-11 22:12:51 +00:00
|
|
|
|
define_native_function(can_parse_source_property_name, can_parse_source);
|
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 20:13:29 +00:00
|
|
|
|
define_native_function(run_queued_promise_jobs_property_name, run_queued_promise_jobs);
|
2020-07-05 17:47:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::is_strict_mode)
|
|
|
|
|
{
|
2020-10-04 11:54:44 +00:00
|
|
|
|
return JS::Value(vm.in_strict_mode());
|
2020-07-05 17:47:40 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-11 22:12:51 +00:00
|
|
|
|
JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::can_parse_source)
|
|
|
|
|
{
|
|
|
|
|
auto source = vm.argument(0).to_string(global_object);
|
|
|
|
|
if (vm.exception())
|
|
|
|
|
return {};
|
|
|
|
|
auto parser = JS::Parser(JS::Lexer(source));
|
|
|
|
|
parser.parse_program();
|
|
|
|
|
return JS::Value(!parser.has_errors());
|
|
|
|
|
}
|
|
|
|
|
|
LibJS: Add initial support for Promises
Almost a year after first working on this, it's finally done: an
implementation of Promises for LibJS! :^)
The core functionality is working and closely following the spec [1].
I mostly took the pseudo code and transformed it into C++ - if you read
and understand it, you will know how the spec implements Promises; and
if you read the spec first, the code will look very familiar.
Implemented functions are:
- Promise() constructor
- Promise.prototype.then()
- Promise.prototype.catch()
- Promise.prototype.finally()
- Promise.resolve()
- Promise.reject()
For the tests I added a new function to test-js's global object,
runQueuedPromiseJobs(), which calls vm.run_queued_promise_jobs().
By design, queued jobs normally only run after the script was fully
executed, making it improssible to test handlers in individual test()
calls by default [2].
Subsequent commits include integrations into LibWeb and js(1) -
pretty-printing, running queued promise jobs when necessary.
This has an unusual amount of dbgln() statements, all hidden behind the
PROMISE_DEBUG flag - I'm leaving them in for now as they've been very
useful while debugging this, things can get quite complex with so many
asynchronously executed functions.
I've not extensively explored use of these APIs for promise-based
functionality in LibWeb (fetch(), Notification.requestPermission()
etc.), but we'll get there in due time.
[1]: https://tc39.es/ecma262/#sec-promise-objects
[2]: https://tc39.es/ecma262/#sec-jobs-and-job-queues
2021-04-01 20:13:29 +00:00
|
|
|
|
JS_DEFINE_NATIVE_FUNCTION(TestRunnerGlobalObject::run_queued_promise_jobs)
|
|
|
|
|
{
|
|
|
|
|
vm.run_queued_promise_jobs();
|
|
|
|
|
return JS::js_undefined();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-22 19:31:37 +00:00
|
|
|
|
static void cleanup_and_exit()
|
|
|
|
|
{
|
|
|
|
|
// Clear the taskbar progress.
|
2021-03-01 17:56:53 +00:00
|
|
|
|
if (TestRunner::the() && TestRunner::the()->is_printing_progress())
|
|
|
|
|
warn("\033]9;-1;\033\\");
|
2020-08-22 19:31:37 +00:00
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_sigabrt(int)
|
|
|
|
|
{
|
2021-01-09 14:09:40 +00:00
|
|
|
|
dbgln("test-js: SIGABRT received, cleaning up.");
|
2020-08-22 19:31:37 +00:00
|
|
|
|
cleanup_and_exit();
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 22:02:25 +00:00
|
|
|
|
static double get_time_in_ms()
|
2020-07-05 17:47:40 +00:00
|
|
|
|
{
|
|
|
|
|
struct timeval tv1;
|
2020-08-27 21:00:00 +00:00
|
|
|
|
auto return_code = gettimeofday(&tv1, nullptr);
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(return_code >= 0);
|
2020-07-05 17:47:40 +00:00
|
|
|
|
return static_cast<double>(tv1.tv_sec) * 1000.0 + static_cast<double>(tv1.tv_usec) / 1000.0;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
template<typename Callback>
|
2020-08-10 22:02:25 +00:00
|
|
|
|
static void iterate_directory_recursively(const String& directory_path, Callback callback)
|
2020-07-06 18:28:20 +00:00
|
|
|
|
{
|
|
|
|
|
Core::DirIterator directory_iterator(directory_path, Core::DirIterator::Flags::SkipDots);
|
|
|
|
|
|
|
|
|
|
while (directory_iterator.has_next()) {
|
2021-04-21 20:16:32 +00:00
|
|
|
|
auto file_path = directory_iterator.next_full_path();
|
2020-07-06 21:46:04 +00:00
|
|
|
|
if (Core::File::is_directory(file_path)) {
|
2020-07-06 18:28:20 +00:00
|
|
|
|
iterate_directory_recursively(file_path, callback);
|
|
|
|
|
} else {
|
|
|
|
|
callback(move(file_path));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 20:44:11 +00:00
|
|
|
|
Vector<String> TestRunner::get_test_paths() const
|
2020-07-06 18:28:20 +00:00
|
|
|
|
{
|
|
|
|
|
Vector<String> paths;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
iterate_directory_recursively(m_test_root, [&](const String& file_path) {
|
2020-07-06 18:28:20 +00:00
|
|
|
|
if (!file_path.ends_with("test-common.js"))
|
|
|
|
|
paths.append(file_path);
|
|
|
|
|
});
|
|
|
|
|
quick_sort(paths);
|
|
|
|
|
return paths;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-12 13:13:12 +00:00
|
|
|
|
Test::Counts TestRunner::run()
|
2020-07-04 18:37:50 +00:00
|
|
|
|
{
|
2020-07-06 23:55:50 +00:00
|
|
|
|
size_t progress_counter = 0;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
auto test_paths = get_test_paths();
|
2020-07-06 23:55:50 +00:00
|
|
|
|
for (auto& path : test_paths) {
|
|
|
|
|
++progress_counter;
|
2020-07-06 18:28:20 +00:00
|
|
|
|
print_file_result(run_file_test(path));
|
2021-03-01 17:56:53 +00:00
|
|
|
|
if (m_print_progress)
|
|
|
|
|
warn("\033]9;{};{};\033\\", progress_counter, test_paths.size());
|
2020-07-06 23:55:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-01 17:56:53 +00:00
|
|
|
|
if (m_print_progress)
|
|
|
|
|
warn("\033]9;-1;\033\\");
|
2020-07-04 18:37:50 +00:00
|
|
|
|
|
|
|
|
|
print_test_results();
|
2021-05-12 13:13:12 +00:00
|
|
|
|
|
|
|
|
|
return m_counts;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 22:02:25 +00:00
|
|
|
|
static Result<NonnullRefPtr<JS::Program>, ParserError> parse_file(const String& file_path)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
2020-07-04 20:45:20 +00:00
|
|
|
|
auto file = Core::File::construct(file_path);
|
2021-05-12 09:26:43 +00:00
|
|
|
|
auto result = file->open(Core::OpenMode::ReadOnly);
|
2020-07-04 17:09:48 +00:00
|
|
|
|
if (!result) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
warnln("Failed to open the following file: \"{}\"", file_path);
|
2020-08-22 19:31:37 +00:00
|
|
|
|
cleanup_and_exit();
|
2020-07-04 17:09:48 +00:00
|
|
|
|
}
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
auto contents = file->read_all();
|
|
|
|
|
String test_file_string(reinterpret_cast<const char*>(contents.data()), contents.size());
|
|
|
|
|
file->close();
|
|
|
|
|
|
|
|
|
|
auto parser = JS::Parser(JS::Lexer(test_file_string));
|
|
|
|
|
auto program = parser.parse_program();
|
|
|
|
|
|
|
|
|
|
if (parser.has_errors()) {
|
|
|
|
|
auto error = parser.errors()[0];
|
2020-07-04 20:45:20 +00:00
|
|
|
|
return Result<NonnullRefPtr<JS::Program>, ParserError>(ParserError { error, error.source_location_hint(test_file_string) });
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 20:45:20 +00:00
|
|
|
|
return Result<NonnullRefPtr<JS::Program>, ParserError>(program);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-10 22:02:25 +00:00
|
|
|
|
static Optional<JsonValue> get_test_results(JS::Interpreter& interpreter)
|
2020-07-04 18:37:50 +00:00
|
|
|
|
{
|
2020-09-27 16:52:42 +00:00
|
|
|
|
auto result = vm->get_variable("__TestResults__", interpreter.global_object());
|
|
|
|
|
auto json_string = JS::JSONObject::stringify_impl(interpreter.global_object(), result, JS::js_undefined(), JS::js_undefined());
|
2020-07-04 18:37:50 +00:00
|
|
|
|
|
|
|
|
|
auto json = JsonValue::from_string(json_string);
|
|
|
|
|
if (!json.has_value())
|
|
|
|
|
return {};
|
|
|
|
|
|
|
|
|
|
return json.value();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JSFileResult TestRunner::run_file_test(const String& test_path)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
2020-09-09 19:06:11 +00:00
|
|
|
|
currently_running_test = test_path;
|
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
double start_time = get_time_in_ms();
|
2020-09-20 17:24:44 +00:00
|
|
|
|
auto interpreter = JS::Interpreter::create<TestRunnerGlobalObject>(*vm);
|
|
|
|
|
|
|
|
|
|
// FIXME: This is a hack while we're refactoring Interpreter/VM stuff.
|
2020-09-21 11:36:32 +00:00
|
|
|
|
JS::VM::InterpreterExecutionScope scope(*interpreter);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-09-08 15:59:13 +00:00
|
|
|
|
interpreter->heap().set_should_collect_on_every_allocation(collect_on_every_allocation);
|
|
|
|
|
|
2020-07-04 20:45:20 +00:00
|
|
|
|
if (!m_test_program) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
auto result = parse_file(String::formatted("{}/test-common.js", m_test_root));
|
2020-07-04 20:45:20 +00:00
|
|
|
|
if (result.is_error()) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
warnln("Unable to parse test-common.js");
|
|
|
|
|
warnln("{}", result.error().error.to_string());
|
|
|
|
|
warnln("{}", result.error().hint);
|
2020-08-25 17:48:32 +00:00
|
|
|
|
cleanup_and_exit();
|
2020-07-04 20:45:20 +00:00
|
|
|
|
}
|
|
|
|
|
m_test_program = result.value();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 20:45:20 +00:00
|
|
|
|
interpreter->run(interpreter->global_object(), *m_test_program);
|
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
auto file_program = parse_file(test_path);
|
2020-07-04 20:45:20 +00:00
|
|
|
|
if (file_program.is_error())
|
|
|
|
|
return { test_path, file_program.error() };
|
|
|
|
|
interpreter->run(interpreter->global_object(), *file_program.value());
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
auto test_json = get_test_results(*interpreter);
|
|
|
|
|
if (!test_json.has_value()) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
warnln("Received malformed JSON from test \"{}\"", test_path);
|
2020-08-22 19:31:37 +00:00
|
|
|
|
cleanup_and_exit();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
JSFileResult file_result { test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1) };
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-05 03:23:46 +00:00
|
|
|
|
// Collect logged messages
|
2020-09-27 13:18:55 +00:00
|
|
|
|
auto& arr = interpreter->vm().get_variable("__UserOutput__", interpreter->global_object()).as_array();
|
2020-07-05 03:23:46 +00:00
|
|
|
|
for (auto& entry : arr.indexed_properties()) {
|
|
|
|
|
auto message = entry.value_and_attributes(&interpreter->global_object()).value;
|
|
|
|
|
file_result.logged_messages.append(message.to_string_without_side_effects());
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
test_json.value().as_object().for_each_member([&](const String& suite_name, const JsonValue& suite_value) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
Test::Suite suite { suite_name };
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(suite_value.is_object());
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
suite_value.as_object().for_each_member([&](const String& test_name, const JsonValue& test_value) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
Test::Case test { test_name, Test::Result::Fail, "" };
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(test_value.is_object());
|
|
|
|
|
VERIFY(test_value.as_object().has("result"));
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
auto result = test_value.as_object().get("result");
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(result.is_string());
|
2020-07-04 18:37:50 +00:00
|
|
|
|
auto result_string = result.as_string();
|
|
|
|
|
if (result_string == "pass") {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test.result = Test::Result::Pass;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
m_counts.tests_passed++;
|
2020-07-04 19:57:12 +00:00
|
|
|
|
} else if (result_string == "fail") {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test.result = Test::Result::Fail;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
m_counts.tests_failed++;
|
2021-02-28 21:09:13 +00:00
|
|
|
|
suite.most_severe_test_result = Test::Result::Fail;
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(test_value.as_object().has("details"));
|
2020-08-22 01:53:54 +00:00
|
|
|
|
auto details = test_value.as_object().get("details");
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY(result.is_string());
|
2020-08-22 01:53:54 +00:00
|
|
|
|
test.details = details.as_string();
|
2020-07-04 19:57:12 +00:00
|
|
|
|
} else {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test.result = Test::Result::Skip;
|
|
|
|
|
if (suite.most_severe_test_result == Test::Result::Pass)
|
|
|
|
|
suite.most_severe_test_result = Test::Result::Skip;
|
2020-07-04 19:57:12 +00:00
|
|
|
|
m_counts.tests_skipped++;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
suite.tests.append(test);
|
|
|
|
|
});
|
|
|
|
|
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (suite.most_severe_test_result == Test::Result::Fail) {
|
2020-07-04 18:37:50 +00:00
|
|
|
|
m_counts.suites_failed++;
|
2021-02-28 21:09:13 +00:00
|
|
|
|
file_result.most_severe_test_result = Test::Result::Fail;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
} else {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (suite.most_severe_test_result == Test::Result::Skip && file_result.most_severe_test_result == Test::Result::Pass)
|
|
|
|
|
file_result.most_severe_test_result = Test::Result::Skip;
|
2020-07-04 18:37:50 +00:00
|
|
|
|
m_counts.suites_passed++;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
file_result.suites.append(suite);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
m_counts.files_total++;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-06 18:28:20 +00:00
|
|
|
|
file_result.time_taken = get_time_in_ms() - start_time;
|
2020-07-04 20:23:38 +00:00
|
|
|
|
m_total_elapsed_time_in_ms += file_result.time_taken;
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
return file_result;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum Modifier {
|
|
|
|
|
BG_RED,
|
|
|
|
|
BG_GREEN,
|
|
|
|
|
FG_RED,
|
|
|
|
|
FG_GREEN,
|
2020-07-04 19:57:12 +00:00
|
|
|
|
FG_ORANGE,
|
2020-07-03 21:36:58 +00:00
|
|
|
|
FG_GRAY,
|
|
|
|
|
FG_BLACK,
|
|
|
|
|
FG_BOLD,
|
2020-07-04 20:23:38 +00:00
|
|
|
|
ITALIC,
|
2020-07-03 21:36:58 +00:00
|
|
|
|
CLEAR,
|
|
|
|
|
};
|
|
|
|
|
|
2020-08-10 22:02:25 +00:00
|
|
|
|
static void print_modifiers(Vector<Modifier> modifiers)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
|
|
|
|
for (auto& modifier : modifiers) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
auto code = [&] {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
switch (modifier) {
|
|
|
|
|
case BG_RED:
|
|
|
|
|
return "\033[48;2;255;0;102m";
|
|
|
|
|
case BG_GREEN:
|
|
|
|
|
return "\033[48;2;102;255;0m";
|
|
|
|
|
case FG_RED:
|
|
|
|
|
return "\033[38;2;255;0;102m";
|
|
|
|
|
case FG_GREEN:
|
|
|
|
|
return "\033[38;2;102;255;0m";
|
2020-07-04 19:57:12 +00:00
|
|
|
|
case FG_ORANGE:
|
|
|
|
|
return "\033[38;2;255;102;0m";
|
2020-07-03 21:36:58 +00:00
|
|
|
|
case FG_GRAY:
|
|
|
|
|
return "\033[38;2;135;139;148m";
|
|
|
|
|
case FG_BLACK:
|
|
|
|
|
return "\033[30m";
|
|
|
|
|
case FG_BOLD:
|
|
|
|
|
return "\033[1m";
|
2020-07-04 20:23:38 +00:00
|
|
|
|
case ITALIC:
|
|
|
|
|
return "\033[3m";
|
2020-07-03 21:36:58 +00:00
|
|
|
|
case CLEAR:
|
|
|
|
|
return "\033[0m";
|
|
|
|
|
}
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY_NOT_REACHED();
|
2020-12-03 00:10:19 +00:00
|
|
|
|
}();
|
|
|
|
|
out("{}", code);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 20:23:38 +00:00
|
|
|
|
void TestRunner::print_file_result(const JSFileResult& file_result) const
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (file_result.most_severe_test_result == Test::Result::Fail || file_result.error.has_value()) {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ BG_RED, FG_BLACK, FG_BOLD });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" FAIL ");
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
} else {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (m_print_times || file_result.most_severe_test_result != Test::Result::Pass) {
|
2020-07-05 14:50:08 +00:00
|
|
|
|
print_modifiers({ BG_GREEN, FG_BLACK, FG_BOLD });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" PASS ");
|
2020-07-05 14:50:08 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
} else {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" {}", file_result.name);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
|
|
|
|
|
if (m_print_times) {
|
|
|
|
|
print_modifiers({ CLEAR, ITALIC, FG_GRAY });
|
|
|
|
|
if (file_result.time_taken < 1000) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ({}ms)", static_cast<int>(file_result.time_taken));
|
2020-07-04 20:23:38 +00:00
|
|
|
|
} else {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ({:3}s)", file_result.time_taken / 1000.0);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
}
|
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
} else {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln();
|
2020-07-04 20:23:38 +00:00
|
|
|
|
}
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-07-05 03:23:46 +00:00
|
|
|
|
if (!file_result.logged_messages.is_empty()) {
|
|
|
|
|
print_modifiers({ FG_GRAY, FG_BOLD });
|
|
|
|
|
#ifdef __serenity__
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ℹ Console output:");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#else
|
|
|
|
|
// This emoji has a second invisible byte after it. The one above does not
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ℹ️ Console output:");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#endif
|
|
|
|
|
print_modifiers({ CLEAR, FG_GRAY });
|
|
|
|
|
for (auto& message : file_result.logged_messages)
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" {}", message);
|
2020-07-05 03:23:46 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (file_result.error.has_value()) {
|
|
|
|
|
auto test_error = file_result.error.value();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
print_modifiers({ FG_RED });
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#ifdef __serenity__
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ❌ The file failed to parse");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#else
|
|
|
|
|
// No invisible byte here, but the spacing still needs to be altered on the host
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" ❌ The file failed to parse");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#endif
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ FG_GRAY });
|
|
|
|
|
for (auto& message : test_error.hint.split('\n', true)) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" {}", message);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
print_modifiers({ FG_RED });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln(" {}", test_error.error.to_string());
|
|
|
|
|
outln();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (file_result.most_severe_test_result != Test::Result::Pass) {
|
2020-07-04 18:37:50 +00:00
|
|
|
|
for (auto& suite : file_result.suites) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (suite.most_severe_test_result == Test::Result::Pass)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
2021-02-28 21:09:13 +00:00
|
|
|
|
bool failed = suite.most_severe_test_result == Test::Result::Fail;
|
2020-07-04 19:57:12 +00:00
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
print_modifiers({ FG_GRAY, FG_BOLD });
|
2020-07-04 19:57:12 +00:00
|
|
|
|
|
|
|
|
|
if (failed) {
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#ifdef __serenity__
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" ❌ Suite: ");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#else
|
|
|
|
|
// No invisible byte here, but the spacing still needs to be altered on the host
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" ❌ Suite: ");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#endif
|
2020-07-04 19:57:12 +00:00
|
|
|
|
} else {
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#ifdef __serenity__
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" ⚠ Suite: ");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#else
|
|
|
|
|
// This emoji has a second invisible byte after it. The one above does not
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" ⚠️ Suite: ");
|
2020-07-05 03:23:46 +00:00
|
|
|
|
#endif
|
2020-07-04 19:57:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
print_modifiers({ CLEAR, FG_GRAY });
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (suite.name == TOP_LEVEL_TEST_NAME) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("<top-level>");
|
2020-07-04 18:37:50 +00:00
|
|
|
|
} else {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{}", suite.name);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
2020-07-04 18:37:50 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
|
|
|
|
for (auto& test : suite.tests) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (test.result == Test::Result::Pass)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
continue;
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
print_modifiers({ FG_GRAY, FG_BOLD });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out(" Test: ");
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (test.result == Test::Result::Fail) {
|
2020-07-04 19:57:12 +00:00
|
|
|
|
print_modifiers({ CLEAR, FG_RED });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{} (failed):", test.name);
|
|
|
|
|
outln(" {}", test.details);
|
2020-07-04 19:57:12 +00:00
|
|
|
|
} else {
|
|
|
|
|
print_modifiers({ CLEAR, FG_ORANGE });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{} (skipped)", test.name);
|
2020-07-04 19:57:12 +00:00
|
|
|
|
}
|
2020-07-04 18:37:50 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 18:37:50 +00:00
|
|
|
|
void TestRunner::print_test_results() const
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("\nTest Suites: ");
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (m_counts.suites_failed) {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ FG_RED });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("{} failed, ", m_counts.suites_failed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
}
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (m_counts.suites_passed) {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ FG_GREEN });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("{} passed, ", m_counts.suites_passed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
}
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{} total", m_counts.suites_failed + m_counts.suites_passed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("Tests: ");
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (m_counts.tests_failed) {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ FG_RED });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("{} failed, ", m_counts.tests_failed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
}
|
2020-07-04 19:57:12 +00:00
|
|
|
|
if (m_counts.tests_skipped) {
|
|
|
|
|
print_modifiers({ FG_ORANGE });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("{} skipped, ", m_counts.tests_skipped);
|
2020-07-04 19:57:12 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
}
|
2020-07-04 18:37:50 +00:00
|
|
|
|
if (m_counts.tests_passed) {
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ FG_GREEN });
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("{} passed, ", m_counts.tests_passed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
print_modifiers({ CLEAR });
|
|
|
|
|
}
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{} total", m_counts.tests_failed + m_counts.tests_skipped + m_counts.tests_passed);
|
2020-07-03 21:36:58 +00:00
|
|
|
|
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("Files: {} total", m_counts.files_total);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
|
2020-12-03 00:10:19 +00:00
|
|
|
|
out("Time: ");
|
2020-07-04 20:23:38 +00:00
|
|
|
|
if (m_total_elapsed_time_in_ms < 1000.0) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{}ms", static_cast<int>(m_total_elapsed_time_in_ms));
|
2020-07-04 20:23:38 +00:00
|
|
|
|
} else {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln("{:>.3}s", m_total_elapsed_time_in_ms / 1000.0);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
}
|
2020-12-03 00:10:19 +00:00
|
|
|
|
outln();
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-10-18 20:44:11 +00:00
|
|
|
|
class Test262ParserTestRunner final : public TestRunner {
|
|
|
|
|
public:
|
|
|
|
|
using TestRunner::TestRunner;
|
|
|
|
|
|
|
|
|
|
private:
|
|
|
|
|
virtual Vector<String> get_test_paths() const override;
|
|
|
|
|
virtual JSFileResult run_file_test(const String& test_path) override;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Vector<String> Test262ParserTestRunner::get_test_paths() const
|
|
|
|
|
{
|
|
|
|
|
Vector<String> paths;
|
|
|
|
|
iterate_directory_recursively(m_test_root, [&](const String& file_path) {
|
|
|
|
|
auto dirname = LexicalPath(file_path).dirname();
|
|
|
|
|
if (dirname.ends_with("early") || dirname.ends_with("fail") || dirname.ends_with("pass") || dirname.ends_with("pass-explicit"))
|
|
|
|
|
paths.append(file_path);
|
|
|
|
|
});
|
|
|
|
|
quick_sort(paths);
|
|
|
|
|
return paths;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
JSFileResult Test262ParserTestRunner::run_file_test(const String& test_path)
|
|
|
|
|
{
|
|
|
|
|
currently_running_test = test_path;
|
|
|
|
|
|
|
|
|
|
auto dirname = LexicalPath(test_path).dirname();
|
|
|
|
|
bool expecting_file_to_parse;
|
|
|
|
|
if (dirname.ends_with("early") || dirname.ends_with("fail")) {
|
|
|
|
|
expecting_file_to_parse = false;
|
|
|
|
|
} else if (dirname.ends_with("pass") || dirname.ends_with("pass-explicit")) {
|
|
|
|
|
expecting_file_to_parse = true;
|
|
|
|
|
} else {
|
2021-02-23 19:42:32 +00:00
|
|
|
|
VERIFY_NOT_REACHED();
|
2020-10-18 20:44:11 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
auto start_time = get_time_in_ms();
|
|
|
|
|
String details = "";
|
2021-02-28 21:09:13 +00:00
|
|
|
|
Test::Result test_result;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
if (test_path.ends_with(".module.js")) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test_result = Test::Result::Skip;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
m_counts.tests_skipped++;
|
|
|
|
|
m_counts.suites_passed++;
|
|
|
|
|
} else {
|
|
|
|
|
auto parse_result = parse_file(test_path);
|
|
|
|
|
if (expecting_file_to_parse) {
|
|
|
|
|
if (!parse_result.is_error()) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test_result = Test::Result::Pass;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
} else {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test_result = Test::Result::Fail;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
details = parse_result.error().error.to_string();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (parse_result.is_error()) {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test_result = Test::Result::Pass;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
} else {
|
2021-02-28 21:09:13 +00:00
|
|
|
|
test_result = Test::Result::Fail;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
details = "File was expected to produce a parser error but didn't";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// test262-parser-tests doesn't have "suites" and "tests" in the usual sense, it just has files
|
|
|
|
|
// and an expectation whether they should parse or not. We add one suite with one test nonetheless:
|
|
|
|
|
//
|
|
|
|
|
// - This makes interpreting skipped test easier as their file is shown as "PASS"
|
|
|
|
|
// - That way we can show additional information such as "file parsed but shouldn't have" or
|
|
|
|
|
// parser errors for files that should parse respectively
|
|
|
|
|
|
2021-02-28 21:09:13 +00:00
|
|
|
|
Test::Case test { expecting_file_to_parse ? "file should parse" : "file should not parse", test_result, details };
|
|
|
|
|
Test::Suite suite { "Parse file", test_result, { test } };
|
2020-10-18 20:44:11 +00:00
|
|
|
|
JSFileResult file_result {
|
|
|
|
|
test_path.substring(m_test_root.length() + 1, test_path.length() - m_test_root.length() - 1),
|
|
|
|
|
{},
|
|
|
|
|
get_time_in_ms() - start_time,
|
|
|
|
|
test_result,
|
|
|
|
|
{ suite }
|
|
|
|
|
};
|
|
|
|
|
|
2021-02-28 21:09:13 +00:00
|
|
|
|
if (test_result == Test::Result::Fail) {
|
2020-10-18 20:44:11 +00:00
|
|
|
|
m_counts.tests_failed++;
|
|
|
|
|
m_counts.suites_failed++;
|
|
|
|
|
} else {
|
|
|
|
|
m_counts.tests_passed++;
|
|
|
|
|
m_counts.suites_passed++;
|
|
|
|
|
}
|
|
|
|
|
m_counts.files_total++;
|
|
|
|
|
m_total_elapsed_time_in_ms += file_result.time_taken;
|
|
|
|
|
|
|
|
|
|
return file_result;
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-04 20:23:38 +00:00
|
|
|
|
int main(int argc, char** argv)
|
2020-07-03 21:36:58 +00:00
|
|
|
|
{
|
2020-08-22 19:31:37 +00:00
|
|
|
|
struct sigaction act;
|
|
|
|
|
memset(&act, 0, sizeof(act));
|
|
|
|
|
act.sa_flags = SA_NOCLDWAIT;
|
|
|
|
|
act.sa_handler = handle_sigabrt;
|
|
|
|
|
int rc = sigaction(SIGABRT, &act, nullptr);
|
|
|
|
|
if (rc < 0) {
|
|
|
|
|
perror("sigaction");
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-09 19:06:11 +00:00
|
|
|
|
#ifdef SIGINFO
|
|
|
|
|
signal(SIGINFO, [](int) {
|
|
|
|
|
static char buffer[4096];
|
|
|
|
|
auto& counts = TestRunner::the()->counts();
|
|
|
|
|
int len = snprintf(buffer, sizeof(buffer), "Pass: %d, Fail: %d, Skip: %d\nCurrent test: %s\n", counts.tests_passed, counts.tests_failed, counts.tests_skipped, currently_running_test.characters());
|
|
|
|
|
write(STDOUT_FILENO, buffer, len);
|
|
|
|
|
});
|
|
|
|
|
#endif
|
|
|
|
|
|
2020-10-18 18:11:57 +00:00
|
|
|
|
bool print_times = false;
|
2021-03-01 17:56:53 +00:00
|
|
|
|
bool print_progress =
|
|
|
|
|
#ifdef __serenity__
|
|
|
|
|
true; // Use OSC 9 to print progress
|
|
|
|
|
#else
|
|
|
|
|
false;
|
|
|
|
|
#endif
|
2020-10-18 20:44:11 +00:00
|
|
|
|
bool test262_parser_tests = false;
|
2020-11-26 08:30:56 +00:00
|
|
|
|
const char* specified_test_root = nullptr;
|
2020-10-18 18:11:57 +00:00
|
|
|
|
|
2020-07-04 20:23:38 +00:00
|
|
|
|
Core::ArgsParser args_parser;
|
|
|
|
|
args_parser.add_option(print_times, "Show duration of each test", "show-time", 't');
|
2021-03-01 17:56:53 +00:00
|
|
|
|
args_parser.add_option(Core::ArgsParser::Option {
|
|
|
|
|
.requires_argument = true,
|
|
|
|
|
.help_string = "Show progress with OSC 9 (true, false)",
|
|
|
|
|
.long_name = "show-progress",
|
|
|
|
|
.short_name = 'p',
|
|
|
|
|
.accept_value = [&](auto* str) {
|
|
|
|
|
if (StringView { "true" } == str)
|
|
|
|
|
print_progress = true;
|
|
|
|
|
else if (StringView { "false" } == str)
|
|
|
|
|
print_progress = false;
|
|
|
|
|
else
|
|
|
|
|
return false;
|
|
|
|
|
return true;
|
|
|
|
|
},
|
|
|
|
|
});
|
2020-09-08 15:59:13 +00:00
|
|
|
|
args_parser.add_option(collect_on_every_allocation, "Collect garbage after every allocation", "collect-often", 'g');
|
2020-10-18 20:44:11 +00:00
|
|
|
|
args_parser.add_option(test262_parser_tests, "Run test262 parser tests", "test262-parser-tests", 0);
|
2020-11-26 08:30:56 +00:00
|
|
|
|
args_parser.add_positional_argument(specified_test_root, "Tests root directory", "path", Core::ArgsParser::Required::No);
|
2020-07-04 20:23:38 +00:00
|
|
|
|
args_parser.parse(argc, argv);
|
|
|
|
|
|
2020-10-18 20:44:11 +00:00
|
|
|
|
if (test262_parser_tests) {
|
|
|
|
|
if (collect_on_every_allocation) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
warnln("--collect-often and --test262-parser-tests options must not be used together");
|
2020-10-18 20:44:11 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
2020-11-26 08:30:56 +00:00
|
|
|
|
if (!specified_test_root) {
|
2020-12-03 00:10:19 +00:00
|
|
|
|
warnln("Test root is required with --test262-parser-tests");
|
2020-10-18 20:44:11 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-28 07:57:35 +00:00
|
|
|
|
if (getenv("DISABLE_DBG_OUTPUT")) {
|
2021-01-18 16:25:44 +00:00
|
|
|
|
AK::set_debug_enabled(false);
|
2020-08-28 07:57:35 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-11-26 08:30:56 +00:00
|
|
|
|
String test_root;
|
|
|
|
|
|
2020-12-02 23:51:04 +00:00
|
|
|
|
if (specified_test_root) {
|
|
|
|
|
test_root = String { specified_test_root };
|
|
|
|
|
} else {
|
2020-07-04 18:50:24 +00:00
|
|
|
|
#ifdef __serenity__
|
2020-10-18 18:11:57 +00:00
|
|
|
|
test_root = "/home/anon/js-tests";
|
2020-07-04 18:50:24 +00:00
|
|
|
|
#else
|
2021-04-20 00:51:04 +00:00
|
|
|
|
char* serenity_source_dir = getenv("SERENITY_SOURCE_DIR");
|
|
|
|
|
if (!serenity_source_dir) {
|
|
|
|
|
warnln("No test root given, test-js requires the SERENITY_SOURCE_DIR environment variable to be set");
|
2020-10-18 18:11:57 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
2021-04-20 00:51:04 +00:00
|
|
|
|
test_root = String::formatted("{}/Userland/Libraries/LibJS/Tests", serenity_source_dir);
|
2020-10-18 18:11:57 +00:00
|
|
|
|
#endif
|
|
|
|
|
}
|
|
|
|
|
if (!Core::File::is_directory(test_root)) {
|
2020-11-26 08:30:56 +00:00
|
|
|
|
warnln("Test root is not a directory: {}", test_root);
|
2020-07-04 18:50:24 +00:00
|
|
|
|
return 1;
|
|
|
|
|
}
|
2020-10-18 18:11:57 +00:00
|
|
|
|
|
|
|
|
|
vm = JS::VM::create();
|
|
|
|
|
|
2021-05-12 13:13:12 +00:00
|
|
|
|
Test::Counts result_counts;
|
2020-10-18 20:44:11 +00:00
|
|
|
|
if (test262_parser_tests)
|
2021-05-12 13:13:12 +00:00
|
|
|
|
result_counts = Test262ParserTestRunner(test_root, print_times, print_progress).run();
|
2020-10-18 20:44:11 +00:00
|
|
|
|
else
|
2021-05-12 13:13:12 +00:00
|
|
|
|
result_counts = TestRunner(test_root, print_times, print_progress).run();
|
2020-07-04 18:50:24 +00:00
|
|
|
|
|
2020-09-20 17:24:44 +00:00
|
|
|
|
vm = nullptr;
|
|
|
|
|
|
2021-05-12 13:13:12 +00:00
|
|
|
|
return result_counts.tests_failed > 0 ? 1 : 0;
|
2020-07-03 21:36:58 +00:00
|
|
|
|
}
|