2020-01-18 11:38:21 +03: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 11:08:39 +03:00
# pragma once
2019-08-02 10:23:03 +03:00
# define AK_TEST_SUITE
2020-10-07 15:02:42 +03:00
namespace AK {
template < typename . . . Parameters >
void warnln ( const char * fmtstr , const Parameters & . . . ) ;
}
2019-08-02 10:23:03 +03:00
2020-10-07 15:02:42 +03:00
using AK : : warnln ;
# define ASSERT(x) \
do { \
if ( ! ( x ) ) \
: : AK : : warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: ASSERT({}) failed " , __FILE__ , __LINE__ , # x ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )
2019-08-02 10:23:03 +03:00
2020-10-07 15:02:42 +03:00
# define RELEASE_ASSERT(x) \
do { \
if ( ! ( x ) ) \
: : AK : : warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: RELEASE_ASSERT({}) failed " , __FILE__ , __LINE__ , # x ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )
2020-08-21 19:52:28 +03:00
2020-10-07 15:02:42 +03:00
# define ASSERT_NOT_REACHED() \
do { \
: : AK : : warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: ASSERT_NOT_REACHED() called " , __FILE__ , __LINE__ ) ; \
: : abort ( ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )
2020-08-21 19:52:28 +03:00
2020-10-07 15:02:42 +03:00
# define TODO() \
do { \
: : AK : : warnln ( stderr , " \033 [31;1mFAIL \033 [0m: {}:{}: TODO() called " , __FILE__ , __LINE__ ) ; \
: : abort ( ) ; \
} while ( false )
2019-08-02 10:23:03 +03:00
2020-10-14 12:20:14 +03:00
# include <stdlib.h>
2020-10-07 15:02:42 +03:00
# include <AK/Format.h>
2019-09-06 16:34:26 +03:00
# include <AK/Function.h>
# include <AK/NonnullRefPtrVector.h>
# include <AK/String.h>
2020-08-20 22:35:15 +03:00
2020-08-21 19:52:28 +03:00
# include <LibCore/ArgsParser.h>
2020-08-20 22:35:15 +03:00
# include <sys/time.h>
2019-07-16 11:08:39 +03:00
namespace AK {
class TestElapsedTimer {
public :
TestElapsedTimer ( ) { restart ( ) ; }
2020-08-20 22:35:15 +03:00
void restart ( ) { gettimeofday ( & m_started , nullptr ) ; }
u64 elapsed_milliseconds ( )
2019-07-16 11:08:39 +03:00
{
2020-08-20 22:35:15 +03: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 11:08:39 +03:00
}
private :
2020-08-20 22:35:15 +03:00
struct timeval m_started ;
2019-07-16 11:08:39 +03:00
} ;
2020-08-21 19:52:28 +03:00
using TestFunction = AK : : Function < void ( ) > ;
2019-07-16 11:08:39 +03: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 12:47:12 +03:00
static TestSuite & the ( )
2019-07-16 11:08:39 +03:00
{
if ( s_global = = nullptr )
s_global = new TestSuite ( ) ;
2019-07-21 12:47:12 +03:00
return * s_global ;
2019-07-16 11:08:39 +03:00
}
2019-07-21 12:49:19 +03:00
static void release ( )
{
2020-08-21 19:52:28 +03:00
if ( s_global )
delete s_global ;
2019-07-21 12:49:19 +03:00
s_global = nullptr ;
}
2020-08-21 19:52:28 +03:00
void run ( const NonnullRefPtrVector < TestCase > & ) ;
2019-07-16 11:08:39 +03:00
void main ( const String & suite_name , int argc , char * * argv ) ;
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 ) ;
}
private :
static TestSuite * s_global ;
NonnullRefPtrVector < TestCase > m_cases ;
2020-08-21 19:52:28 +03:00
u64 m_testtime = 0 ;
u64 m_benchtime = 0 ;
2019-07-16 11:08:39 +03:00
String m_suite_name ;
} ;
void TestSuite : : main ( const String & suite_name , int argc , char * * argv )
{
m_suite_name = suite_name ;
2020-08-21 19:52:28 +03: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-03 00:14:37 +03:00
args_parser . add_option ( do_list_cases , " List available test cases. " , " list " , 0 ) ;
2020-08-21 19:52:28 +03: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 15:02:42 +03:00
outln ( " Available cases for {}: " , suite_name ) ;
2020-08-21 19:52:28 +03:00
for ( const auto & test : matching_tests ) {
2020-10-07 15:02:42 +03:00
outln ( " {} " , test . name ( ) ) ;
2019-07-16 11:08:39 +03:00
}
2020-08-21 19:52:28 +03:00
} else {
2020-10-07 15:02:42 +03:00
outln ( " Running {} cases out of {}. " , matching_tests . size ( ) , m_cases . size ( ) ) ;
2019-07-16 11:08:39 +03:00
2020-08-21 19:52:28 +03:00
run ( matching_tests ) ;
2019-07-16 11:08:39 +03: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 10:25:24 +03:00
if ( ! search . is_empty ( ) & & ! t . name ( ) . matches ( search , CaseSensitivity : : CaseInsensitive ) ) {
2019-07-16 11:08:39 +03:00
continue ;
}
if ( ! find_tests & & ! t . is_benchmark ( ) ) {
continue ;
}
if ( ! find_benchmarks & & t . is_benchmark ( ) ) {
continue ;
}
matches . append ( t ) ;
}
return matches ;
}
void TestSuite : : run ( const NonnullRefPtrVector < TestCase > & tests )
{
2020-08-21 19:52:28 +03:00
size_t test_count = 0 ;
size_t benchmark_count = 0 ;
2019-07-16 11:08:39 +03:00
TestElapsedTimer global_timer ;
2020-08-21 19:52:28 +03:00
2019-07-16 11:08:39 +03:00
for ( const auto & t : tests ) {
2020-08-21 19:52:28 +03:00
const auto test_type = t . is_benchmark ( ) ? " benchmark " : " test " ;
2020-10-04 16:35:43 +03:00
warnln ( " Running {} '{}'. " , test_type , t . name ( ) ) ;
2020-08-21 19:52:28 +03:00
2019-07-16 11:08:39 +03:00
TestElapsedTimer timer ;
2020-08-20 18:54:04 +03:00
t . func ( ) ( ) ;
2020-08-21 19:52:28 +03:00
const auto time = timer . elapsed_milliseconds ( ) ;
2020-10-04 16:35:43 +03:00
dbgln ( " Completed {} '{}' in {}ms " , test_type , t . name ( ) , time ) ;
2020-08-21 19:52:28 +03:00
2019-07-16 11:08:39 +03:00
if ( t . is_benchmark ( ) ) {
m_benchtime + = time ;
benchmark_count + + ;
} else {
m_testtime + = time ;
test_count + + ;
}
}
2020-08-21 19:52:28 +03:00
2020-10-07 15:02:42 +03: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 ) ) ;
2020-08-23 00:37:10 +03:00
}
2019-07-16 11:08:39 +03:00
}
using AK : : TestCase ;
using AK : : TestSuite ;
2020-08-21 19:52:28 +03: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 " ) ; \
TestSuite : : the ( ) . main ( # x , argc , argv ) ; \
TestSuite : : release ( ) ; \
2019-07-16 11:08:39 +03:00
}
2020-10-07 15:02:42 +03:00
# define EXPECT_EQ(a, b) \
do { \
auto lhs = ( a ) ; \
auto rhs = ( b ) ; \
if ( lhs ! = rhs ) \
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={} " , __FILE__ , __LINE__ , # a , # b , FormatIfSupported { lhs } , FormatIfSupported { rhs } ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )
2020-08-23 00:37:10 +03: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 15:02:42 +03:00
# define EXPECT_EQ_FORCE(a, b) \
do { \
auto lhs = ( a ) ; \
auto rhs = ( b ) ; \
if ( lhs ! = rhs ) \
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT_EQ({}, {}) failed with lhs={} and rhs={} " , __FILE__ , __LINE__ , # a , # b , lhs , rhs ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )
2019-07-16 11:08:39 +03:00
2020-10-07 15:02:42 +03:00
# define EXPECT(x) \
do { \
if ( ! ( x ) ) \
warnln ( " \033 [31;1mFAIL \033 [0m: {}:{}: EXPECT({}) failed " , __FILE__ , __LINE__ , # x ) ; \
2020-08-25 17:20:52 +03:00
} while ( false )