2021-05-18 14:15:12 +00:00
/*
* Copyright ( c ) 2020 , Matthew Olsson < mattco @ serenityos . org >
2022-08-21 13:00:56 +00:00
* Copyright ( c ) 2020 - 2022 , Linus Groh < linusg @ serenityos . org >
2021-05-18 14:15:12 +00:00
* Copyright ( c ) 2021 , Ali Mohammad Pur < mpfard @ serenityos . org >
2021-05-25 18:53:59 +00:00
* Copyright ( c ) 2021 , Andreas Kling < kling @ serenityos . org >
2021-05-18 14:15:12 +00:00
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
# pragma once
# include <AK/ByteBuffer.h>
# include <AK/JsonObject.h>
# include <AK/JsonValue.h>
# include <AK/LexicalPath.h>
# include <AK/QuickSort.h>
# include <AK/Result.h>
# include <AK/Tuple.h>
# include <LibCore/DirIterator.h>
2023-02-09 02:02:46 +00:00
# include <LibCore/File.h>
2021-06-12 03:49:25 +00:00
# include <LibJS/Bytecode/Interpreter.h>
2021-05-18 14:15:12 +00:00
# include <LibJS/Interpreter.h>
# include <LibJS/Lexer.h>
# include <LibJS/Parser.h>
# include <LibJS/Runtime/Array.h>
# include <LibJS/Runtime/GlobalObject.h>
# include <LibJS/Runtime/JSONObject.h>
# include <LibJS/Runtime/TypedArray.h>
2021-06-12 02:37:09 +00:00
# include <LibJS/Runtime/WeakMap.h>
2021-06-09 17:10:47 +00:00
# include <LibJS/Runtime/WeakSet.h>
2021-09-14 18:58:33 +00:00
# include <LibJS/Script.h>
# include <LibJS/SourceTextModule.h>
2021-05-18 14:15:12 +00:00
# include <LibTest/Results.h>
2021-06-27 18:57:35 +00:00
# include <LibTest/TestRunner.h>
2021-05-25 18:53:59 +00:00
# include <fcntl.h>
2021-05-18 14:15:12 +00:00
# include <sys/time.h>
# include <unistd.h>
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-08-11 18:43:30 +00:00
# include <serenity.h>
# endif
2021-05-18 14:15:12 +00:00
# define STRCAT(x, y) __STRCAT(x, y)
# define STRSTRCAT(x, y) __STRSTRCAT(x, y)
# define __STRCAT(x, y) x #y
# define __STRSTRCAT(x, y) x y
// Note: This is a little weird, so here's an explanation:
// If the vararg isn't given, the tuple initializer will simply expand to `fn, ::Test::JS::__testjs_last<1>()`
// and if it _is_ given (say as `A`), the tuple initializer will expand to `fn, ::Test::JS::__testjs_last<1, A>()`, which will end up being evaluated as `A`
// and if multiple args are given, the static_assert will be sad.
# define __TESTJS_REGISTER_GLOBAL_FUNCTION(name, fn, ...) \
struct __TestJS_register_ # # fn { \
static_assert ( \
: : Test : : JS : : __testjs_count ( __VA_ARGS__ ) < = 1 , \
STRCAT ( STRSTRCAT ( STRCAT ( " Expected at most three arguments to TESTJS_GLOBAL_FUNCTION at line " , __LINE__ ) , " , in file " ) , __FILE__ ) ) ; \
__TestJS_register_ # # fn ( ) noexcept \
{ \
: : Test : : JS : : s_exposed_global_functions . set ( \
name , \
{ fn , : : Test : : JS : : __testjs_last < 1 , # # __VA_ARGS__ > ( ) } ) ; \
} \
} __testjs_register_ # # fn { } ;
# define TESTJS_GLOBAL_FUNCTION(function, exposed_name, ...) \
2021-10-19 22:58:04 +00:00
JS_DECLARE_NATIVE_FUNCTION ( function ) ; \
2021-05-18 14:15:12 +00:00
__TESTJS_REGISTER_GLOBAL_FUNCTION ( # exposed_name , function , # # __VA_ARGS__ ) ; \
2021-10-19 22:58:04 +00:00
JS_DEFINE_NATIVE_FUNCTION ( function )
2021-05-18 14:15:12 +00:00
# define TESTJS_MAIN_HOOK() \
struct __TestJS_main_hook { \
__TestJS_main_hook ( ) \
{ \
: : Test : : JS : : g_main_hook = hook ; \
} \
static void hook ( ) ; \
} __testjs_common_register_ # # name { } ; \
void __TestJS_main_hook : : hook ( )
2021-05-30 06:05:44 +00:00
# define TESTJS_PROGRAM_FLAG(flag, help_string, long_name, short_name) \
bool flag { false } ; \
struct __TestJS_flag_hook_ # # flag { \
__TestJS_flag_hook_ # # flag ( ) \
{ \
: : Test : : JS : : g_extra_args . set ( & ( flag ) , { help_string , long_name , short_name } ) ; \
} ; \
} __testjs_flag_hook_ # # flag ;
2021-05-18 14:15:12 +00:00
# define TEST_ROOT(path) \
2022-12-04 18:02:33 +00:00
DeprecatedString Test : : JS : : g_test_root_fragment = path
# define TESTJS_RUN_FILE_FUNCTION(...) \
struct __TestJS_run_file { \
__TestJS_run_file ( ) \
{ \
: : Test : : JS : : g_run_file = hook ; \
} \
static : : Test : : JS : : IntermediateRunFileResult hook ( DeprecatedString const & , JS : : Interpreter & , JS : : ExecutionContext & ) ; \
} __testjs_common_run_file { } ; \
2021-05-30 06:05:44 +00:00
: : Test : : JS : : IntermediateRunFileResult __TestJS_run_file : : hook ( __VA_ARGS__ )
2021-10-14 15:12:53 +00:00
# define TESTJS_CREATE_INTERPRETER_HOOK(...) \
struct __TestJS_create_interpreter_hook { \
__TestJS_create_interpreter_hook ( ) \
{ \
: : Test : : JS : : g_create_interpreter_hook = hook ; \
} \
static NonnullOwnPtr < JS : : Interpreter > hook ( ) ; \
} __testjs_create_interpreter_hook { } ; \
NonnullOwnPtr < JS : : Interpreter > __TestJS_create_interpreter_hook : : hook ( __VA_ARGS__ )
2021-05-18 14:15:12 +00:00
namespace Test : : JS {
namespace JS = : : JS ;
template < typename . . . Args >
static consteval size_t __testjs_count ( Args . . . ) { return sizeof . . . ( Args ) ; }
template < auto . . . Values >
2022-01-07 05:49:37 +00:00
static consteval size_t __testjs_last ( )
{
Array values { Values . . . } ;
return values [ values . size ( ) - 1U ] ;
}
2021-05-18 14:15:12 +00:00
static constexpr auto TOP_LEVEL_TEST_NAME = " __$$TOP_LEVEL$$__ " ;
extern RefPtr < JS : : VM > g_vm ;
extern bool g_collect_on_every_allocation ;
2021-06-12 03:49:25 +00:00
extern bool g_run_bytecode ;
2022-12-04 18:02:33 +00:00
extern DeprecatedString g_currently_running_test ;
2021-05-18 14:15:12 +00:00
struct FunctionWithLength {
2022-08-22 10:48:08 +00:00
JS : : ThrowCompletionOr < JS : : Value > ( * function ) ( JS : : VM & ) ;
2021-05-18 14:15:12 +00:00
size_t length { 0 } ;
} ;
2022-12-04 18:02:33 +00:00
extern HashMap < DeprecatedString , FunctionWithLength > s_exposed_global_functions ;
extern DeprecatedString g_test_root_fragment ;
extern DeprecatedString g_test_root ;
2021-05-18 14:15:12 +00:00
extern int g_test_argc ;
extern char * * g_test_argv ;
extern Function < void ( ) > g_main_hook ;
2021-10-14 15:12:53 +00:00
extern Function < NonnullOwnPtr < JS : : Interpreter > ( ) > g_create_interpreter_hook ;
2022-12-04 18:02:33 +00:00
extern HashMap < bool * , Tuple < DeprecatedString , DeprecatedString , char > > g_extra_args ;
2021-05-18 14:15:12 +00:00
struct ParserError {
2022-11-23 11:39:23 +00:00
JS : : ParserError error ;
2022-12-04 18:02:33 +00:00
DeprecatedString hint ;
2021-05-18 14:15:12 +00:00
} ;
struct JSFileResult {
2022-12-04 18:02:33 +00:00
DeprecatedString name ;
2021-05-18 14:15:12 +00:00
Optional < ParserError > error { } ;
double time_taken { 0 } ;
// A failed test takes precedence over a skipped test, which both have
// precedence over a passed test
Test : : Result most_severe_test_result { Test : : Result : : Pass } ;
Vector < Test : : Suite > suites { } ;
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > logged_messages { } ;
2021-05-18 14:15:12 +00:00
} ;
2021-05-30 06:05:44 +00:00
enum class RunFileHookResult {
RunAsNormal ,
SkipFile ,
} ;
using IntermediateRunFileResult = AK : : Result < JSFileResult , RunFileHookResult > ;
2022-12-04 18:02:33 +00:00
extern IntermediateRunFileResult ( * g_run_file ) ( DeprecatedString const & , JS : : Interpreter & , JS : : ExecutionContext & ) ;
2021-05-30 06:05:44 +00:00
2021-06-27 18:57:35 +00:00
class TestRunner : public : : Test : : TestRunner {
2021-05-18 14:15:12 +00:00
public :
2022-12-04 18:02:33 +00:00
TestRunner ( DeprecatedString test_root , DeprecatedString common_path , bool print_times , bool print_progress , bool print_json , bool detailed_json )
2022-03-08 03:39:41 +00:00
: : : Test : : TestRunner ( move ( test_root ) , print_times , print_progress , print_json , detailed_json )
2021-06-27 18:57:35 +00:00
, m_common_path ( move ( common_path ) )
2021-05-18 14:15:12 +00:00
{
g_test_root = m_test_root ;
}
virtual ~ TestRunner ( ) = default ;
protected :
2022-12-04 18:02:33 +00:00
virtual void do_run_single_test ( DeprecatedString const & test_path , size_t , size_t ) override ;
virtual Vector < DeprecatedString > get_test_paths ( ) const override ;
virtual JSFileResult run_file_test ( DeprecatedString const & test_path ) ;
2022-04-01 17:58:27 +00:00
void print_file_result ( JSFileResult const & file_result ) const ;
2021-05-18 14:15:12 +00:00
2022-12-04 18:02:33 +00:00
DeprecatedString m_common_path ;
2021-05-18 14:15:12 +00:00
} ;
class TestRunnerGlobalObject final : public JS : : GlobalObject {
JS_OBJECT ( TestRunnerGlobalObject , JS : : GlobalObject ) ;
public :
2022-08-01 18:27:20 +00:00
TestRunnerGlobalObject ( JS : : Realm & realm )
: JS : : GlobalObject ( realm )
{
}
2023-01-28 17:33:35 +00:00
virtual JS : : ThrowCompletionOr < void > initialize ( JS : : Realm & ) override ;
2021-05-18 14:15:12 +00:00
virtual ~ TestRunnerGlobalObject ( ) override = default ;
} ;
2023-01-28 17:33:35 +00:00
inline JS : : ThrowCompletionOr < void > TestRunnerGlobalObject : : initialize ( JS : : Realm & realm )
2021-05-18 14:15:12 +00:00
{
2023-01-28 17:33:35 +00:00
MUST_OR_THROW_OOM ( Base : : initialize ( realm ) ) ;
2022-08-28 13:42:50 +00:00
2021-07-05 23:15:08 +00:00
define_direct_property ( " global " , this , JS : : Attribute : : Enumerable ) ;
2021-05-18 14:15:12 +00:00
for ( auto & entry : s_exposed_global_functions ) {
2021-10-19 22:58:04 +00:00
define_native_function (
2022-08-22 20:47:35 +00:00
realm ,
2022-08-22 10:48:08 +00:00
entry . key , [ fn = entry . value . function ] ( auto & vm ) {
return fn ( vm ) ;
2021-05-18 14:15:12 +00:00
} ,
2021-07-05 23:15:08 +00:00
entry . value . length , JS : : default_attributes ) ;
2021-05-18 14:15:12 +00:00
}
2023-01-28 17:33:35 +00:00
return { } ;
2021-05-18 14:15:12 +00:00
}
2021-09-14 18:58:33 +00:00
inline ByteBuffer load_entire_file ( StringView path )
2021-05-18 14:15:12 +00:00
{
2022-03-10 14:14:21 +00:00
auto try_load_entire_file = [ ] ( StringView const & path ) - > ErrorOr < ByteBuffer > {
2023-02-09 02:02:46 +00:00
auto file = TRY ( Core : : File : : open ( path , Core : : File : : OpenMode : : Read ) ) ;
2022-03-10 14:14:21 +00:00
auto file_size = TRY ( file - > size ( ) ) ;
auto content = TRY ( ByteBuffer : : create_uninitialized ( file_size ) ) ;
2023-03-01 16:24:50 +00:00
TRY ( file - > read_until_filled ( content . bytes ( ) ) ) ;
2022-03-10 14:14:21 +00:00
return content ;
} ;
auto buffer_or_error = try_load_entire_file ( path ) ;
if ( buffer_or_error . is_error ( ) ) {
warnln ( " Failed to open the following file: \" {} \" , error: {} " , path , buffer_or_error . release_error ( ) ) ;
2021-05-18 14:15:12 +00:00
cleanup_and_exit ( ) ;
}
2022-03-10 14:14:21 +00:00
return buffer_or_error . release_value ( ) ;
2021-09-14 18:58:33 +00:00
}
2022-09-05 12:31:25 +00:00
inline AK : : Result < JS : : NonnullGCPtr < JS : : Script > , ParserError > parse_script ( StringView path , JS : : Realm & realm )
2021-09-14 18:58:33 +00:00
{
auto contents = load_entire_file ( path ) ;
auto script_or_errors = JS : : Script : : parse ( contents , realm , path ) ;
if ( script_or_errors . is_error ( ) ) {
auto errors = script_or_errors . release_error ( ) ;
return ParserError { errors [ 0 ] , errors [ 0 ] . source_location_hint ( contents ) } ;
}
return script_or_errors . release_value ( ) ;
}
2021-05-18 14:15:12 +00:00
2022-09-05 12:31:25 +00:00
inline AK : : Result < JS : : NonnullGCPtr < JS : : SourceTextModule > , ParserError > parse_module ( StringView path , JS : : Realm & realm )
2021-09-14 18:58:33 +00:00
{
auto contents = load_entire_file ( path ) ;
auto script_or_errors = JS : : SourceTextModule : : parse ( contents , realm , path ) ;
2021-05-18 14:15:12 +00:00
2021-09-14 18:58:33 +00:00
if ( script_or_errors . is_error ( ) ) {
auto errors = script_or_errors . release_error ( ) ;
return ParserError { errors [ 0 ] , errors [ 0 ] . source_location_hint ( contents ) } ;
2021-05-18 14:15:12 +00:00
}
2021-09-14 18:58:33 +00:00
return script_or_errors . release_value ( ) ;
2021-05-18 14:15:12 +00:00
}
2022-01-04 21:39:52 +00:00
inline ErrorOr < JsonValue > get_test_results ( JS : : Interpreter & interpreter )
2021-05-18 14:15:12 +00:00
{
2022-08-22 18:00:49 +00:00
auto results = MUST ( interpreter . realm ( ) . global_object ( ) . get ( " __TestResults__ " ) ) ;
2022-08-21 16:41:49 +00:00
auto json_string = MUST ( JS : : JSONObject : : stringify_impl ( * g_vm , results , JS : : js_undefined ( ) , JS : : js_undefined ( ) ) ) ;
2021-05-18 14:15:12 +00:00
2022-01-04 21:39:52 +00:00
return JsonValue : : from_string ( json_string ) ;
2021-05-18 14:15:12 +00:00
}
2022-12-04 18:02:33 +00:00
inline void TestRunner : : do_run_single_test ( DeprecatedString const & test_path , size_t , size_t )
2021-06-27 18:57:35 +00:00
{
auto file_result = run_file_test ( test_path ) ;
if ( ! m_print_json )
print_file_result ( file_result ) ;
2022-03-08 03:39:41 +00:00
if ( needs_detailed_suites ( ) )
ensure_suites ( ) . extend ( file_result . suites ) ;
2021-06-27 18:57:35 +00:00
}
2022-12-04 18:02:33 +00:00
inline Vector < DeprecatedString > TestRunner : : get_test_paths ( ) const
2021-06-27 18:57:35 +00:00
{
2022-12-04 18:02:33 +00:00
Vector < DeprecatedString > paths ;
iterate_directory_recursively ( m_test_root , [ & ] ( DeprecatedString const & file_path ) {
2022-07-11 17:32:29 +00:00
if ( ! file_path . ends_with ( " .js " sv ) )
2021-06-27 18:57:35 +00:00
return ;
2022-07-11 17:32:29 +00:00
if ( ! file_path . ends_with ( " test-common.js " sv ) )
2021-06-27 18:57:35 +00:00
paths . append ( file_path ) ;
} ) ;
quick_sort ( paths ) ;
return paths ;
}
2022-12-04 18:02:33 +00:00
inline JSFileResult TestRunner : : run_file_test ( DeprecatedString const & test_path )
2021-05-18 14:15:12 +00:00
{
g_currently_running_test = test_path ;
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-08-11 18:43:30 +00:00
auto string_id = perf_register_string ( test_path . characters ( ) , test_path . length ( ) ) ;
perf_event ( PERF_EVENT_SIGNPOST , string_id , 0 ) ;
# endif
2021-05-18 14:15:12 +00:00
double start_time = get_time_in_ms ( ) ;
auto interpreter = JS : : Interpreter : : create < TestRunnerGlobalObject > ( * g_vm ) ;
2022-01-16 12:16:04 +00:00
// Since g_vm is reused for each new interpreter, Interpreter::create will end up pushing multiple
// global execution contexts onto the VM's execution context stack. To prevent this, we immediately
// pop the global execution context off the execution context stack and manually handle pushing
// and popping it. Since the global execution context should be the only thing on the stack
// at interpreter creation, let's assert there is only one.
VERIFY ( g_vm - > execution_context_stack ( ) . size ( ) = = 1 ) ;
auto & global_execution_context = * g_vm - > execution_context_stack ( ) . take_first ( ) ;
2021-05-18 14:15:12 +00:00
// FIXME: This is a hack while we're refactoring Interpreter/VM stuff.
JS : : VM : : InterpreterExecutionScope scope ( * interpreter ) ;
interpreter - > heap ( ) . set_should_collect_on_every_allocation ( g_collect_on_every_allocation ) ;
2021-10-02 14:35:55 +00:00
2021-05-30 06:05:44 +00:00
if ( g_run_file ) {
2022-02-22 04:19:04 +00:00
auto result = g_run_file ( test_path , * interpreter , global_execution_context ) ;
2021-05-30 06:05:44 +00:00
if ( result . is_error ( ) & & result . error ( ) = = RunFileHookResult : : SkipFile ) {
return {
test_path ,
{ } ,
0 ,
Test : : Result : : Skip ,
{ } ,
{ }
} ;
}
if ( ! result . is_error ( ) ) {
auto value = result . release_value ( ) ;
for ( auto & suite : value . suites ) {
if ( suite . most_severe_test_result = = Result : : Pass )
m_counts . suites_passed + + ;
else if ( suite . most_severe_test_result = = Result : : Fail )
m_counts . suites_failed + + ;
for ( auto & test : suite . tests ) {
if ( test . result = = Result : : Pass )
m_counts . tests_passed + + ;
else if ( test . result = = Result : : Fail )
m_counts . tests_failed + + ;
else if ( test . result = = Result : : Skip )
m_counts . tests_skipped + + ;
}
}
+ + m_counts . files_total ;
m_total_elapsed_time_in_ms + = value . time_taken ;
return value ;
}
}
2022-01-16 12:16:04 +00:00
// FIXME: Since a new interpreter is created every time with a new realm, we no longer cache the test-common.js file as scripts are parsed for the current realm only.
// Find a way to cache this.
auto result = parse_script ( m_common_path , interpreter - > realm ( ) ) ;
if ( result . is_error ( ) ) {
warnln ( " Unable to parse test-common.js " ) ;
2022-12-06 01:12:49 +00:00
warnln ( " {} " , result . error ( ) . error . to_deprecated_string ( ) ) ;
2022-01-16 12:16:04 +00:00
warnln ( " {} " , result . error ( ) . hint ) ;
cleanup_and_exit ( ) ;
2021-05-18 14:15:12 +00:00
}
2022-01-16 12:16:04 +00:00
auto test_script = result . release_value ( ) ;
2021-05-18 14:15:12 +00:00
2021-06-12 03:49:25 +00:00
if ( g_run_bytecode ) {
2022-02-12 16:24:08 +00:00
auto executable = MUST ( JS : : Bytecode : : Generator : : generate ( test_script - > parse_node ( ) ) ) ;
2022-01-31 12:25:39 +00:00
executable - > name = test_path ;
2021-10-24 11:34:46 +00:00
if ( JS : : Bytecode : : g_dump_bytecode )
2022-01-31 12:25:39 +00:00
executable - > dump ( ) ;
2022-08-22 18:35:23 +00:00
JS : : Bytecode : : Interpreter bytecode_interpreter ( interpreter - > realm ( ) ) ;
2022-01-31 12:25:39 +00:00
MUST ( bytecode_interpreter . run ( * executable ) ) ;
2021-06-12 03:49:25 +00:00
} else {
2022-03-17 22:40:17 +00:00
g_vm - > push_execution_context ( global_execution_context ) ;
2022-01-16 12:16:04 +00:00
MUST ( interpreter - > run ( * test_script ) ) ;
g_vm - > pop_execution_context ( ) ;
2021-06-12 03:49:25 +00:00
}
2021-09-14 18:58:33 +00:00
auto file_script = parse_script ( test_path , interpreter - > realm ( ) ) ;
2022-11-16 23:50:01 +00:00
JS : : ThrowCompletionOr < JS : : Value > top_level_result { JS : : js_undefined ( ) } ;
2021-09-14 18:58:33 +00:00
if ( file_script . is_error ( ) )
return { test_path , file_script . error ( ) } ;
2021-06-12 03:49:25 +00:00
if ( g_run_bytecode ) {
2022-02-12 16:24:08 +00:00
auto executable_result = JS : : Bytecode : : Generator : : generate ( file_script . value ( ) - > parse_node ( ) ) ;
if ( ! executable_result . is_error ( ) ) {
auto executable = executable_result . release_value ( ) ;
executable - > name = test_path ;
if ( JS : : Bytecode : : g_dump_bytecode )
executable - > dump ( ) ;
2022-08-22 18:35:23 +00:00
JS : : Bytecode : : Interpreter bytecode_interpreter ( interpreter - > realm ( ) ) ;
2022-11-16 23:50:01 +00:00
top_level_result = bytecode_interpreter . run ( * executable ) ;
2022-02-12 16:24:08 +00:00
}
2021-06-12 03:49:25 +00:00
} else {
2022-03-17 22:40:17 +00:00
g_vm - > push_execution_context ( global_execution_context ) ;
2022-11-16 23:50:01 +00:00
top_level_result = interpreter - > run ( file_script . value ( ) ) ;
2022-01-16 12:16:04 +00:00
g_vm - > pop_execution_context ( ) ;
2021-06-12 03:49:25 +00:00
}
2021-05-18 14:15:12 +00:00
2022-08-21 13:00:56 +00:00
g_vm - > push_execution_context ( global_execution_context ) ;
2021-05-18 14:15:12 +00:00
auto test_json = get_test_results ( * interpreter ) ;
2022-08-21 13:00:56 +00:00
g_vm - > pop_execution_context ( ) ;
2022-01-04 21:39:52 +00:00
if ( test_json . is_error ( ) ) {
2021-05-18 14:15:12 +00:00
warnln ( " Received malformed JSON from test \" {} \" " , test_path ) ;
cleanup_and_exit ( ) ;
}
JSFileResult file_result { test_path . substring ( m_test_root . length ( ) + 1 , test_path . length ( ) - m_test_root . length ( ) - 1 ) } ;
// Collect logged messages
2022-08-22 18:00:49 +00:00
auto user_output = MUST ( interpreter - > realm ( ) . global_object ( ) . get ( " __UserOutput__ " ) ) ;
2021-09-18 14:31:50 +00:00
auto & arr = user_output . as_array ( ) ;
2021-05-18 14:15:12 +00:00
for ( auto & entry : arr . indexed_properties ( ) ) {
2021-10-02 22:52:27 +00:00
auto message = MUST ( arr . get ( entry . index ( ) ) ) ;
2023-02-13 02:54:02 +00:00
file_result . logged_messages . append ( message . to_string_without_side_effects ( ) . release_value_but_fixme_should_propagate_errors ( ) . to_deprecated_string ( ) ) ;
2021-05-18 14:15:12 +00:00
}
2022-12-04 18:02:33 +00:00
test_json . value ( ) . as_object ( ) . for_each_member ( [ & ] ( DeprecatedString const & suite_name , JsonValue const & suite_value ) {
2022-03-08 03:39:41 +00:00
Test : : Suite suite { test_path , suite_name } ;
2021-05-18 14:15:12 +00:00
VERIFY ( suite_value . is_object ( ) ) ;
2022-12-04 18:02:33 +00:00
suite_value . as_object ( ) . for_each_member ( [ & ] ( const DeprecatedString & test_name , const JsonValue & test_value ) {
2022-03-08 03:39:41 +00:00
Test : : Case test { test_name , Test : : Result : : Fail , " " , 0 } ;
2021-05-18 14:15:12 +00:00
VERIFY ( test_value . is_object ( ) ) ;
2022-07-11 17:32:29 +00:00
VERIFY ( test_value . as_object ( ) . has ( " result " sv ) ) ;
2021-05-18 14:15:12 +00:00
2022-12-21 17:28:11 +00:00
auto result = test_value . as_object ( ) . get_deprecated_string ( " result " sv ) ;
VERIFY ( result . has_value ( ) ) ;
auto result_string = result . value ( ) ;
2021-05-18 14:15:12 +00:00
if ( result_string = = " pass " ) {
test . result = Test : : Result : : Pass ;
m_counts . tests_passed + + ;
} else if ( result_string = = " fail " ) {
test . result = Test : : Result : : Fail ;
m_counts . tests_failed + + ;
suite . most_severe_test_result = Test : : Result : : Fail ;
2022-07-11 17:32:29 +00:00
VERIFY ( test_value . as_object ( ) . has ( " details " sv ) ) ;
2022-12-21 17:28:11 +00:00
auto details = test_value . as_object ( ) . get_deprecated_string ( " details " sv ) ;
VERIFY ( result . has_value ( ) ) ;
test . details = details . value ( ) ;
2021-05-18 14:15:12 +00:00
} else {
test . result = Test : : Result : : Skip ;
if ( suite . most_severe_test_result = = Test : : Result : : Pass )
suite . most_severe_test_result = Test : : Result : : Skip ;
m_counts . tests_skipped + + ;
}
2022-12-21 17:28:11 +00:00
test . duration_us = test_value . as_object ( ) . get_u64 ( " duration " sv ) . value_or ( 0 ) ;
2022-03-08 03:39:41 +00:00
2021-05-18 14:15:12 +00:00
suite . tests . append ( test ) ;
} ) ;
if ( suite . most_severe_test_result = = Test : : Result : : Fail ) {
m_counts . suites_failed + + ;
file_result . most_severe_test_result = Test : : Result : : Fail ;
} else {
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 ;
m_counts . suites_passed + + ;
}
file_result . suites . append ( suite ) ;
} ) ;
2022-11-16 23:50:01 +00:00
if ( top_level_result . is_error ( ) ) {
Test : : Suite suite { test_path , " <top-level> " } ;
suite . most_severe_test_result = Result : : Crashed ;
Test : : Case test_case { " <top-level> " , Test : : Result : : Fail , " " , 0 } ;
auto error = top_level_result . release_error ( ) . release_value ( ) . release_value ( ) ;
if ( error . is_object ( ) ) {
StringBuilder detail_builder ;
auto & error_object = error . as_object ( ) ;
auto name = error_object . get_without_side_effects ( g_vm - > names . name ) . value_or ( JS : : js_undefined ( ) ) ;
auto message = error_object . get_without_side_effects ( g_vm - > names . message ) . value_or ( JS : : js_undefined ( ) ) ;
if ( name . is_accessor ( ) | | message . is_accessor ( ) ) {
2023-02-13 02:54:02 +00:00
detail_builder . append ( error . to_string_without_side_effects ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-11-16 23:50:01 +00:00
} else {
2023-02-13 02:54:02 +00:00
detail_builder . append ( name . to_string_without_side_effects ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-11-16 23:50:01 +00:00
detail_builder . append ( " : " sv ) ;
2023-02-13 02:54:02 +00:00
detail_builder . append ( message . to_string_without_side_effects ( ) . release_value_but_fixme_should_propagate_errors ( ) ) ;
2022-11-16 23:50:01 +00:00
}
if ( is < JS : : Error > ( error_object ) ) {
auto & error_as_error = static_cast < JS : : Error & > ( error_object ) ;
detail_builder . append ( ' \n ' ) ;
2023-02-16 19:09:11 +00:00
detail_builder . append ( error_as_error . stack_string ( * g_vm ) . release_allocated_value_but_fixme_should_propagate_errors ( ) ) ;
2022-11-16 23:50:01 +00:00
}
2022-12-06 01:12:49 +00:00
test_case . details = detail_builder . to_deprecated_string ( ) ;
2022-11-16 23:50:01 +00:00
} else {
2023-02-13 02:54:02 +00:00
test_case . details = error . to_string_without_side_effects ( ) . release_value_but_fixme_should_propagate_errors ( ) . to_deprecated_string ( ) ;
2022-11-16 23:50:01 +00:00
}
suite . tests . append ( move ( test_case ) ) ;
file_result . suites . append ( suite ) ;
m_counts . suites_failed + + ;
file_result . most_severe_test_result = Test : : Result : : Fail ;
}
2021-05-18 14:15:12 +00:00
m_counts . files_total + + ;
file_result . time_taken = get_time_in_ms ( ) - start_time ;
m_total_elapsed_time_in_ms + = file_result . time_taken ;
return file_result ;
}
2022-04-01 17:58:27 +00:00
inline void TestRunner : : print_file_result ( JSFileResult const & file_result ) const
2021-05-18 14:15:12 +00:00
{
if ( file_result . most_severe_test_result = = Test : : Result : : Fail | | file_result . error . has_value ( ) ) {
print_modifiers ( { BG_RED , FG_BLACK , FG_BOLD } ) ;
out ( " FAIL " ) ;
print_modifiers ( { CLEAR } ) ;
} else {
if ( m_print_times | | file_result . most_severe_test_result ! = Test : : Result : : Pass ) {
print_modifiers ( { BG_GREEN , FG_BLACK , FG_BOLD } ) ;
out ( " PASS " ) ;
print_modifiers ( { CLEAR } ) ;
} else {
return ;
}
}
out ( " {} " , file_result . name ) ;
if ( m_print_times ) {
print_modifiers ( { CLEAR , ITALIC , FG_GRAY } ) ;
if ( file_result . time_taken < 1000 ) {
outln ( " ({}ms) " , static_cast < int > ( file_result . time_taken ) ) ;
} else {
outln ( " ({:3}s) " , file_result . time_taken / 1000.0 ) ;
}
print_modifiers ( { CLEAR } ) ;
} else {
outln ( ) ;
}
if ( ! file_result . logged_messages . is_empty ( ) ) {
print_modifiers ( { FG_GRAY , FG_BOLD } ) ;
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-05-18 14:15:12 +00:00
outln ( " ℹ Console output: " ) ;
# else
// This emoji has a second invisible byte after it. The one above does not
outln ( " ℹ ️ Console output: " ) ;
# endif
print_modifiers ( { CLEAR , FG_GRAY } ) ;
for ( auto & message : file_result . logged_messages )
outln ( " {} " , message ) ;
}
if ( file_result . error . has_value ( ) ) {
auto test_error = file_result . error . value ( ) ;
print_modifiers ( { FG_RED } ) ;
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-05-18 14:15:12 +00:00
outln ( " ❌ The file failed to parse " ) ;
# else
// No invisible byte here, but the spacing still needs to be altered on the host
outln ( " ❌ The file failed to parse " ) ;
# endif
outln ( ) ;
print_modifiers ( { FG_GRAY } ) ;
2022-10-22 13:38:21 +00:00
for ( auto & message : test_error . hint . split ( ' \n ' , SplitBehavior : : KeepEmpty ) ) {
2021-05-18 14:15:12 +00:00
outln ( " {} " , message ) ;
}
print_modifiers ( { FG_RED } ) ;
2022-12-06 01:12:49 +00:00
outln ( " {} " , test_error . error . to_deprecated_string ( ) ) ;
2021-05-18 14:15:12 +00:00
outln ( ) ;
return ;
}
if ( file_result . most_severe_test_result ! = Test : : Result : : Pass ) {
for ( auto & suite : file_result . suites ) {
if ( suite . most_severe_test_result = = Test : : Result : : Pass )
continue ;
bool failed = suite . most_severe_test_result = = Test : : Result : : Fail ;
print_modifiers ( { FG_GRAY , FG_BOLD } ) ;
if ( failed ) {
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-05-18 14:15:12 +00:00
out ( " ❌ Suite: " ) ;
# else
// No invisible byte here, but the spacing still needs to be altered on the host
out ( " ❌ Suite: " ) ;
# endif
} else {
2022-10-09 21:23:23 +00:00
# ifdef AK_OS_SERENITY
2021-05-18 14:15:12 +00:00
out ( " ⚠ Suite: " ) ;
# else
// This emoji has a second invisible byte after it. The one above does not
out ( " ⚠️ Suite: " ) ;
# endif
}
print_modifiers ( { CLEAR , FG_GRAY } ) ;
if ( suite . name = = TOP_LEVEL_TEST_NAME ) {
outln ( " <top-level> " ) ;
} else {
outln ( " {} " , suite . name ) ;
}
print_modifiers ( { CLEAR } ) ;
for ( auto & test : suite . tests ) {
if ( test . result = = Test : : Result : : Pass )
continue ;
print_modifiers ( { FG_GRAY , FG_BOLD } ) ;
out ( " Test: " ) ;
if ( test . result = = Test : : Result : : Fail ) {
print_modifiers ( { CLEAR , FG_RED } ) ;
outln ( " {} (failed): " , test . name ) ;
outln ( " {} " , test . details ) ;
} else {
print_modifiers ( { CLEAR , FG_ORANGE } ) ;
outln ( " {} (skipped) " , test . name ) ;
}
print_modifiers ( { CLEAR } ) ;
}
}
}
}
}