2013-01-17 20:59:28 +01:00
// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
// UnitTests
//
// This is a program to directly test various functions, without going
// through a PSP. Especially useful for things like opcode emitters,
// hashes, and various data conversion utility function.
//
// TODO: Make a test of nice unittest asserts and count successes etc.
// Or just integrate with an existing testing framework.
2020-10-10 10:06:28 +02:00
//
// To use, set command line parameter to one or more of the tests below, or "all".
// Search for "availableTests".
2023-04-23 14:36:34 +02:00
//
// Example of how to run with CMake:
//
// ./b.sh --unittest
// build/unittest EscapeMenuString
2013-01-17 20:59:28 +01:00
2021-01-10 12:16:02 -08:00
# include "ppsspp_config.h"
2021-05-06 01:31:38 +02:00
2023-02-10 14:34:33 +01:00
# include <algorithm>
2013-04-13 23:05:15 +02:00
# include <cstdio>
# include <cstdlib>
# include <cmath>
2021-01-06 16:37:04 +01:00
# include <vector>
2013-01-17 20:59:28 +01:00
# include <string>
2014-11-29 11:37:45 +01:00
# include <sstream>
2021-05-06 01:31:38 +02:00
2021-01-10 12:16:02 -08:00
# if PPSSPP_PLATFORM(ANDROID)
2020-08-27 20:01:37 -07:00
# include <jni.h>
# endif
2013-01-17 20:59:28 +01:00
2022-09-01 10:46:47 +02:00
# include "Common/Data/Collections/TinySet.h"
2023-05-16 23:29:41 +02:00
# include "Common/Data/Collections/FastVec.h"
2024-11-21 23:15:17 +01:00
# include "Common/Data/Collections/CharQueue.h"
2022-12-01 17:09:54 +01:00
# include "Common/Data/Convert/SmallDataConvert.h"
2020-10-01 13:05:04 +02:00
# include "Common/Data/Text/Parsers.h"
2021-09-25 10:59:54 -07:00
# include "Common/Data/Text/WrapText.h"
2021-03-20 11:18:54 +01:00
# include "Common/Data/Encoding/Utf8.h"
2024-11-21 23:15:17 +01:00
# include "Common/Buffer.h"
2021-09-25 10:59:54 -07:00
# include "Common/File/Path.h"
# include "Common/Input/InputState.h"
# include "Common/Math/math_util.h"
# include "Common/Render/DrawBuffer.h"
# include "Common/System/NativeApp.h"
# include "Common/System/System.h"
2023-09-23 11:19:27 -07:00
# include "Common/Thread/ThreadUtil.h"
2023-09-25 18:42:23 +02:00
# include "Common/Data/Format/IniFile.h"
2024-07-26 15:59:01 +02:00
# include "Common/TimeUtil.h"
2016-01-01 12:50:38 +01:00
# include "Common/ArmEmitter.h"
2019-09-28 08:40:41 -07:00
# include "Common/BitScan.h"
# include "Common/CPUDetect.h"
2020-08-15 16:25:50 +02:00
# include "Common/Log.h"
2023-04-23 14:36:34 +02:00
# include "Common/StringUtils.h"
2014-01-10 21:32:08 -08:00
# include "Core/Config.h"
2024-06-04 10:25:46 +02:00
# include "Common/Data/Convert/ColorConv.h"
2023-03-10 09:01:00 -05:00
# include "Common/File/VFS/VFS.h"
# include "Common/File/VFS/DirectoryReader.h"
2015-09-12 11:21:54 +02:00
# include "Core/FileSystems/ISOFileSystem.h"
2019-09-28 08:40:41 -07:00
# include "Core/MemMap.h"
2023-04-01 13:43:42 +02:00
# include "Core/KeyMap.h"
2019-09-28 08:40:41 -07:00
# include "Core/MIPS/MIPSVFPUUtils.h"
2018-03-25 18:52:16 -07:00
# include "GPU/Common/TextureDecoder.h"
2023-02-10 14:07:45 +01:00
# include "GPU/Common/GPUStateUtils.h"
2013-01-17 20:59:28 +01:00
2023-05-16 14:34:28 +02:00
# include "Common/File/AndroidContentURI.h"
2021-03-14 11:01:51 +01:00
2014-11-07 22:40:28 -08:00
# include "unittest/JitHarness.h"
2015-07-03 15:25:40 -07:00
# include "unittest/TestVertexJit.h"
2014-11-29 11:37:45 +01:00
# include "unittest/UnitTest.h"
2013-07-30 21:39:04 +02:00
2021-01-06 16:37:04 +01:00
2013-09-04 12:07:42 +02:00
std : : string System_GetProperty ( SystemProperty prop ) { return " " ; }
2021-01-06 16:37:04 +01:00
std : : vector < std : : string > System_GetPropertyStringVec ( SystemProperty prop ) { return std : : vector < std : : string > ( ) ; }
2024-04-05 11:07:57 +02:00
int64_t System_GetPropertyInt ( SystemProperty prop ) {
2017-04-05 16:21:08 +02:00
return - 1 ;
}
2020-01-06 01:04:07 +08:00
float System_GetPropertyFloat ( SystemProperty prop ) {
return - 1 ;
}
2017-04-29 17:35:12 -07:00
bool System_GetPropertyBool ( SystemProperty prop ) {
2021-02-21 22:02:11 +01:00
switch ( prop ) {
case SYSPROP_CAN_JIT :
return true ;
2021-06-13 10:28:27 +02:00
default :
return false ;
2021-02-21 22:02:11 +01:00
}
2017-04-29 17:35:12 -07:00
}
2023-03-21 11:10:09 +01:00
void System_Notify ( SystemNotification notification ) { }
2023-09-30 11:21:22 +02:00
void System_PostUIMessage ( UIMessage message , const std : : string & param ) { }
2023-03-24 17:19:57 +01:00
void System_AudioGetDebugStats ( char * buf , size_t bufSize ) { if ( buf ) buf [ 0 ] = ' \0 ' ; }
void System_AudioClear ( ) { }
void System_AudioPushSamples ( const s32 * audio , int numSamples ) { }
2013-07-30 21:39:04 +02:00
2023-07-01 12:31:46 +02:00
// TODO: To avoid having to define these here, these should probably be turned into system "requests".
2024-09-26 01:09:56 +02:00
// To clear the secret entirely, just save an empty string.
bool NativeSaveSecret ( std : : string_view nameOfSecret , std : : string_view data ) { return false ; }
std : : string NativeLoadSecret ( std : : string_view nameOfSecret ) {
2023-07-01 12:31:46 +02:00
return " " ;
}
2021-01-10 12:16:02 -08:00
# if PPSSPP_PLATFORM(ANDROID)
2020-08-27 20:01:37 -07:00
JNIEnv * getEnv ( ) {
return nullptr ;
}
jclass findClass ( const char * name ) {
return nullptr ;
}
2021-01-10 12:16:02 -08:00
2023-03-21 10:42:23 +01:00
bool System_AudioRecordingIsAvailable ( ) { return false ; }
bool System_AudioRecordingState ( ) { return false ; }
2020-08-27 20:01:37 -07:00
# endif
2015-09-12 11:21:54 +02:00
# ifndef M_PI_2
2013-11-30 12:45:31 +01:00
# define M_PI_2 1.57079632679489661923
2015-09-12 11:21:54 +02:00
# endif
2013-11-30 12:45:31 +01:00
2014-06-05 21:29:20 +02:00
// asin acos atan: https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h
2013-11-30 12:45:31 +01:00
// TODO:
// Fast approximate sincos for NEON
// http://blog.julien.cayzac.name/2009/12/fast-sinecosine-for-armv7neon.html
// Fast sincos
// http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos
// minimax (surprisingly terrible! something must be wrong)
// double asin_plus_sqrtthing = .9998421793 + (1.012386649 + (-.6575341673 + .8999841642 + (-1.669668977 + (1.571945105 - .5860008052 * x) * x) * x) * x) * x;
// VERY good. 6 MAD, one division.
// double asin_plus_sqrtthing = (1.807607311 + (.191900116 + (-2.511278506 + (1.062519236 + (-.3572142480 + .1087063463 * x) * x) * x) * x) * x) / (1.807601897 - 1.615203794 * x);
// float asin_plus_sqrtthing_correct_ends =
// (1.807607311f + (.191900116f + (-2.511278506f + (1.062519236f + (-.3572142480f + .1087063463f * x) * x) * x) * x) * x) / (1.807607311f - 1.615195094 * x);
// Unfortunately this is very serial.
// At least there are only 8 constants needed - load them into two low quads and go to town.
// For every step, VDUP the constant into a new register (out of two alternating), then VMLA or VFMA into it.
// http://www.ecse.rpi.edu/~wrf/Research/Short_Notes/arcsin/
// minimax polynomial rational approx, pretty good, get four digits consistently.
// unfortunately fastasin(1.0) / M_PI_2 != 1.0f, but it's pretty close.
float fastasin ( double x ) {
float sign = x > = 0.0f ? 1.0f : - 1.0f ;
x = fabs ( x ) ;
float sqrtthing = sqrt ( 1.0f - x * x ) ;
2013-11-30 20:49:27 +01:00
// note that the sqrt can run parallel while we do the rest
// if the hardware supports it
2013-11-30 12:45:31 +01:00
float y = - .3572142480f + .1087063463f * x ;
y = y * x + 1.062519236f ;
y = y * x + - 2.511278506f ;
y = y * x + .191900116f ;
y = y * x + 1.807607311f ;
y / = ( 1.807607311f - 1.615195094 * x ) ;
return sign * ( y - sqrtthing ) ;
}
2015-09-12 11:21:54 +02:00
double atan_66s ( double x ) {
const double c1 = 1.6867629106 ;
const double c2 = 0.4378497304 ;
const double c3 = 1.6867633134 ;
double x2 ; // The input argument squared
2013-11-30 12:45:31 +01:00
2015-09-12 11:21:54 +02:00
x2 = x * x ;
2013-11-30 12:45:31 +01:00
return ( x * ( c1 + x2 * c2 ) / ( c3 + x2 ) ) ;
}
// Terrible.
double fastasin2 ( double x ) {
return atan_66s ( x / sqrt ( 1 - x * x ) ) ;
}
// Also terrible.
float fastasin3 ( float x ) {
return x + x * x * x * x * x * 0.4971 ;
}
2013-12-01 11:50:17 +01:00
// Great! This is the one we'll use. Can be easily rescaled to get the right range for free.
// http://mathforum.org/library/drmath/view/54137.html
// http://www.musicdsp.org/showone.php?id=115
float fastasin4 ( float x ) {
float sign = x > = 0.0f ? 1.0f : - 1.0f ;
x = fabs ( x ) ;
x = M_PI / 2 - sqrtf ( 1.0f - x ) * ( 1.5707288 + - 0.2121144 * x + 0.0742610 * x * x + - 0.0187293 * x * x * x ) ;
return sign * x ;
}
// Or this:
float fastasin5 ( float x )
{
float sign = x > = 0.0f ? 1.0f : - 1.0f ;
x = fabs ( x ) ;
float fRoot = sqrtf ( 1.0f - x ) ;
2013-12-03 12:57:05 +01:00
float fResult = 0.0742610f + - 0.0187293f * x ;
fResult = - 0.2121144f + fResult * x ;
fResult = 1.5707288f + fResult * x ;
2013-12-01 11:50:17 +01:00
fResult = M_PI / 2 - fRoot * fResult ;
return sign * fResult ;
}
2013-11-30 12:45:31 +01:00
// This one is unfortunately not very good. But lets us avoid PI entirely
// thanks to the special arguments of the PSP functions.
// http://www.dspguru.com/dsp/tricks/parabolic-approximation-of-sin-and-cos
# define C 0.70710678118654752440f // 1.0f / sqrt(2.0f)
// Some useful constants (PI and <math.h> are not part of algo)
# define BITSPERQUARTER (20)
void fcs ( float angle , float & sinout , float & cosout ) {
int phasein = angle * ( 1 < < BITSPERQUARTER ) ;
// Modulo phase into quarter, convert to float 0..1
float modphase = ( phasein & ( ( 1 < < BITSPERQUARTER ) - 1 ) ) * ( 1.0f / ( 1 < < BITSPERQUARTER ) ) ;
2020-01-06 01:04:07 +08:00
// Extract quarter bits
2013-11-30 12:45:31 +01:00
int quarter = phasein > > BITSPERQUARTER ;
// Recognize quarter
2015-09-12 11:21:54 +02:00
if ( ! quarter ) {
2013-11-30 12:45:31 +01:00
// First quarter, angle = 0 .. pi/2
float x = modphase - 0.5f ; // 1 sub
float temp = ( 2 - 4 * C ) * x * x + C ; // 2 mul, 1 add
sinout = temp + x ; // 1 add
cosout = temp - x ; // 1 sub
} else if ( quarter = = 1 ) {
// Second quarter, angle = pi/2 .. pi
float x = 0.5f - modphase ; // 1 sub
float temp = ( 2 - 4 * C ) * x * x + C ; // 2 mul, 1 add
sinout = x + temp ; // 1 add
cosout = x - temp ; // 1 sub
} else if ( quarter = = 2 ) {
// Third quarter, angle = pi .. 1.5pi
float x = modphase - 0.5f ; // 1 sub
float temp = ( 4 * C - 2 ) * x * x - C ; // 2 mul, 1 sub
sinout = temp - x ; // 1 sub
cosout = temp + x ; // 1 add
} else if ( quarter = = 3 ) {
// Fourth quarter, angle = 1.5pi..2pi
float x = modphase - 0.5f ; // 1 sub
float temp = ( 2 - 4 * C ) * x * x + C ; // 2 mul, 1 add
sinout = x - temp ; // 1 sub
cosout = x + temp ; // 1 add
}
}
2013-11-30 20:49:27 +01:00
# undef C
const float PI_SQR = 9.86960440108935861883449099987615114f ;
//https://code.google.com/p/math-neon/source/browse/trunk/math_floorf.c?r=18
// About 2 correct decimals. Not great.
void fcs2 ( float theta , float & outsine , float & outcosine ) {
float gamma = theta + 1 ;
gamma + = 2 ;
gamma / = 4 ;
theta + = 2 ;
theta / = 4 ;
//theta -= (float)(int)theta;
//gamma -= (float)(int)gamma;
theta - = floorf ( theta ) ;
gamma - = floorf ( gamma ) ;
theta * = 4 ;
theta - = 2 ;
gamma * = 4 ;
gamma - = 2 ;
2015-09-12 11:21:54 +02:00
float x = 2 * gamma - gamma * fabs ( gamma ) ;
float y = 2 * theta - theta * fabs ( theta ) ;
2016-05-31 10:40:14 -07:00
const float P = 0.225f ;
2015-09-12 11:21:54 +02:00
outsine = P * ( y * fabsf ( y ) - y ) + y ; // Q * y + P * y * abs(y)
outcosine = P * ( x * fabsf ( x ) - x ) + x ; // Q * y + P * y * abs(y)
2013-11-30 20:49:27 +01:00
}
2013-11-30 12:45:31 +01:00
void fastsincos ( float x , float & sine , float & cosine ) {
2013-11-30 20:49:27 +01:00
fcs2 ( x , sine , cosine ) ;
2013-11-30 12:45:31 +01:00
}
bool TestSinCos ( ) {
for ( int i = - 100 ; i < = 100 ; i + + ) {
float f = i / 30.0f ;
// The PSP sin/cos take as argument angle * M_PI_2.
// We need to match that.
float slowsin = sinf ( f * M_PI_2 ) , slowcos = cosf ( f * M_PI_2 ) ;
float fastsin , fastcos ;
fastsincos ( f , fastsin , fastcos ) ;
printf ( " %f: slow: %0.8f, %0.8f fast: %0.8f, %0.8f \n " , f , slowsin , slowcos , fastsin , fastcos ) ;
}
return true ;
}
bool TestAsin ( ) {
for ( int i = - 100 ; i < = 100 ; i + + ) {
float f = i / 100.0f ;
float slowval = asinf ( f ) / M_PI_2 ;
2013-12-01 11:50:17 +01:00
float fastval = fastasin5 ( f ) / M_PI_2 ;
2013-11-30 12:45:31 +01:00
printf ( " slow: %0.16f fast: %0.16f \n " , slowval , fastval ) ;
float diff = fabsf ( slowval - fastval ) ;
2013-12-01 11:50:17 +01:00
// EXPECT_TRUE(diff < 0.0001f);
2013-11-30 12:45:31 +01:00
}
2013-12-01 11:50:17 +01:00
// EXPECT_TRUE(fastasin(1.0) / M_PI_2 <= 1.0f);
2013-11-30 12:45:31 +01:00
return true ;
}
2013-04-13 23:05:15 +02:00
bool TestMathUtil ( ) {
EXPECT_FALSE ( my_isinf ( 1.0 ) ) ;
volatile float zero = 0.0f ;
EXPECT_TRUE ( my_isinf ( 1.0f / zero ) ) ;
EXPECT_FALSE ( my_isnan ( 1.0f / zero ) ) ;
return true ;
}
2013-11-28 12:38:45 +01:00
bool TestParsers ( ) {
const char * macstr = " 01:02:03:ff:fe:fd " ;
uint8_t mac [ 6 ] ;
ParseMacAddress ( macstr , mac ) ;
EXPECT_TRUE ( mac [ 0 ] = = 1 ) ;
EXPECT_TRUE ( mac [ 1 ] = = 2 ) ;
EXPECT_TRUE ( mac [ 2 ] = = 3 ) ;
EXPECT_TRUE ( mac [ 3 ] = = 255 ) ;
EXPECT_TRUE ( mac [ 4 ] = = 254 ) ;
EXPECT_TRUE ( mac [ 5 ] = = 253 ) ;
return true ;
}
2022-09-01 10:46:47 +02:00
bool TestTinySet ( ) {
TinySet < int , 4 > a ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 0 ) ;
a . push_back ( 1 ) ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 1 ) ;
a . push_back ( 2 ) ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 2 ) ;
TinySet < int , 4 > b ;
b . push_back ( 8 ) ;
b . push_back ( 9 ) ;
b . push_back ( 10 ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 3 ) ;
a . append ( b ) ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 5 ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 3 ) ;
b . append ( b ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 6 ) ;
EXPECT_EQ_INT ( a [ 0 ] , 1 ) ;
EXPECT_EQ_INT ( a [ 1 ] , 2 ) ;
EXPECT_EQ_INT ( a [ 2 ] , 8 ) ;
EXPECT_EQ_INT ( a [ 3 ] , 9 ) ;
EXPECT_EQ_INT ( a [ 4 ] , 10 ) ;
a . append ( a ) ;
EXPECT_EQ_INT ( a . size ( ) , 10 ) ;
EXPECT_EQ_INT ( a [ 9 ] , 10 ) ;
b . push_back ( 11 ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 7 ) ;
b . push_back ( 12 ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 8 ) ;
b . push_back ( 13 ) ;
EXPECT_EQ_INT ( b . size ( ) , 9 ) ;
return true ;
}
2023-05-16 23:29:41 +02:00
bool TestFastVec ( ) {
FastVec < int > a ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 0 ) ;
a . push_back ( 1 ) ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 1 ) ;
a . push_back ( 2 ) ;
EXPECT_EQ_INT ( ( int ) a . size ( ) , 2 ) ;
FastVec < int > b ;
b . push_back ( 8 ) ;
b . push_back ( 9 ) ;
b . push_back ( 10 ) ;
EXPECT_EQ_INT ( ( int ) b . size ( ) , 3 ) ;
for ( int i = 0 ; i < 100 ; i + + ) {
b . push_back ( 33 ) ;
}
EXPECT_EQ_INT ( ( int ) b . size ( ) , 103 ) ;
2023-05-17 00:38:39 +02:00
int items [ 4 ] = { 50 , 60 , 70 , 80 } ;
b . insert ( b . begin ( ) + 1 , items , items + 4 ) ;
EXPECT_EQ_INT ( b [ 0 ] , 8 ) ;
EXPECT_EQ_INT ( b [ 1 ] , 50 ) ;
EXPECT_EQ_INT ( b [ 2 ] , 60 ) ;
EXPECT_EQ_INT ( b [ 3 ] , 70 ) ;
EXPECT_EQ_INT ( b [ 4 ] , 80 ) ;
EXPECT_EQ_INT ( b [ 5 ] , 9 ) ;
2023-05-17 01:10:40 +02:00
b . resize ( 2 ) ;
b . insert ( b . end ( ) , items , items + 4 ) ;
EXPECT_EQ_INT ( b [ 0 ] , 8 ) ;
EXPECT_EQ_INT ( b [ 1 ] , 50 ) ;
EXPECT_EQ_INT ( b [ 2 ] , 50 ) ;
EXPECT_EQ_INT ( b [ 3 ] , 60 ) ;
EXPECT_EQ_INT ( b [ 4 ] , 70 ) ;
EXPECT_EQ_INT ( b [ 5 ] , 80 ) ;
2023-05-16 23:29:41 +02:00
return true ;
}
2014-06-15 11:51:30 +02:00
bool TestVFPUSinCos ( ) {
float sine , cosine ;
2023-03-10 09:01:00 -05:00
// Needed for VFPU tables.
// There might be a better place to invoke it, but whatever.
g_VFS . Register ( " " , new DirectoryReader ( Path ( " assets " ) ) ) ;
2023-03-09 23:23:33 -05:00
InitVFPU ( ) ;
2014-06-15 11:51:30 +02:00
vfpu_sincos ( 0.0f , sine , cosine ) ;
EXPECT_EQ_FLOAT ( sine , 0.0f ) ;
EXPECT_EQ_FLOAT ( cosine , 1.0f ) ;
vfpu_sincos ( 1.0f , sine , cosine ) ;
EXPECT_APPROX_EQ_FLOAT ( sine , 1.0f ) ;
EXPECT_APPROX_EQ_FLOAT ( cosine , 0.0f ) ;
vfpu_sincos ( 2.0f , sine , cosine ) ;
EXPECT_APPROX_EQ_FLOAT ( sine , 0.0f ) ;
EXPECT_APPROX_EQ_FLOAT ( cosine , - 1.0f ) ;
vfpu_sincos ( 3.0f , sine , cosine ) ;
EXPECT_APPROX_EQ_FLOAT ( sine , - 1.0f ) ;
EXPECT_APPROX_EQ_FLOAT ( cosine , 0.0f ) ;
vfpu_sincos ( 4.0f , sine , cosine ) ;
EXPECT_EQ_FLOAT ( sine , 0.0f ) ;
EXPECT_EQ_FLOAT ( cosine , 1.0f ) ;
vfpu_sincos ( 5.0f , sine , cosine ) ;
EXPECT_APPROX_EQ_FLOAT ( sine , 1.0f ) ;
EXPECT_APPROX_EQ_FLOAT ( cosine , 0.0f ) ;
2020-10-10 10:06:28 +02:00
vfpu_sincos ( - 1.0f , sine , cosine ) ;
EXPECT_EQ_FLOAT ( sine , - 1.0f ) ;
EXPECT_EQ_FLOAT ( cosine , 0.0f ) ;
vfpu_sincos ( - 2.0f , sine , cosine ) ;
EXPECT_EQ_FLOAT ( sine , 0.0f ) ;
EXPECT_EQ_FLOAT ( cosine , - 1.0f ) ;
for ( float angle = - 10.0f ; angle < 10.0f ; angle + = 0.1f ) {
2014-06-15 11:51:30 +02:00
vfpu_sincos ( angle , sine , cosine ) ;
EXPECT_APPROX_EQ_FLOAT ( sine , sinf ( angle * M_PI_2 ) ) ;
EXPECT_APPROX_EQ_FLOAT ( cosine , cosf ( angle * M_PI_2 ) ) ;
2020-10-10 10:06:28 +02:00
printf ( " sine: %f==%f cosine: %f==%f \n " , sine , sinf ( angle * M_PI_2 ) , cosine , cosf ( angle * M_PI_2 ) ) ;
2014-06-15 11:51:30 +02:00
}
return true ;
}
2014-12-12 23:49:23 +01:00
bool TestMatrixTranspose ( ) {
MatrixSize sz = M_4x4 ;
int matrix = 0 ; // M000
u8 cols [ 4 ] ;
u8 rows [ 4 ] ;
GetMatrixColumns ( matrix , sz , cols ) ;
GetMatrixRows ( matrix , sz , rows ) ;
int transposed = Xpose ( matrix ) ;
u8 x_cols [ 4 ] ;
u8 x_rows [ 4 ] ;
GetMatrixColumns ( transposed , sz , x_cols ) ;
GetMatrixRows ( transposed , sz , x_rows ) ;
for ( int i = 0 ; i < GetMatrixSide ( sz ) ; i + + ) {
EXPECT_EQ_INT ( cols [ i ] , x_rows [ i ] ) ;
EXPECT_EQ_INT ( x_cols [ i ] , rows [ i ] ) ;
}
return true ;
}
2014-11-29 11:37:45 +01:00
void TestGetMatrix ( int matrix , MatrixSize sz ) {
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : System , " Testing matrix %s " , GetMatrixNotation ( matrix , sz ) . c_str ( ) ) ;
2014-11-29 11:37:45 +01:00
u8 fullMatrix [ 16 ] ;
u8 cols [ 4 ] ;
u8 rows [ 4 ] ;
2014-12-12 23:49:23 +01:00
2014-11-29 11:37:45 +01:00
GetMatrixColumns ( matrix , sz , cols ) ;
GetMatrixRows ( matrix , sz , rows ) ;
GetMatrixRegs ( fullMatrix , sz , matrix ) ;
int n = GetMatrixSide ( sz ) ;
VectorSize vsz = GetVectorSize ( sz ) ;
for ( int i = 0 ; i < n ; i + + ) {
// int colName = GetColumnName(matrix, sz, i, 0);
// int rowName = GetRowName(matrix, sz, i, 0);
int colName = cols [ i ] ;
int rowName = rows [ i ] ;
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : System , " Column %i: %s " , i , GetVectorNotation ( colName , vsz ) . c_str ( ) ) ;
INFO_LOG ( Log : : System , " Row %i: %s " , i , GetVectorNotation ( rowName , vsz ) . c_str ( ) ) ;
2014-11-29 11:37:45 +01:00
u8 colRegs [ 4 ] ;
u8 rowRegs [ 4 ] ;
GetVectorRegs ( colRegs , vsz , colName ) ;
GetVectorRegs ( rowRegs , vsz , rowName ) ;
// Check that the individual regs are the expected ones.
std : : stringstream a , b , c , d ;
for ( int j = 0 ; j < n ; j + + ) {
a . clear ( ) ;
b . clear ( ) ;
a < < ( int ) fullMatrix [ i * 4 + j ] < < " " ;
b < < ( int ) colRegs [ j ] < < " " ;
c . clear ( ) ;
d . clear ( ) ;
c < < ( int ) fullMatrix [ j * 4 + i ] < < " " ;
d < < ( int ) rowRegs [ j ] < < " " ;
}
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : System , " Col: %s vs %s " , a . str ( ) . c_str ( ) , b . str ( ) . c_str ( ) ) ;
2014-11-29 11:37:45 +01:00
if ( a . str ( ) ! = b . str ( ) )
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : System , " WRONG! " ) ;
INFO_LOG ( Log : : System , " Row: %s vs %s " , c . str ( ) . c_str ( ) , d . str ( ) . c_str ( ) ) ;
2014-11-29 11:37:45 +01:00
if ( c . str ( ) ! = d . str ( ) )
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : System , " WRONG! " ) ;
2014-11-29 11:37:45 +01:00
}
}
2015-09-12 11:21:54 +02:00
bool TestParseLBN ( ) {
const char * validStrings [ ] = {
" /sce_lbn0x5fa0_size0x1428 " ,
" /sce_lbn7050_sizeee850 " ,
" /sce_lbn0x5eeeh_size0x234x " , // Check for trailing chars. See #7960.
" /sce_lbneee__size434. " , // Check for trailing chars. See #7960.
} ;
int expectedResults [ ] [ 2 ] = {
{ 0x5fa0 , 0x1428 } ,
{ 0x7050 , 0xee850 } ,
{ 0x5eee , 0x234 } ,
{ 0xeee , 0x434 } ,
} ;
const char * invalidStrings [ ] = {
" /sce_lbn0x5fa0_sze0x1428 " ,
" " ,
" // " ,
} ;
for ( int i = 0 ; i < ARRAY_SIZE ( validStrings ) ; i + + ) {
u32 startSector = 0 , readSize = 0 ;
// printf("testing %s\n", validStrings[i]);
EXPECT_TRUE ( parseLBN ( validStrings [ i ] , & startSector , & readSize ) ) ;
EXPECT_EQ_INT ( startSector , expectedResults [ i ] [ 0 ] ) ;
EXPECT_EQ_INT ( readSize , expectedResults [ i ] [ 1 ] ) ;
}
for ( int i = 0 ; i < ARRAY_SIZE ( invalidStrings ) ; i + + ) {
u32 startSector , readSize ;
EXPECT_FALSE ( parseLBN ( invalidStrings [ i ] , & startSector , & readSize ) ) ;
}
return true ;
}
2018-03-25 18:52:16 -07:00
// So we can use EXPECT_TRUE, etc.
struct AlignedMem {
AlignedMem ( size_t sz , size_t alignment = 16 ) {
p_ = AllocateAlignedMemory ( sz , alignment ) ;
}
~ AlignedMem ( ) {
FreeAlignedMemory ( p_ ) ;
}
operator void * ( ) {
return p_ ;
}
operator char * ( ) {
return ( char * ) p_ ;
}
private :
void * p_ ;
} ;
bool TestQuickTexHash ( ) {
static const int BUF_SIZE = 1024 ;
AlignedMem buf ( BUF_SIZE , 16 ) ;
memset ( buf , 0 , BUF_SIZE ) ;
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0xaa756edc ) ;
2018-03-25 18:52:16 -07:00
memset ( buf , 1 , BUF_SIZE ) ;
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0x66f81b1c ) ;
2018-03-25 18:52:16 -07:00
strncpy ( buf , " hello " , BUF_SIZE ) ;
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0xf6028131 ) ;
2018-03-25 18:52:16 -07:00
strncpy ( buf , " goodbye " , BUF_SIZE ) ;
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0xef81b54f ) ;
2018-03-25 18:52:16 -07:00
// Simple patterns.
for ( int i = 0 ; i < BUF_SIZE ; + + i ) {
char * p = buf ;
p [ i ] = i & 0xFF ;
}
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0x0d64531c ) ;
2018-03-25 18:52:16 -07:00
int j = 573 ;
for ( int i = 0 ; i < BUF_SIZE ; + + i ) {
char * p = buf ;
j + = ( ( i * 7 ) + ( i & 3 ) ) * 11 ;
p [ i ] = j & 0xFF ;
}
2022-04-13 11:18:18 +02:00
EXPECT_EQ_HEX ( StableQuickTexHash ( buf , BUF_SIZE ) , 0x58de8dbc ) ;
2018-03-25 18:52:16 -07:00
2019-05-26 07:32:22 -07:00
return true ;
2018-03-25 18:52:16 -07:00
}
2019-06-14 18:03:10 +02:00
bool TestCLZ ( ) {
static const uint32_t input [ ] = {
0xFFFFFFFF ,
0x00FFFFF0 ,
0x00101000 ,
0x00003000 ,
0x00000001 ,
0x00000000 ,
} ;
static const uint32_t expected [ ] = {
0 ,
8 ,
11 ,
18 ,
31 ,
32 ,
} ;
for ( int i = 0 ; i < ARRAY_SIZE ( input ) ; i + + ) {
EXPECT_EQ_INT ( clz32 ( input [ i ] ) , expected [ i ] ) ;
}
return true ;
}
2019-09-28 08:40:41 -07:00
static bool TestMemMap ( ) {
Memory : : g_MemorySize = Memory : : RAM_DOUBLE_SIZE ;
enum class Flags {
NO_KERNEL = 0 ,
ALLOW_KERNEL = 1 ,
} ;
struct Range {
uint32_t base ;
uint32_t size ;
Flags flags ;
} ;
static const Range ranges [ ] = {
{ 0x08000000 , Memory : : RAM_DOUBLE_SIZE , Flags : : ALLOW_KERNEL } ,
{ 0x00010000 , Memory : : SCRATCHPAD_SIZE , Flags : : NO_KERNEL } ,
{ 0x04000000 , 0x00800000 , Flags : : NO_KERNEL } ,
} ;
static const uint32_t extraBits [ ] = {
0x00000000 ,
0x40000000 ,
0x80000000 ,
} ;
for ( const auto & range : ranges ) {
size_t testBits = range . flags = = Flags : : ALLOW_KERNEL ? 3 : 2 ;
for ( size_t i = 0 ; i < testBits ; + + i ) {
uint32_t base = range . base | extraBits [ i ] ;
EXPECT_TRUE ( Memory : : IsValidAddress ( base ) ) ;
EXPECT_TRUE ( Memory : : IsValidAddress ( base + range . size - 1 ) ) ;
EXPECT_FALSE ( Memory : : IsValidAddress ( base + range . size ) ) ;
EXPECT_FALSE ( Memory : : IsValidAddress ( base - 1 ) ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , range . size ) , range . size ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , range . size + 1 ) , range . size ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , range . size - 1 ) , range . size - 1 ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , 0 ) , 0 ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , 0x80000001 ) , range . size ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , 0x40000001 ) , range . size ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , 0x20000001 ) , range . size ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( base , 0x10000001 ) , range . size ) ;
2021-03-28 19:42:29 -07:00
EXPECT_EQ_HEX ( Memory : : ValidSize ( base + range . size - 0x10 , 0x20000001 ) , 0x10 ) ;
2019-09-28 08:40:41 -07:00
}
}
2021-04-21 19:32:22 -07:00
EXPECT_FALSE ( Memory : : IsValidAddress ( 0x00015000 ) ) ;
EXPECT_FALSE ( Memory : : IsValidAddress ( 0x04900000 ) ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( 0x00015000 , 4 ) , 0 ) ;
EXPECT_EQ_HEX ( Memory : : ValidSize ( 0x04900000 , 4 ) , 0 ) ;
2019-09-28 08:40:41 -07:00
return true ;
}
2021-05-06 01:31:38 +02:00
static bool TestPath ( ) {
// Also test the Path class while we're at it.
Path path ( " /asdf/jkl/ " ) ;
EXPECT_EQ_STR ( path . ToString ( ) , std : : string ( " /asdf/jkl " ) ) ;
Path path2 ( " /asdf/jkl " ) ;
2021-05-09 18:38:48 +02:00
EXPECT_EQ_STR ( path2 . NavigateUp ( ) . ToString ( ) , std : : string ( " /asdf " ) ) ;
2021-05-06 01:31:38 +02:00
Path path3 = path2 / " foo/bar " ;
2021-05-09 18:38:48 +02:00
EXPECT_EQ_STR ( path3 . WithExtraExtension ( " .txt " ) . ToString ( ) , std : : string ( " /asdf/jkl/foo/bar.txt " ) ) ;
2021-05-06 01:31:38 +02:00
2022-09-30 12:26:30 +03:00
EXPECT_EQ_STR ( Path ( " foo.bar/hello " ) . GetFileExtension ( ) , std : : string ( ) ) ;
2021-05-09 18:38:48 +02:00
EXPECT_EQ_STR ( Path ( " foo.bar/hello.txt " ) . WithReplacedExtension ( " .txt " , " .html " ) . ToString ( ) , std : : string ( " foo.bar/hello.html " ) ) ;
2021-05-09 19:06:02 +02:00
EXPECT_EQ_STR ( Path ( " C: \\ Yo " ) . NavigateUp ( ) . ToString ( ) , std : : string ( " C: " ) ) ;
2022-01-29 13:36:25 -08:00
# if PPSSPP_PLATFORM(WINDOWS)
2021-05-09 19:06:02 +02:00
EXPECT_EQ_STR ( Path ( " C: " ) . NavigateUp ( ) . ToString ( ) , std : : string ( " / " ) ) ;
2021-05-09 18:38:48 +02:00
EXPECT_EQ_STR ( Path ( " C: \\ Yo " ) . GetDirectory ( ) , std : : string ( " C: " ) ) ;
EXPECT_EQ_STR ( Path ( " C: \\ Yo " ) . GetFilename ( ) , std : : string ( " Yo " ) ) ;
EXPECT_EQ_STR ( Path ( " C: \\ Yo \\ Lo " ) . GetDirectory ( ) , std : : string ( " C:/Yo " ) ) ;
EXPECT_EQ_STR ( Path ( " C: \\ Yo \\ Lo " ) . GetFilename ( ) , std : : string ( " Lo " ) ) ;
2022-03-01 21:07:17 -08:00
EXPECT_EQ_STR ( Path ( R " ( \\ host \ share \f ilename) " ) . GetRootVolume ( ) . ToString ( ) , std : : string ( " //host " ) ) ;
EXPECT_EQ_STR ( Path ( R " ( \\ ? \ UNC \ share \f ilename) " ) . GetRootVolume ( ) . ToString ( ) , std : : string ( " //?/UNC " ) ) ;
EXPECT_EQ_STR ( Path ( R " ( \\ ? \ C: \ share \f ilename) " ) . GetRootVolume ( ) . ToString ( ) , std : : string ( " //?/C: " ) ) ;
2022-01-29 13:36:25 -08:00
# endif
2021-06-02 00:05:04 +02:00
2021-07-25 00:54:41 +02:00
std : : string computedPath ;
EXPECT_TRUE ( Path ( " /a/b " ) . ComputePathTo ( Path ( " /a/b/c/d/e " ) , computedPath ) ) ;
EXPECT_EQ_STR ( computedPath , std : : string ( " c/d/e " ) ) ;
EXPECT_TRUE ( Path ( " / " ) . ComputePathTo ( Path ( " /home/foo/bar " ) , computedPath ) ) ;
EXPECT_EQ_STR ( computedPath , std : : string ( " home/foo/bar " ) ) ;
2021-06-02 00:05:04 +02:00
2023-01-02 22:07:14 +01:00
EXPECT_TRUE ( Path ( " /a/b " ) . ComputePathTo ( Path ( " /a/b " ) , computedPath ) ) ;
EXPECT_EQ_STR ( computedPath , std : : string ( ) ) ;
2021-05-06 01:31:38 +02:00
return true ;
}
2021-03-14 11:01:51 +01:00
static bool TestAndroidContentURI ( ) {
static const char * treeURIString = " content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO " ;
static const char * directoryURIString = " content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO " ;
2021-08-07 12:17:31 +02:00
static const char * fileTreeURIString = " content://com.android.externalstorage.documents/tree/primary%3APSP%20ISO/document/primary%3APSP%20ISO%2FTekken%206.iso " ;
static const char * fileNonTreeString = " content://com.android.externalstorage.documents/document/primary%3APSP%2Fcrash_bad_execaddr.prx " ;
2023-05-16 14:34:28 +02:00
static const char * downloadURIString = " content://com.android.providers.downloads.documents/document/msf%3A10000000006 " ;
2021-03-14 11:01:51 +01:00
2021-05-16 00:01:21 +02:00
AndroidContentURI treeURI ;
2021-03-14 11:01:51 +01:00
EXPECT_TRUE ( treeURI . Parse ( std : : string ( treeURIString ) ) ) ;
2021-05-16 00:01:21 +02:00
AndroidContentURI dirURI ;
2021-04-26 23:34:20 +02:00
EXPECT_TRUE ( dirURI . Parse ( std : : string ( directoryURIString ) ) ) ;
2021-08-07 12:17:31 +02:00
AndroidContentURI fileTreeURI ;
EXPECT_TRUE ( fileTreeURI . Parse ( std : : string ( fileTreeURIString ) ) ) ;
AndroidContentURI fileTreeURICopy ;
EXPECT_TRUE ( fileTreeURICopy . Parse ( std : : string ( fileTreeURIString ) ) ) ;
2021-05-16 00:01:21 +02:00
AndroidContentURI fileURI ;
2021-08-07 12:17:31 +02:00
EXPECT_TRUE ( fileURI . Parse ( std : : string ( fileNonTreeString ) ) ) ;
2021-04-26 23:34:20 +02:00
2021-08-07 12:17:31 +02:00
EXPECT_EQ_STR ( fileTreeURI . GetLastPart ( ) , std : : string ( " Tekken 6.iso " ) ) ;
2021-04-26 23:34:20 +02:00
2021-08-07 12:17:31 +02:00
EXPECT_TRUE ( treeURI . TreeContains ( fileTreeURI ) ) ;
2021-03-14 11:01:51 +01:00
2021-08-07 12:17:31 +02:00
EXPECT_TRUE ( fileTreeURI . CanNavigateUp ( ) ) ;
fileTreeURI . NavigateUp ( ) ;
EXPECT_FALSE ( fileTreeURI . CanNavigateUp ( ) ) ;
2023-03-21 10:42:23 +01:00
2021-08-07 12:17:31 +02:00
EXPECT_EQ_STR ( fileTreeURI . FilePath ( ) , fileTreeURI . RootPath ( ) ) ;
2021-03-14 11:01:51 +01:00
2021-08-07 12:17:31 +02:00
EXPECT_EQ_STR ( fileTreeURI . ToString ( ) , std : : string ( directoryURIString ) ) ;
2021-06-02 00:05:04 +02:00
2021-07-25 00:54:41 +02:00
std : : string diff ;
2021-08-07 12:17:31 +02:00
EXPECT_TRUE ( dirURI . ComputePathTo ( fileTreeURICopy , diff ) ) ;
2021-06-02 00:05:04 +02:00
EXPECT_EQ_STR ( diff , std : : string ( " Tekken 6.iso " ) ) ;
2021-08-07 12:17:31 +02:00
EXPECT_EQ_STR ( fileURI . GetFileExtension ( ) , std : : string ( " .prx " ) ) ;
2023-05-16 16:05:58 +02:00
EXPECT_TRUE ( fileURI . CanNavigateUp ( ) ) ; // Can now virtually navigate up one step from these.
2021-08-07 12:17:31 +02:00
2023-05-16 16:05:58 +02:00
// These are annoying because they hide the actual filename, and we can't get at a parent folder.
// Decided to handle the ':' as a directory separator for navigation purposes, which fixes the problem (though not the extension thing).
2023-05-16 14:34:28 +02:00
AndroidContentURI downloadURI ;
EXPECT_TRUE ( downloadURI . Parse ( std : : string ( downloadURIString ) ) ) ;
EXPECT_EQ_STR ( downloadURI . GetLastPart ( ) , std : : string ( " 10000000006 " ) ) ;
2023-05-16 16:05:58 +02:00
EXPECT_TRUE ( downloadURI . CanNavigateUp ( ) ) ;
EXPECT_TRUE ( downloadURI . NavigateUp ( ) ) ;
// While this is not an openable valid content URI, we can still get something that we can concatenate a filename on top of.
EXPECT_EQ_STR ( downloadURI . ToString ( ) , std : : string ( " content://com.android.providers.downloads.documents/document/msf%3A " ) ) ;
EXPECT_EQ_STR ( downloadURI . GetLastPart ( ) , std : : string ( " msf: " ) ) ;
downloadURI = downloadURI . WithComponent ( " myfile " ) ;
EXPECT_EQ_STR ( downloadURI . ToString ( ) , std : : string ( " content://com.android.providers.downloads.documents/document/msf%3Amyfile " ) ) ;
2021-03-14 11:01:51 +01:00
return true ;
}
2021-09-25 10:59:54 -07:00
class UnitTestWordWrapper : public WordWrapper {
public :
2024-05-24 12:43:38 +02:00
UnitTestWordWrapper ( std : : string_view str , float maxW , int flags )
2021-09-25 10:59:54 -07:00
: WordWrapper ( str , maxW , flags ) {
}
protected :
2024-05-24 12:39:20 +02:00
float MeasureWidth ( std : : string_view str ) override {
2021-09-25 10:59:54 -07:00
// Simple case for unit testing.
int w = 0 ;
2024-05-24 12:39:20 +02:00
for ( UTF8 utf ( str ) ; ! utf . end ( ) ; ) {
2021-09-25 11:37:10 -07:00
uint32_t c = utf . next ( ) ;
switch ( c ) {
2021-09-25 10:59:54 -07:00
case ' ' :
case ' . ' :
w + = 1 ;
break ;
2021-09-25 11:37:10 -07:00
case 0x00AD :
// No width for soft hyphens.
break ;
2021-09-25 10:59:54 -07:00
default :
w + = 2 ;
break ;
}
}
return w ;
}
} ;
# define EXPECT_WORDWRAP_EQ_STR(a, l, f, b) if (UnitTestWordWrapper(a, l, f).Wrapped() != b) { printf("%s: Test Fail (%d, %s)\n%s\nvs\n%s\n", __FUNCTION__, l, #f, UnitTestWordWrapper(a, l, f).Wrapped().c_str(), std::string(b).c_str()); return false; }
static bool TestWrapText ( ) {
// If there's enough space, it shouldn't wrap. This is exactly enough.
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 10 , 0 , " Hello " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 10 , FLAG_WRAP_TEXT , " Hello " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 10 , FLAG_ELLIPSIZE_TEXT , " Hello " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 10 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Hello " ) ;
// Try a single word that doesn't fit in the space.
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 6 , 0 , " Hello " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 6 , FLAG_WRAP_TEXT , " Hel \n lo " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 6 , FLAG_ELLIPSIZE_TEXT , " H... " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello " , 6 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " H... " ) ;
// Now, multiple words.
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 14 , 0 , " Hello goodbye " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 14 , FLAG_WRAP_TEXT , " Hello \n goodbye " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 14 , FLAG_ELLIPSIZE_TEXT , " Hello... " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 14 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Hello \n goodbye " ) ;
2021-09-25 11:46:00 -07:00
// Multiple words with something short after...
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye yes " , 14 , 0 , " Hello goodbye " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye yes " , 14 , FLAG_WRAP_TEXT , " Hello \n goodbye \n yes " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye yes " , 14 , FLAG_ELLIPSIZE_TEXT , " Hello... " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye yes " , 14 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Hello \n goodbye \n yes " ) ;
2021-09-25 10:59:54 -07:00
// Now, multiple words, but only the first fits.
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 10 , 0 , " Hello " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 10 , FLAG_WRAP_TEXT , " Hello \n goodb \n ye " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 10 , FLAG_ELLIPSIZE_TEXT , " Hel... " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello goodbye " , 10 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Hello \n goo... " ) ;
2021-09-25 11:37:10 -07:00
// How about the shy character?
const std : : string shyTestString = StringFromFormat ( " Very%c%clong " , 0xC2 , 0xAD ) ;
EXPECT_WORDWRAP_EQ_STR ( shyTestString . c_str ( ) , 10 , 0 , shyTestString ) ;
EXPECT_WORDWRAP_EQ_STR ( shyTestString . c_str ( ) , 10 , FLAG_WRAP_TEXT , " Very- \n long " ) ;
EXPECT_WORDWRAP_EQ_STR ( shyTestString . c_str ( ) , 10 , FLAG_ELLIPSIZE_TEXT , " Very... " ) ;
EXPECT_WORDWRAP_EQ_STR ( shyTestString . c_str ( ) , 10 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Very- \n long " ) ;
2021-09-25 12:01:41 -07:00
// Newlines should not be removed and should influence wrapping.
EXPECT_WORDWRAP_EQ_STR ( " Hello \n goodbye yes \n no " , 14 , 0 , " Hello \n goodbye " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello \n goodbye yes \n no " , 14 , FLAG_WRAP_TEXT , " Hello \n goodbye \n yes \n no " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello \n goodbye yes \n no " , 14 , FLAG_ELLIPSIZE_TEXT , " Hello \n goodb... \n no " ) ;
EXPECT_WORDWRAP_EQ_STR ( " Hello \n goodbye yes \n no " , 14 , FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT , " Hello \n goodbye \n yes \n no " ) ;
2021-09-25 10:59:54 -07:00
return true ;
}
2022-12-01 17:09:54 +01:00
static bool TestSmallDataConvert ( ) {
float f [ 4 ] = { 1.0f / 255.0f , 2.0f / 255.0f , 3.0f / 255.0f , 4.0f / 255.f } ;
uint32_t result = Float4ToUint8x4_NoClamp ( f ) ;
EXPECT_EQ_HEX ( result , 0x04030201 ) ;
result = Float4ToUint8x4 ( f ) ;
EXPECT_EQ_HEX ( result , 0x04030201 ) ;
return true ;
}
2023-02-11 14:45:01 +01:00
float DepthSliceFactor ( u32 useFlags ) ;
2023-02-10 14:07:45 +01:00
static bool TestDepthMath ( ) {
// These are in normalized space.
2023-02-11 01:11:02 +01:00
static const volatile float testValues [ ] = { 0.0f , 0.1f , 0.5f , M_PI / 4.0f , 0.9f , 1.0f } ;
2023-02-10 14:07:45 +01:00
2023-02-11 11:23:00 +01:00
// Flag combinations that can happen (any combination not included here is invalid, see comment
// over in GPUStateUtils.cpp):
2023-02-10 14:07:45 +01:00
static const u32 useFlagsArray [ ] = {
0 ,
GPU_USE_ACCURATE_DEPTH ,
GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT ,
2023-02-11 01:11:02 +01:00
GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH ,
GPU_USE_DEPTH_CLAMP | GPU_USE_ACCURATE_DEPTH | GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT , // Here, GPU_SCALE_DEPTH_FROM_24BIT_TO_16BIT should take precedence over USE_DEPTH_CLAMP.
2023-02-10 14:07:45 +01:00
} ;
2023-02-11 11:24:02 +01:00
static const float expectedScale [ ] = { 65535.0f , 262140.0f , 16777215.0f , 65535.0f , 16777215.0f , } ;
2023-02-11 01:11:02 +01:00
static const float expectedOffset [ ] = { 0.0f , 0.375f , 0.498047f , 0.0f , 0.498047f , } ;
2023-02-10 14:34:33 +01:00
EXPECT_REL_EQ_FLOAT ( 100000.0f , 100001.0f , 0.00001f ) ;
2023-02-10 14:07:45 +01:00
for ( int j = 0 ; j < ARRAY_SIZE ( useFlagsArray ) ; j + + ) {
u32 useFlags = useFlagsArray [ j ] ;
printf ( " j: %d useflags: %d \n " , j , useFlags ) ;
DepthScaleFactors factors = GetDepthScaleFactors ( useFlags ) ;
EXPECT_EQ_FLOAT ( factors . ScaleU16 ( ) , expectedScale [ j ] ) ;
2023-02-10 14:34:33 +01:00
EXPECT_REL_EQ_FLOAT ( factors . Offset ( ) , expectedOffset [ j ] , 0.00001f ) ;
2023-02-11 14:37:48 +01:00
EXPECT_REL_EQ_FLOAT ( factors . Scale ( ) , DepthSliceFactor ( useFlags ) , 0.0001f ) ;
2023-02-10 14:07:45 +01:00
for ( int i = 0 ; i < ARRAY_SIZE ( testValues ) ; i + + ) {
2023-02-10 14:34:33 +01:00
float testValue = testValues [ i ] * 65535.0f ;
float encoded = factors . EncodeFromU16 ( testValue ) ;
2023-02-10 14:07:45 +01:00
float decodedU16 = factors . DecodeToU16 ( encoded ) ;
2023-02-10 14:34:33 +01:00
EXPECT_REL_EQ_FLOAT ( decodedU16 , testValue , 0.0001f ) ;
2023-02-10 14:07:45 +01:00
}
}
return true ;
}
2023-04-01 13:43:42 +02:00
bool TestInputMapping ( ) {
InputMapping mapping ;
2023-05-26 18:40:13 +02:00
mapping . deviceId = DEVICE_ID_PAD_0 ;
2023-04-01 13:43:42 +02:00
mapping . keyCode = 20 ;
InputMapping mapping2 ;
2023-05-26 18:40:13 +02:00
mapping2 . deviceId = DEVICE_ID_PAD_8 ;
2023-04-01 13:43:42 +02:00
mapping2 . keyCode = 38 ;
std : : string cfg = mapping . ToConfigString ( ) ;
InputMapping parsedMapping = InputMapping : : FromConfigString ( cfg ) ;
EXPECT_EQ_INT ( parsedMapping . deviceId , mapping . deviceId ) ;
EXPECT_EQ_INT ( parsedMapping . keyCode , mapping . keyCode ) ;
using KeyMap : : MultiInputMapping ;
MultiInputMapping multi ( mapping ) ;
EXPECT_EQ_STR ( multi . ToConfigString ( ) , mapping . ToConfigString ( ) ) ;
multi . mappings . push_back ( mapping2 ) ;
EXPECT_FALSE ( multi . EqualsSingleMapping ( mapping ) ) ;
EXPECT_TRUE ( multi . mappings . contains ( mapping2 ) ) ;
EXPECT_TRUE ( multi . mappings . contains ( mapping ) ) ;
std : : string cfgMulti = multi . ToConfigString ( ) ;
EXPECT_EQ_STR ( cfgMulti , std : : string ( " 10-20:18-38 " ) ) ;
MultiInputMapping parsedMulti = MultiInputMapping : : FromConfigString ( cfgMulti ) ;
EXPECT_EQ_INT ( ( int ) parsedMulti . mappings . size ( ) , 2 ) ;
// OK, both single and multiple mappings parse. Let's now see if the old parsing can handle a multimapping.
// This is a requirement for the new format.
InputMapping parsedMultiSingle = InputMapping : : FromConfigString ( cfgMulti ) ; // yes this is an intentional mismatch
// We should get the first mapping.
EXPECT_TRUE ( parsedMultiSingle = = mapping ) ;
return true ;
}
2023-04-23 14:36:34 +02:00
bool TestEscapeMenuString ( ) {
char c ;
std : : string temp = UnescapeMenuString ( " &File " , & c ) ;
EXPECT_EQ_INT ( ( int ) c , ( int ) ' F ' ) ;
EXPECT_EQ_STR ( temp , std : : string ( " File " ) ) ;
temp = UnescapeMenuString ( " U&til " , & c ) ;
EXPECT_EQ_INT ( ( int ) c , ( int ) ' t ' ) ;
EXPECT_EQ_STR ( temp , std : : string ( " Util " ) ) ;
temp = UnescapeMenuString ( " Ed&it " , nullptr ) ;
EXPECT_EQ_STR ( temp , std : : string ( " Edit " ) ) ;
temp = UnescapeMenuString ( " Cut && Paste " , nullptr ) ;
EXPECT_EQ_STR ( temp , std : : string ( " Cut & Paste " ) ) ;
2023-04-23 14:42:36 +02:00
temp = UnescapeMenuString ( " &A&B " , & c ) ;
EXPECT_EQ_STR ( temp , std : : string ( " AB " ) ) ;
EXPECT_EQ_INT ( ( int ) c , ( int ) ' A ' ) ;
2023-04-23 14:36:34 +02:00
return true ;
}
2023-04-01 13:43:42 +02:00
2023-07-16 16:16:47 +02:00
bool TestSubstitutions ( ) {
std : : string output = ApplySafeSubstitutions ( " %3 %2 %1 " , " a " , " b " , " c " ) ;
EXPECT_EQ_STR ( output , std : : string ( " c b a " ) ) ;
return true ;
}
2023-09-25 18:42:23 +02:00
bool TestIniFile ( ) {
const std : : string testLine = " adsf \\ #asdf = jkl \\ # # comment " ;
const std : : string testLine2 = " # Just a comment " ;
std : : string temp ;
2024-11-30 01:24:54 +01:00
ParsedIniLine line ( testLine ) ;
2023-09-25 18:42:23 +02:00
line . Reconstruct ( & temp ) ;
EXPECT_EQ_STR ( testLine , temp ) ;
temp . clear ( ) ;
2024-11-30 01:24:54 +01:00
ParsedIniLine line2 ( testLine2 ) ;
line2 . Reconstruct ( & temp ) ;
2023-09-25 18:42:23 +02:00
EXPECT_EQ_STR ( testLine2 , temp ) ;
return true ;
}
2024-06-04 10:28:13 +02:00
inline u32 ReferenceRGBA5551ToRGBA8888 ( u16 src ) {
u8 r = Convert5To8 ( ( src > > 0 ) & 0x1F ) ;
u8 g = Convert5To8 ( ( src > > 5 ) & 0x1F ) ;
u8 b = Convert5To8 ( ( src > > 10 ) & 0x1F ) ;
u8 a = ( src > > 15 ) & 0x1 ;
a = ( a ) ? 0xff : 0 ;
return ( a < < 24 ) | ( b < < 16 ) | ( g < < 8 ) | r ;
2024-06-04 10:25:46 +02:00
}
2024-06-04 10:28:13 +02:00
inline u32 ReferenceRGB565ToRGBA8888 ( u16 src ) {
u8 r = Convert5To8 ( ( src > > 0 ) & 0x1F ) ;
u8 g = Convert6To8 ( ( src > > 5 ) & 0x3F ) ;
u8 b = Convert5To8 ( ( src > > 11 ) & 0x1F ) ;
u8 a = 0xFF ;
return ( a < < 24 ) | ( b < < 16 ) | ( g < < 8 ) | r ;
2024-06-04 10:25:46 +02:00
}
bool TestColorConv ( ) {
// Can exhaustively test the 16->32 conversions.
for ( int i = 0 ; i < 65536 ; i + + ) {
u16 col16 = i ;
2024-06-04 10:28:13 +02:00
u32 reference = ReferenceRGBA5551ToRGBA8888 ( col16 ) ;
u32 value = RGBA5551ToRGBA8888 ( col16 ) ;
2024-06-04 10:25:46 +02:00
EXPECT_EQ_INT ( reference , value ) ;
2024-06-04 10:28:13 +02:00
reference = ReferenceRGB565ToRGBA8888 ( col16 ) ;
value = RGB565ToRGBA8888 ( col16 ) ;
2024-06-04 10:25:46 +02:00
EXPECT_EQ_INT ( reference , value ) ;
}
return true ;
}
2024-11-21 23:15:17 +01:00
CharQueue GetQueue ( ) {
CharQueue queue ( 5 ) ;
return queue ;
}
bool TestCharQueue ( ) {
// We use a tiny block size for testing.
CharQueue queue = std : : move ( GetQueue ( ) ) ;
// Add 16 chars.
queue . push_back ( " abcdefghijkl " ) ;
queue . push_back ( " mnop " ) ;
std : : string testStr ;
queue . iterate_blocks ( [ & ] ( const char * buf , size_t sz ) {
testStr . append ( buf , sz ) ;
return true ;
} ) ;
EXPECT_EQ_STR ( testStr , std : : string ( " abcdefghijklmnop " ) ) ;
EXPECT_EQ_CHAR ( queue . peek ( 11 ) , ' l ' ) ;
EXPECT_EQ_CHAR ( queue . peek ( 12 ) , ' m ' ) ;
EXPECT_EQ_CHAR ( queue . peek ( 15 ) , ' p ' ) ;
EXPECT_EQ_INT ( queue . block_count ( ) , 3 ) ; // Didn't fit in the first block, so the two pushes above should have each created one additional block.
EXPECT_EQ_INT ( queue . size ( ) , 16 ) ;
char dest [ 15 ] ;
EXPECT_EQ_INT ( queue . pop_front_bulk ( dest , 4 ) , 4 ) ;
EXPECT_EQ_INT ( queue . size ( ) , 12 ) ;
EXPECT_EQ_MEM ( dest , " abcd " , 4 ) ;
EXPECT_EQ_INT ( queue . pop_front_bulk ( dest , 6 ) , 6 ) ;
EXPECT_EQ_INT ( queue . size ( ) , 6 ) ;
EXPECT_EQ_MEM ( dest , " efghij " , 6 ) ;
queue . push_back ( " qr " ) ;
EXPECT_EQ_INT ( queue . pop_front_bulk ( dest , 4 ) , 4 ) ; // should pop off klmn
EXPECT_EQ_MEM ( dest , " klmn " , 4 ) ;
EXPECT_EQ_INT ( queue . size ( ) , 4 ) ;
EXPECT_EQ_CHAR ( queue . peek ( 3 ) , ' r ' ) ;
queue . pop_front_bulk ( dest , 4 ) ;
EXPECT_EQ_MEM ( dest , " opqr " , 4 ) ;
EXPECT_TRUE ( queue . empty ( ) ) ;
2024-11-22 10:13:41 +01:00
queue . push_back ( " asdf " ) ;
EXPECT_EQ_INT ( queue . next_crlf_offset ( ) , - 1 ) ;
queue . push_back ( " \r \r \n " ) ;
EXPECT_EQ_INT ( queue . next_crlf_offset ( ) , 5 ) ;
2024-11-21 23:15:17 +01:00
return true ;
}
bool TestBuffer ( ) {
Buffer b = Buffer : : Void ( ) ;
b . Append ( " hello " ) ;
b . Append ( " world " ) ;
std : : string temp ;
b . Take ( 10 , & temp ) ;
EXPECT_EQ_STR ( temp , std : : string ( " helloworld " ) ) ;
return true ;
}
2014-11-07 22:40:28 -08:00
typedef bool ( * TestFunc ) ( ) ;
struct TestItem {
const char * name ;
TestFunc func ;
} ;
# define TEST_ITEM(name) { #name, &Test ##name, }
2014-11-29 11:37:45 +01:00
bool TestArmEmitter ( ) ;
2015-03-06 21:42:19 +01:00
bool TestArm64Emitter ( ) ;
2014-12-06 02:00:57 +01:00
bool TestX64Emitter ( ) ;
2022-08-27 15:43:44 -07:00
bool TestRiscVEmitter ( ) ;
2020-10-19 20:38:43 +02:00
bool TestShaderGenerators ( ) ;
2022-01-29 18:34:14 -08:00
bool TestSoftwareGPUJit ( ) ;
2022-07-24 10:37:54 -07:00
bool TestIRPassSimplify ( ) ;
2020-11-28 00:12:06 +01:00
bool TestThreadManager ( ) ;
2023-05-02 11:35:45 +02:00
bool TestVFS ( ) ;
2014-12-06 02:00:57 +01:00
2014-11-07 22:40:28 -08:00
TestItem availableTests [ ] = {
2020-08-29 08:45:50 -07:00
# if PPSSPP_ARCH(ARM64) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
2015-03-06 21:42:19 +01:00
TEST_ITEM ( Arm64Emitter ) ,
# endif
2020-08-29 08:45:50 -07:00
# if PPSSPP_ARCH(ARM) || PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
2014-11-07 22:40:28 -08:00
TEST_ITEM ( ArmEmitter ) ,
2015-03-06 21:42:19 +01:00
# endif
2020-08-29 08:45:50 -07:00
# if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86)
2014-12-06 02:00:57 +01:00
TEST_ITEM ( X64Emitter ) ,
2022-08-27 15:43:44 -07:00
# endif
# if PPSSPP_ARCH(AMD64) || PPSSPP_ARCH(X86) || PPSSPP_ARCH(RISCV64)
TEST_ITEM ( RiscVEmitter ) ,
2014-12-07 21:05:50 -08:00
# endif
2015-07-03 15:25:40 -07:00
TEST_ITEM ( VertexJit ) ,
2015-03-06 21:42:19 +01:00
TEST_ITEM ( Asin ) ,
TEST_ITEM ( SinCos ) ,
2014-11-07 22:40:28 -08:00
TEST_ITEM ( VFPUSinCos ) ,
TEST_ITEM ( MathUtil ) ,
TEST_ITEM ( Parsers ) ,
2022-07-24 10:37:54 -07:00
TEST_ITEM ( IRPassSimplify ) ,
2014-11-07 22:40:28 -08:00
TEST_ITEM ( Jit ) ,
2015-09-12 11:21:54 +02:00
TEST_ITEM ( MatrixTranspose ) ,
TEST_ITEM ( ParseLBN ) ,
2018-03-25 18:52:16 -07:00
TEST_ITEM ( QuickTexHash ) ,
2019-06-14 18:03:10 +02:00
TEST_ITEM ( CLZ ) ,
2021-03-28 19:42:29 -07:00
TEST_ITEM ( MemMap ) ,
2020-10-19 20:38:43 +02:00
TEST_ITEM ( ShaderGenerators ) ,
2022-01-29 18:34:14 -08:00
TEST_ITEM ( SoftwareGPUJit ) ,
2021-05-06 01:31:38 +02:00
TEST_ITEM ( Path ) ,
2021-03-14 11:01:51 +01:00
TEST_ITEM ( AndroidContentURI ) ,
2020-11-28 00:12:06 +01:00
TEST_ITEM ( ThreadManager ) ,
2021-09-25 10:59:54 -07:00
TEST_ITEM ( WrapText ) ,
2022-09-01 10:46:47 +02:00
TEST_ITEM ( TinySet ) ,
2023-05-16 23:29:41 +02:00
TEST_ITEM ( FastVec ) ,
2022-12-01 17:09:54 +01:00
TEST_ITEM ( SmallDataConvert ) ,
2023-02-10 14:07:45 +01:00
TEST_ITEM ( DepthMath ) ,
2023-04-01 13:43:42 +02:00
TEST_ITEM ( InputMapping ) ,
2023-04-23 14:36:34 +02:00
TEST_ITEM ( EscapeMenuString ) ,
2023-05-02 11:35:45 +02:00
TEST_ITEM ( VFS ) ,
2023-07-16 16:16:47 +02:00
TEST_ITEM ( Substitutions ) ,
2023-09-25 18:42:23 +02:00
TEST_ITEM ( IniFile ) ,
2024-06-04 10:25:46 +02:00
TEST_ITEM ( ColorConv ) ,
2024-11-21 23:15:17 +01:00
TEST_ITEM ( CharQueue ) ,
TEST_ITEM ( Buffer ) ,
2014-11-07 22:40:28 -08:00
} ;
2014-03-12 18:09:28 +01:00
int main ( int argc , const char * argv [ ] ) {
2023-09-23 11:19:27 -07:00
SetCurrentThreadName ( " UnitTest " ) ;
2024-07-26 15:59:01 +02:00
TimeInit ( ) ;
2023-09-23 11:19:27 -07:00
2024-04-29 11:13:35 +02:00
printf ( " CPU name: %s \n " , cpu_info . cpu_string ) ;
printf ( " ABI: %s \n " , GetCompilerABI ( ) ) ;
// In case we're on ARM, assume these are available.
2014-03-12 18:09:28 +01:00
cpu_info . bNEON = true ;
cpu_info . bVFP = true ;
cpu_info . bVFPv3 = true ;
cpu_info . bVFPv4 = true ;
2014-01-10 21:32:08 -08:00
g_Config . bEnableLogging = true ;
2014-11-07 22:40:28 -08:00
bool allTests = false ;
TestFunc testFunc = nullptr ;
if ( argc > = 2 ) {
if ( ! strcasecmp ( argv [ 1 ] , " all " ) ) {
allTests = true ;
}
for ( auto f : availableTests ) {
if ( ! strcasecmp ( argv [ 1 ] , f . name ) ) {
testFunc = f . func ;
break ;
}
}
}
if ( allTests ) {
int passes = 0 ;
int fails = 0 ;
for ( auto f : availableTests ) {
if ( f . func ( ) ) {
+ + passes ;
} else {
printf ( " %s: FAILED \n " , f . name ) ;
+ + fails ;
}
}
if ( passes > 0 ) {
printf ( " %d tests passed. \n " , passes ) ;
}
if ( fails > 0 ) {
2024-04-29 11:13:35 +02:00
printf ( " %d tests failed! \n " , fails ) ;
2014-11-07 22:40:28 -08:00
return 2 ;
}
} else if ( testFunc = = nullptr ) {
2024-04-29 10:31:42 +02:00
fprintf ( stderr , " You may select a test to run by passing an argument, either \" all \" or one or more of the below. \n " ) ;
2014-11-07 22:40:28 -08:00
fprintf ( stderr , " \n " ) ;
fprintf ( stderr , " Available tests: \n " ) ;
for ( auto f : availableTests ) {
fprintf ( stderr , " * %s \n " , f . name ) ;
}
return 1 ;
} else {
if ( ! testFunc ( ) ) {
return 2 ;
}
}
2013-01-17 20:59:28 +01:00
return 0 ;
2014-01-09 12:09:07 +01:00
}