2020-01-18 08:38:21 +00:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
* All rights reserved .
*
* Redistribution and use in source and binary forms , with or without
* modification , are permitted provided that the following conditions are met :
*
* 1. Redistributions of source code must retain the above copyright notice , this
* list of conditions and the following disclaimer .
*
* 2. Redistributions in binary form must reproduce the above copyright notice ,
* this list of conditions and the following disclaimer in the documentation
* and / or other materials provided with the distribution .
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS " AS IS "
* AND ANY EXPRESS OR IMPLIED WARRANTIES , INCLUDING , BUT NOT LIMITED TO , THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED . IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT , INDIRECT , INCIDENTAL , SPECIAL , EXEMPLARY , OR CONSEQUENTIAL
* DAMAGES ( INCLUDING , BUT NOT LIMITED TO , PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES ; LOSS OF USE , DATA , OR PROFITS ; OR BUSINESS INTERRUPTION ) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY , WHETHER IN CONTRACT , STRICT LIABILITY ,
* OR TORT ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE , EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE .
*/
2019-07-16 08:08:39 +00:00
# pragma once
2021-02-23 07:33:03 +00:00
# include <AK/Assertions.h>
2021-02-21 23:07:24 +00:00
# include <AK/CheckedFormatString.h>
2019-08-02 07:23:03 +00:00
2020-10-07 12:02:42 +00:00
namespace AK {
template < typename . . . Parameters >
2021-02-21 23:07:24 +00:00
void warnln ( CheckedFormatString < Parameters . . . > & & fmtstr , const Parameters & . . . ) ;
2020-10-07 12:02:42 +00:00
2021-02-28 19:47:48 +00:00
// Declare a helper so that we can call it from VERIFY in included headers
// before defining TestSuite
inline void current_test_case_did_fail ( ) ;
2020-10-07 12:02:42 +00:00
}
2019-08-02 07:23:03 +00:00
2020-10-07 12:02:42 +00:00
using AK : : warnln ;
2021-02-23 19:42:32 +00:00
# undef VERIFY
# define VERIFY(x) \
2020-10-07 12:02:42 +00:00
do { \
2021-02-28 19:47:48 +00:00
if ( ! ( x ) ) { \
2021-02-23 19:42:32 +00:00
: : AK : : warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: VERIFY({}) failed " , __FILE__ , __LINE__ , # x ) ; \
2021-02-28 19:47:48 +00:00
current_test_case_did_fail ( ) ; \
} \
2020-08-25 14:20:52 +00:00
} while ( false )
2019-08-02 07:23:03 +00:00
2021-02-23 19:42:32 +00:00
# undef VERIFY_NOT_REACHED
# define VERIFY_NOT_REACHED() \
2020-10-07 12:02:42 +00:00
do { \
2021-02-23 19:42:32 +00:00
: : AK : : warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: VERIFY_NOT_REACHED() called " , __FILE__ , __LINE__ ) ; \
2020-10-07 12:02:42 +00:00
: : abort ( ) ; \
2020-08-25 14:20:52 +00:00
} while ( false )
2020-08-21 16:52:28 +00:00
2021-02-23 07:33:03 +00:00
# undef TODO
2020-10-07 12:02:42 +00:00
# define TODO() \
do { \
: : AK : : warnln ( stderr , " \033 [31;1mFAIL \033 [0m: {}:{}: TODO() called " , __FILE__ , __LINE__ ) ; \
: : abort ( ) ; \
} while ( false )
2019-08-02 07:23:03 +00:00
2020-10-07 12:02:42 +00:00
# include <AK/Format.h>
2019-09-06 13:34:26 +00:00
# include <AK/Function.h>
# include <AK/NonnullRefPtrVector.h>
# include <AK/String.h>
2020-08-21 16:52:28 +00:00
# include <LibCore/ArgsParser.h>
2021-02-23 07:33:03 +00:00
# include <stdlib.h>
2020-08-20 19:35:15 +00:00
# include <sys/time.h>
2019-07-16 08:08:39 +00:00
namespace AK {
class TestElapsedTimer {
public :
TestElapsedTimer ( ) { restart ( ) ; }
2020-08-20 19:35:15 +00:00
void restart ( ) { gettimeofday ( & m_started , nullptr ) ; }
u64 elapsed_milliseconds ( )
2019-07-16 08:08:39 +00:00
{
2020-08-20 19:35:15 +00:00
struct timeval now ;
gettimeofday ( & now , nullptr ) ;
struct timeval delta ;
timersub ( & now , & m_started , & delta ) ;
return delta . tv_sec * 1000 + delta . tv_usec / 1000 ;
2019-07-16 08:08:39 +00:00
}
private :
2020-08-20 19:35:15 +00:00
struct timeval m_started ;
2019-07-16 08:08:39 +00:00
} ;
2021-02-25 20:10:47 +00:00
using TestFunction = Function < void ( ) > ;
2019-07-16 08:08:39 +00:00
class TestCase : public RefCounted < TestCase > {
public :
TestCase ( const String & name , TestFunction & & fn , bool is_benchmark )
: m_name ( name )
, m_function ( move ( fn ) )
, m_is_benchmark ( is_benchmark )
{
}
bool is_benchmark ( ) const { return m_is_benchmark ; }
const String & name ( ) const { return m_name ; }
const TestFunction & func ( ) const { return m_function ; }
private :
String m_name ;
TestFunction m_function ;
bool m_is_benchmark ;
} ;
class TestSuite {
public :
2019-07-21 09:47:12 +00:00
static TestSuite & the ( )
2019-07-16 08:08:39 +00:00
{
if ( s_global = = nullptr )
s_global = new TestSuite ( ) ;
2019-07-21 09:47:12 +00:00
return * s_global ;
2019-07-16 08:08:39 +00:00
}
2019-07-21 09:49:19 +00:00
static void release ( )
{
2020-08-21 16:52:28 +00:00
if ( s_global )
delete s_global ;
2019-07-21 09:49:19 +00:00
s_global = nullptr ;
}
2021-02-28 19:47:48 +00:00
int run ( const NonnullRefPtrVector < TestCase > & ) ;
int main ( const String & suite_name , int argc , char * * argv ) ;
2019-07-16 08:08:39 +00:00
NonnullRefPtrVector < TestCase > find_cases ( const String & search , bool find_tests , bool find_benchmarks ) ;
void add_case ( const NonnullRefPtr < TestCase > & test_case )
{
m_cases . append ( test_case ) ;
}
2021-02-28 19:47:48 +00:00
void current_test_case_did_fail ( ) { m_current_test_case_passed = false ; }
2019-07-16 08:08:39 +00:00
private :
static TestSuite * s_global ;
NonnullRefPtrVector < TestCase > m_cases ;
2020-08-21 16:52:28 +00:00
u64 m_testtime = 0 ;
u64 m_benchtime = 0 ;
2019-07-16 08:08:39 +00:00
String m_suite_name ;
2021-02-28 19:47:48 +00:00
bool m_current_test_case_passed = true ;
2019-07-16 08:08:39 +00:00
} ;
2021-02-28 19:47:48 +00:00
inline void current_test_case_did_fail ( ) { TestSuite : : the ( ) . current_test_case_did_fail ( ) ; }
int TestSuite : : main ( const String & suite_name , int argc , char * * argv )
2019-07-16 08:08:39 +00:00
{
m_suite_name = suite_name ;
2020-08-21 16:52:28 +00:00
Core : : ArgsParser args_parser ;
bool do_tests_only = false ;
bool do_benchmarks_only = false ;
bool do_list_cases = false ;
const char * search_string = " * " ;
args_parser . add_option ( do_tests_only , " Only run tests. " , " tests " , 0 ) ;
args_parser . add_option ( do_benchmarks_only , " Only run benchmarks. " , " bench " , 0 ) ;
2020-10-02 21:14:37 +00:00
args_parser . add_option ( do_list_cases , " List available test cases. " , " list " , 0 ) ;
2020-08-21 16:52:28 +00:00
args_parser . add_positional_argument ( search_string , " Only run matching cases. " , " pattern " , Core : : ArgsParser : : Required : : No ) ;
args_parser . parse ( argc , argv ) ;
const auto & matching_tests = find_cases ( search_string , ! do_benchmarks_only , ! do_tests_only ) ;
if ( do_list_cases ) {
2020-10-07 12:02:42 +00:00
outln ( " Available cases for {}: " , suite_name ) ;
2020-08-21 16:52:28 +00:00
for ( const auto & test : matching_tests ) {
2020-10-07 12:02:42 +00:00
outln ( " {} " , test . name ( ) ) ;
2019-07-16 08:08:39 +00:00
}
2021-02-28 19:47:48 +00:00
return 0 ;
2019-07-16 08:08:39 +00:00
}
2021-02-28 19:47:48 +00:00
outln ( " Running {} cases out of {}. " , matching_tests . size ( ) , m_cases . size ( ) ) ;
return run ( matching_tests ) ;
2019-07-16 08:08:39 +00:00
}
NonnullRefPtrVector < TestCase > TestSuite : : find_cases ( const String & search , bool find_tests , bool find_benchmarks )
{
NonnullRefPtrVector < TestCase > matches ;
for ( const auto & t : m_cases ) {
2020-02-26 07:25:24 +00:00
if ( ! search . is_empty ( ) & & ! t . name ( ) . matches ( search , CaseSensitivity : : CaseInsensitive ) ) {
2019-07-16 08:08:39 +00:00
continue ;
}
if ( ! find_tests & & ! t . is_benchmark ( ) ) {
continue ;
}
if ( ! find_benchmarks & & t . is_benchmark ( ) ) {
continue ;
}
matches . append ( t ) ;
}
return matches ;
}
2021-02-28 19:47:48 +00:00
int TestSuite : : run ( const NonnullRefPtrVector < TestCase > & tests )
2019-07-16 08:08:39 +00:00
{
2020-08-21 16:52:28 +00:00
size_t test_count = 0 ;
2021-02-28 19:47:48 +00:00
size_t test_failed_count = 0 ;
2020-08-21 16:52:28 +00:00
size_t benchmark_count = 0 ;
2019-07-16 08:08:39 +00:00
TestElapsedTimer global_timer ;
2020-08-21 16:52:28 +00:00
2019-07-16 08:08:39 +00:00
for ( const auto & t : tests ) {
2020-08-21 16:52:28 +00:00
const auto test_type = t . is_benchmark ( ) ? " benchmark " : " test " ;
2020-10-04 13:35:43 +00:00
warnln ( " Running {} '{}'. " , test_type , t . name ( ) ) ;
2021-02-28 19:47:48 +00:00
m_current_test_case_passed = true ;
2020-08-21 16:52:28 +00:00
2019-07-16 08:08:39 +00:00
TestElapsedTimer timer ;
2020-08-20 15:54:04 +00:00
t . func ( ) ( ) ;
2020-08-21 16:52:28 +00:00
const auto time = timer . elapsed_milliseconds ( ) ;
2021-02-28 19:47:48 +00:00
dbgln ( " {} {} '{}' in {}ms " , m_current_test_case_passed ? " Completed " : " Failed " , test_type , t . name ( ) , time ) ;
2020-08-21 16:52:28 +00:00
2019-07-16 08:08:39 +00:00
if ( t . is_benchmark ( ) ) {
m_benchtime + = time ;
benchmark_count + + ;
} else {
m_testtime + = time ;
test_count + + ;
}
2021-02-28 19:47:48 +00:00
if ( ! m_current_test_case_passed ) {
test_failed_count + + ;
}
2019-07-16 08:08:39 +00:00
}
2020-08-21 16:52:28 +00:00
2020-10-07 12:02:42 +00:00
dbgln ( " Finished {} tests and {} benchmarks in {}ms ({}ms tests, {}ms benchmarks, {}ms other). " ,
test_count ,
benchmark_count ,
global_timer . elapsed_milliseconds ( ) ,
m_testtime ,
m_benchtime ,
global_timer . elapsed_milliseconds ( ) - ( m_testtime + m_benchtime ) ) ;
2021-02-28 19:47:48 +00:00
dbgln ( " Out of {} tests, {} passed and {} failed. " , test_count , test_count - test_failed_count , test_failed_count ) ;
return ( int ) test_failed_count ;
2020-08-22 21:37:10 +00:00
}
2019-07-16 08:08:39 +00:00
}
2021-02-28 19:47:48 +00:00
using AK : : current_test_case_did_fail ;
2019-07-16 08:08:39 +00:00
using AK : : TestCase ;
using AK : : TestSuite ;
2020-08-21 16:52:28 +00:00
# define __TESTCASE_FUNC(x) __test_##x
# define __TESTCASE_TYPE(x) __TestCase_##x
# define TEST_CASE(x) \
static void __TESTCASE_FUNC ( x ) ( ) ; \
struct __TESTCASE_TYPE ( x ) { \
__TESTCASE_TYPE ( x ) \
( ) { TestSuite : : the ( ) . add_case ( adopt ( * new TestCase ( # x , __TESTCASE_FUNC ( x ) , false ) ) ) ; } \
} ; \
static struct __TESTCASE_TYPE ( x ) __TESTCASE_TYPE ( x ) ; \
static void __TESTCASE_FUNC ( x ) ( )
# define __BENCHMARK_FUNC(x) __benchmark_##x
# define __BENCHMARK_TYPE(x) __BenchmarkCase_##x
# define BENCHMARK_CASE(x) \
static void __BENCHMARK_FUNC ( x ) ( ) ; \
struct __BENCHMARK_TYPE ( x ) { \
__BENCHMARK_TYPE ( x ) \
( ) { TestSuite : : the ( ) . add_case ( adopt ( * new TestCase ( # x , __BENCHMARK_FUNC ( x ) , true ) ) ) ; } \
} ; \
static struct __BENCHMARK_TYPE ( x ) __BENCHMARK_TYPE ( x ) ; \
static void __BENCHMARK_FUNC ( x ) ( )
# define TEST_MAIN(x) \
TestSuite * TestSuite : : s_global = nullptr ; \
template < size_t N > \
constexpr size_t compiletime_lenof ( const char ( & ) [ N ] ) \
{ \
return N - 1 ; \
} \
int main ( int argc , char * * argv ) \
{ \
static_assert ( compiletime_lenof ( # x ) ! = 0 , " Set SuiteName " ) ; \
2021-02-28 19:47:48 +00:00
int ret = TestSuite : : the ( ) . main ( # x , argc , argv ) ; \
2020-08-21 16:52:28 +00:00
TestSuite : : release ( ) ; \
2021-02-28 19:47:48 +00:00
return ret ; \
2019-07-16 08:08:39 +00:00
}
2020-10-07 12:02:42 +00:00
# define EXPECT_EQ(a, b) \
do { \
auto lhs = ( a ) ; \
auto rhs = ( b ) ; \
2021-02-28 19:47:48 +00:00
if ( lhs ! = rhs ) { \
2020-10-07 12:02:42 +00:00
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={} " , __FILE__ , __LINE__ , # a , # b , FormatIfSupported { lhs } , FormatIfSupported { rhs } ) ; \
2021-02-28 19:47:48 +00:00
current_test_case_did_fail ( ) ; \
} \
2020-08-25 14:20:52 +00:00
} while ( false )
2020-08-22 21:37:10 +00:00
// If you're stuck and `EXPECT_EQ` seems to refuse to print anything useful,
// try this: It'll spit out a nice compiler error telling you why it doesn't print.
2020-10-07 12:02:42 +00:00
# define EXPECT_EQ_FORCE(a, b) \
do { \
auto lhs = ( a ) ; \
auto rhs = ( b ) ; \
2021-02-28 19:47:48 +00:00
if ( lhs ! = rhs ) { \
2020-10-07 12:02:42 +00:00
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={} " , __FILE__ , __LINE__ , # a , # b , lhs , rhs ) ; \
2021-02-28 19:47:48 +00:00
current_test_case_did_fail ( ) ; \
} \
2020-08-25 14:20:52 +00:00
} while ( false )
2019-07-16 08:08:39 +00:00
2020-10-07 12:02:42 +00:00
# define EXPECT(x) \
do { \
2021-02-28 19:47:48 +00:00
if ( ! ( x ) ) { \
2020-10-07 12:02:42 +00:00
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT({}) failed " , __FILE__ , __LINE__ , # x ) ; \
2021-02-28 19:47:48 +00:00
current_test_case_did_fail ( ) ; \
} \
2020-08-25 14:20:52 +00:00
} while ( false )
2021-03-02 19:33:44 +00:00
2021-04-15 07:36:14 +00:00
# define EXPECT_APPROXIMATE(a, b) \
do { \
auto expect_close_lhs = a ; \
auto expect_close_rhs = b ; \
auto expect_close_diff = static_cast < double > ( expect_close_lhs ) - static_cast < double > ( expect_close_rhs ) ; \
if ( abs ( expect_close_diff ) > = 0.000001 ) { \
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT_APPROXIMATE({}, {}) " \
" failed with lhs={}, rhs={}, (lhs-rhs)={} " , \
__FILE__ , __LINE__ , # a , # b , expect_close_lhs , expect_close_rhs , expect_close_diff ) ; \
current_test_case_did_fail ( ) ; \
} \
2021-03-02 19:33:44 +00:00
} while ( false )