1
0
forked from 0ad/0ad

# add boatload of self-tests

(these are the old self-tests ripped out as well as new ones)
note: have not run yet; several would surely fail if they run (can't
anyway until cxxtest is installed)

This was SVN commit r3912.
This commit is contained in:
janwas 2006-05-31 04:17:45 +00:00
parent 1ead202b24
commit e0dfbe719d
20 changed files with 2094 additions and 0 deletions

View File

@ -0,0 +1,105 @@
#include <cxxtest/TestSuite.h>
#include "lib/res/file/archive_builder.h"
class TestArchiveBuilder : public CxxTest::TestSuite
{
const char* const archive_fn;
const size_t NUM_FILES;
const size_t MAX_FILE_SIZE;
std::set<const char*> existing_names;
const char* gen_random_name()
{
// 10 chars is enough for (10-1)*5 bits = 45 bits > u32
char name_tmp[10];
for(;;)
{
u32 rand_num = rand(0, 100000);
base32(4, (u8*)&rand_num, name_tmp);
// store filename in atom pool
const char* atom_fn = file_make_unique_fn_copy(name_tmp);
// done if the filename is unique (not been generated yet)
if(existing_names.find(atom_fn) == existing_names.end())
{
existing_names.insert(atom_fn);
break;
}
}
}
struct TestFile
{
off_t size;
u8* data; // must be delete[]-ed after comparing
};
// (must be separate array and end with NULL entry (see Filenames))
const char* filenames[NUM_FILES+1];
void generate_random_files()
{
TestFile files[NUM_FILES];
for(size_t i = 0; i < NUM_FILES; i++)
{
const size_t size = rand(0, MAX_FILE_SIZE);
u8* data = new u8[size];
// random data won't compress at all, and we want to exercise
// the uncompressed codepath as well => make some of the files
// easily compressible (much less values).
const bool make_easily_compressible = (rand(0, 100) > 50);
if(make_easily_compressible)
{
for(size_t i = 0; i < size; i++)
data[i] = rand() & 0x0F;
}
else
{
for(size_t i = 0; i < size; i++)
data[i] = rand() & 0xFF;
}
filenames[i] = gen_random_name();
files[i].size = size;
files[i].data = data;
TS_ASSERT_OK(vfs_store(filenames[i], data, size, FILE_NO_AIO));
}
filenames[NUM_FILES] = NULL;
}
public:
TestArchiveBuilder()
: archive_fn("test_archive_random_data.zip"),
NUM_FILES(300), MAX_FILE_SIZE(20000) {}
void test()
{
generate_random_files();
// build and open archive
TS_ASSERT_OK(archive_build(archive_fn, filenames));
Handle ha = archive_open(archive_fn);
TS_ASSERT(ha > 0);
// read in each file and compare file contents
for(size_t i = 0; i < num_files; i++)
{
File f;
TS_ASSERT_OK(afile_open(ha, filenames[i], 0, 0, &f));
FileIOBuf buf = FILE_BUF_ALLOC;
ssize_t bytes_read = afile_read(&f, 0, files[i].size, &buf);
TS_ASSERT_EQUAL(bytes_read, files[i].size);
TS_ASSERT_SAME_DATA(buf, files[i].data);
TS_ASSERT_OK(file_buf_free(buf));
SAFE_ARRAY_DELETE(files[i].data);
}
TS_ASSERT_OK(archive_close(ha));
}
};

View File

@ -0,0 +1,47 @@
#include <cxxtest/TestSuite.h>
#include "lib/res/file/compression.h"
class TestCompression : public CxxTest::TestSuite
{
public:
void test()
{
// generate random input data
const size_t data_size = 10000;
u8 data[data_size];
for(size_t i = 0; i < data_size; i++)
data[i] = rand() & 0xFF;
void* cdata; size_t csize;
u8 ucdata[data_size];
// compress
uintptr_t c = comp_alloc(CT_COMPRESSION, CM_DEFLATE);
{
TS_ASSERT(c != 0);
TS_ASSERT_OK(comp_alloc_output(c, in_size));
const ssize_t cdata_produced = comp_feed(c, in, in_size);
TS_ASSERT(cdata_produced > 0);
TS_ASSERT_OK(comp_finish(c, &cdata, &csize));
TS_ASSERT(cdata_produced <= csize); // can't have produced more than total
}
// decompress
uintptr_t d = comp_alloc(CT_DECOMPRESSION, CM_DEFLATE);
{
TS_ASSERT(d != 0);
TS_ASSERT_OK(comp_set_output(ucdata, data_size));
const ssize_t ucdata_produced = comp_feed(c, cdata, cdata_size);
TS_ASSERT(ucdata_produced > 0);
void* ucdata_final; size_t ucsize_final;
TS_ASSERT_OK(comp_finish(c, &ucdata_final, &ucsize_final));
TS_ASSERT(ucdata_produced <= ucsize_final); // can't have produced more than total
TS_ASSERT_EQUAL(ucdata_final, ucdata); // output buffer address is same
TS_ASSERT_EQUAL(ucsize_final, data_size); // correct amount of output
}
// verify data survived intact
TS_ASSERT_SAME_DATA(data, ucdata, data_size);
}
};

View File

@ -0,0 +1,63 @@
#include <cxxtest/TestSuite.h>
#include "lib/res/file/file_cache.h"
class TestFileCache : public CxxTest::TestSuite
{
public:
void test_cache_allocator()
{
// allocated address -> its size
typedef std::map<void*, size_t> AllocMap;
AllocMap allocations;
// put allocator through its paces by allocating several times
// its capacity (this ensures memory is reused)
srand(1);
size_t total_size_used = 0;
while(total_size_used < 4*MAX_CACHE_SIZE)
{
size_t size = rand(1, MAX_CACHE_SIZE/4);
total_size_used += size;
void* p;
// until successful alloc:
for(;;)
{
p = cache_allocator.alloc(size);
if(p)
break;
// out of room - remove a previous allocation
// .. choose one at random
size_t chosen_idx = (size_t)rand(0, (uint)allocations.size());
AllocMap::iterator it = allocations.begin();
for(; chosen_idx != 0; chosen_idx--)
++it;
cache_allocator.dealloc((u8*)it->first, it->second);
allocations.erase(it);
}
// must not already have been allocated
TS_ASSERT_EQUAL(allocations.find(p), allocations.end());
allocations[p] = size;
}
// reset to virginal state
cache_allocator.reset();
}
void test_file_cache()
{
// we need a unique address for file_cache_add, but don't want to
// actually put it in the atom_fn storage (permanently clutters it).
// just increment this pointer (evil but works since it's not used).
// const char* atom_fn = (const char*)1;
// give to file_cache
// file_cache_add((FileIOBuf)p, size, atom_fn++);
file_cache_reset();
TS_ASSERT(file_cache.empty());
// note: even though everything has now been freed,
// the freelists may be a bit scattered already.
}
};

View File

@ -0,0 +1,64 @@
#include <cxxtest/TestSuite.h>
#include "lib/res/file/path.h"
class TestPath : public CxxTest::TestSuite
{
public:
void test_conversion()
{
char N_path[PATH_MAX] = {0};
TS_ASSERT_OK(file_make_native_path("a/b/c", N_path));
#if OS_WIN
TS_ASSERT_STR_EQUAL(N_path, "a\\b\\c");
#else
TS_ASSERT_STR_EQUAL(N_path, "a/b/c");
#endif
char P_path[PATH_MAX] = {0};
TS_ASSERT_OK(file_make_portable_path("a\\b\\c", P_path));
#if OS_WIN
TS_ASSERT_STR_EQUAL(P_path, "a/b/c"));
#else
// sounds strange, but correct: on non-Windows, \\ didn't
// get recognized as separators and weren't converted.
TS_ASSERT_STR_EQUAL(P_path, "a\\b\\c"));
#endif
}
// file_make_full_*_path is left untested (hard to do so)
void test_atom()
{
path_init();
// file_make_unique_fn_copy
// .. return same address for same string?
const char* atom1 = file_make_unique_fn_copy("a/bc/def");
const char* atom2 = file_make_unique_fn_copy("a/bc/def");
TS_ASSERT_EQUAL(atom1, atom2);
// .. early out (already in pool) check works?
const char* atom3 = file_make_unique_fn_copy(atom1);
TS_ASSERT_EQUAL(atom3, atom1);
// path_is_atom_fn
// is it reported as in pool?
TS_ASSERT(path_is_atom_fn(atom1));
// file_get_random_name
// see if the atom added above eventually comes out when a
// random one is returned from the pool.
int tries_left;
for(tries_left = 1000; tries != 0; tries--)
{
const char* random_name = file_get_random_name();
if(random_name == atom1)
break;
}
TS_ASSERT(tries_left != 0);
}
};

View File

@ -0,0 +1,23 @@
#include <cxxtest/TestSuite.h>
#include "lib/res/file/zip.h"
class TestZip : public CxxTest::TestSuite
{
public:
void test_fat_timedate_conversion()
{
// note: FAT time stores second/2, which means converting may
// end up off by 1 second.
time_t t, converted_t;
t = time(0);
converted_t = time_t_from_FAT(FAT_from_time_t(t));
TS_ASSERT_DELTA(t, converted_t, 2);
t++;
converted_t = time_t_from_FAT(FAT_from_time_t(t));
TS_ASSERT_DELTA(t, converted_t, 2);
}
};

View File

@ -0,0 +1,130 @@
#include <cxxtest/TestSuite.h>
#include "lib/self_test.h"
#include "lib/res/graphics/tex.h"
class TestTex : public CxxTest::TestSuite
{
void generate_encode_decode_compare(size_t w, size_t h, uint flags, uint bpp,
const char* filename)
{
// generate test data
const size_t size = w*h*bpp/8;
u8* img = new u8[size];
for(size_t i = 0; i < size; i++)
img[i] = rand() & 0xFF;
// wrap in Tex
Tex t;
TS_ASSERT_OK(tex_wrap(w, h, bpp, flags, img, &t));
// encode to file format
DynArray da;
TS_ASSERT_OK(tex_encode(&t, filename, &da));
memset(&t, 0, sizeof(t));
// decode from file format
MEM_DTOR dtor = 0; // we'll free da manually
TS_ASSERT_OK(tex_decode(da.base, da.cur_size, dtor, &t));
// make sure pixel format gets converted completely to plain
TS_ASSERT_OK(tex_transform_to(&t, 0));
// compare img
TS_ASSERT_SAME_DATA(tex_get_data(&t), img);
// cleanup
TS_ASSERT_OK(tex_free(&t));
TS_ASSERT_OK(da_free(&da));
delete[] img;
}
public:
// this also covers BGR and orientation transforms.
void test_encode_decode()
{
// for each codec
TexCodecVTbl* c = 0;
for(;;)
{
c = tex_codec_next(c);
if(!c)
break;
// get an extension that this codec will support
// (so that tex_encode uses this codec)
char extension[30] = {'.'};
strcpy_s(extension+1, 29, c->name);
// .. make sure the c->name hack worked
TexCodecVTbl* correct_c;
TS_ASSERT_OK(tex_codec_for_filename(extension, &correct_c));
TS_ASSERT_EQUAL(c, correct_c);
// for each test width/height combination
const uint widths [] = { 4, 5, 4, 256, 384 };
const uint heights[] = { 4, 4, 5, 256, 256 };
for(size_t i = 0; i < ARRAY_SIZE(widths); i++)
{
// for each bit depth
for(uint bpp = 8; bpp <= 32; bpp += 8)
{
uint flags = 0;
if(!strcmp(extension, ".dds")
flags |= (TEX_DXT&3); // DXT3
if(bpp == 8)
flags |= TEX_GRAY;
else if(bpp == 32)
flags |= TEX_ALPHA;
// normal
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
// top-down
flags &= ~TEX_ORIENTATION; flags |= TEX_TOP_DOWN;
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
// bottom up
flags &= ~TEX_ORIENTATION; flags |= TEX_BOTTOM_UP;
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
flags &= ~TEX_ORIENTATION;
flags |= TEX_BGR;
// bgr, normal
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
// bgr, top-down
flags ~= TEX_ORIENTATION; flags |= TEX_TOP_DOWN;
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
// bgr, bottom up
flags ~= TEX_ORIENTATION; flags |= TEX_BOTTOM_UP;
generate_encode_decode_compare(widths[i], heights[i], flags, bpp, extension);
} // for bpp
} // for width/height
} // foreach codec
}
// have mipmaps be created for a test image; check resulting size and pixels
void test_mipmap_create()
{
const u8 img[] = { 0x10,0x20,0x30, 0x40,0x60,0x80, 0xA0,0xA4,0xA8, 0xC0,0xC1,0xC2 };
// assumes 2x2 box filter algorithm with rounding
const u8 mipmap[] = { 0x70,0x79,0x87 };
Tex t;
TS_ASSERT_OK(tex_wrap(2, 2, 24, 0, img, &t));
TS_ASSERT_OK(tex_transform_to(&t, TEX_MIPMAPS));
const u8* const out_img = tex_get_data(&t);
TS_ASSERT_EQUAL(tex_img_size(&t, 12+3));
TS_ASSERT_SAME_DATA(out_img, img, 12);
TS_ASSERT_SAME_DATA(out_img+12, mipmap, 3);
}
void test_img_size()
{
Tex t;
TS_ASSERT_OK(tex_wrap(100, 100, 32, TEX_ALPHA, 0, &t));
TS_ASSERT_EQUAL(tex_img_size(&t), 40000);
// DXT rounds up to 4x4 blocks; DXT1a is 4bpp
TS_ASSERT_OK(tex_wrap(97, 97, 32, DXT1A, 0, &t));
TS_ASSERT_EQUAL(tex_img_size(&t), 5000);
}
};

View File

@ -0,0 +1,54 @@
#include <cxxtest/TestSuite.h>
#include "lib/sysdep/sysdep.h"
#include "lib/posix.h" // fminf etc.
class TestSysdep : public CxxTest::TestSuite
{
public:
void test_float_int()
{
TS_ASSERT_EQUAL(i32_from_float(0.99999f), 0);
TS_ASSERT_EQUAL(i32_from_float(1.0f), 1);
TS_ASSERT_EQUAL(i32_from_float(1.01f), 1);
TS_ASSERT_EQUAL(i32_from_float(5.6f), 5);
TS_ASSERT_EQUAL(i32_from_double(0.99999), 0);
TS_ASSERT_EQUAL(i32_from_double(1.0), 1);
TS_ASSERT_EQUAL(i32_from_double(1.01), 1);
TS_ASSERT_EQUAL(i32_from_double(5.6), 5);
TS_ASSERT_EQUAL(i64_from_double(0.99999), 0LL);
TS_ASSERT_EQUAL(i64_from_double(1.0), 1LL);
TS_ASSERT_EQUAL(i64_from_double(1.01), 1LL);
TS_ASSERT_EQUAL(i64_from_double(5.6), 5LL);
}
void test_round()
{
TS_ASSERT_EQUAL(rintf(0.99999f), 1.0f);
TS_ASSERT_EQUAL(rintf(1.0f), 1.0f);
TS_ASSERT_EQUAL(rintf(1.01f), 1.0f);
TS_ASSERT_EQUAL(rintf(5.6f), 5.0f);
TS_ASSERT_EQUAL(rint(0.99999), 1.0);
TS_ASSERT_EQUAL(rint(1.0), 1.0);
TS_ASSERT_EQUAL(rint(1.01), 1.0);
TS_ASSERT_EQUAL(rint(5.6), 5.0);
}
void test_min_max()
{
TS_ASSERT_EQUAL(fminf(0.0f, 10000.0f), 0.0f);
TS_ASSERT_EQUAL(fminf(100.0f, 10000.0f), 100.0f);
TS_ASSERT_EQUAL(fminf(-1.0f, 2.0f), -1.0f);
TS_ASSERT_EQUAL(fminf(-2.0f, 1.0f), -2.0f);
TS_ASSERT_EQUAL(fminf(0.001f, 0.00001f), 0.00001f);
TS_ASSERT_EQUAL(fmaxf(0.0f, 10000.0f), 10000.0f);
TS_ASSERT_EQUAL(fmaxf(100.0f, 10000.0f), 10000.0f);
TS_ASSERT_EQUAL(fmaxf(-1.0f, 2.0f), 2.0f);
TS_ASSERT_EQUAL(fmaxf(-2.0f, 1.0f), 1.0f);
TS_ASSERT_EQUAL(fmaxf(0.001f, 0.00001f), 0.001f);
}
};

View File

@ -0,0 +1,28 @@
#include <cxxtest/TestSuite.h>
#include "lib/sysdep/ia32.h"
// note: ia32_i??_from_*, ia32_rint*, ia32_fm??f are all tested within
// sysdep to avoid test duplication (both the ia32 versions and
// the portable fallback must behave the same).
class TestIA32: public CxxTest::TestSuite
{
public:
void test_rdtsc()
{
// must increase monotonously
const u64 c1 = ia32_rdtsc();
const u64 c2 = ia32_rdtsc();
const u64 c3 = ia32_rdtsc();
TS_ASSERT(c1 < c2 && c2 < c3);
}
void test_ia32_cap()
{
// make sure the really common/basic caps end up reported as true
TS_ASSERT(ia32_cap(IA32_CAP_FPU));
TS_ASSERT(ia32_cap(IA32_CAP_TSC));
TS_ASSERT(ia32_cap(IA32_CAP_MMX));
}
};

View File

@ -0,0 +1,236 @@
// note: this is more of an on-demand display of the stack trace than
// self-test of it.
// TODO: compare against known-good result?
// problem: results may differ by compiler (e.g. due to differing STL)
#include <cxxtest/TestSuite.h>
#include "lib/debug.h" // no wdbg_sym interface needed
class TestWdbgSym : public CxxTest::TestSuite
{
#pragma optimize("", off)
static void test_array()
{
struct Small
{
int i1;
int i2;
};
struct Large
{
double d1;
double d2;
double d3;
double d4;
};
Large large_array_of_large_structs[8] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(large_array_of_large_structs);
Large small_array_of_large_structs[2] = { { 0.0,0.0,0.0,0.0 } }; UNUSED2(small_array_of_large_structs);
Small large_array_of_small_structs[8] = { { 1,2 } }; UNUSED2(large_array_of_small_structs);
Small small_array_of_small_structs[2] = { { 1,2 } }; UNUSED2(small_array_of_small_structs);
int ints[] = { 1,2,3,4,5 }; UNUSED2(ints);
wchar_t chars[] = { 'w','c','h','a','r','s',0 }; UNUSED2(chars);
// note: prefer simple error (which also generates stack trace) to
// exception, because it is guaranteed to work (no issues with the
// debugger swallowing exceptions).
DISPLAY_ERROR(L"wdbg_sym self test: check if stack trace below is ok.");
//RaiseException(0xf001,0,0,0);
}
// also used by test_stl as an element type
struct Nested
{
int nested_member;
struct Nested* self_ptr;
};
static void test_udt()
{
Nested nested = { 123 }; nested.self_ptr = &nested;
typedef struct
{
u8 s1;
u8 s2;
char s3;
}
Small;
Small small__ = { 0x55, 0xaa, -1 }; UNUSED2(small__);
struct Large
{
u8 large_member_u8;
std::string large_member_string;
double large_member_double;
}
large = { 0xff, "large struct string", 123456.0 }; UNUSED2(large);
class Base
{
int base_int;
std::wstring base_wstring;
public:
Base()
: base_int(123), base_wstring(L"base wstring")
{
}
};
class Derived : private Base
{
double derived_double;
public:
Derived()
: derived_double(-1.0)
{
}
}
derived;
test_array();
}
// STL containers and their contents
static void test_stl()
{
std::vector<std::wstring> v_wstring;
v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2");
std::deque<int> d_int;
d_int.push_back(1); d_int.push_back(2); d_int.push_back(3);
std::deque<std::string> d_string;
d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c");
std::list<float> l_float;
l_float.push_back(0.1f); l_float.push_back(0.2f); l_float.push_back(0.3f); l_float.push_back(0.4f);
std::map<std::string, int> m_string_int;
m_string_int.insert(std::make_pair<std::string,int>("s5", 5));
m_string_int.insert(std::make_pair<std::string,int>("s6", 6));
m_string_int.insert(std::make_pair<std::string,int>("s7", 7));
std::map<int, std::string> m_int_string;
m_int_string.insert(std::make_pair<int,std::string>(1, "s1"));
m_int_string.insert(std::make_pair<int,std::string>(2, "s2"));
m_int_string.insert(std::make_pair<int,std::string>(3, "s3"));
std::map<int, int> m_int_int;
m_int_int.insert(std::make_pair<int,int>(1, 1));
m_int_int.insert(std::make_pair<int,int>(2, 2));
m_int_int.insert(std::make_pair<int,int>(3, 3));
STL_HASH_MAP<std::string, int> hm_string_int;
hm_string_int.insert(std::make_pair<std::string,int>("s5", 5));
hm_string_int.insert(std::make_pair<std::string,int>("s6", 6));
hm_string_int.insert(std::make_pair<std::string,int>("s7", 7));
STL_HASH_MAP<int, std::string> hm_int_string;
hm_int_string.insert(std::make_pair<int,std::string>(1, "s1"));
hm_int_string.insert(std::make_pair<int,std::string>(2, "s2"));
hm_int_string.insert(std::make_pair<int,std::string>(3, "s3"));
STL_HASH_MAP<int, int> hm_int_int;
hm_int_int.insert(std::make_pair<int,int>(1, 1));
hm_int_int.insert(std::make_pair<int,int>(2, 2));
hm_int_int.insert(std::make_pair<int,int>(3, 3));
std::set<uintptr_t> s_uintptr;
s_uintptr.insert(0x123); s_uintptr.insert(0x456);
// empty
std::deque<u8> d_u8_empty;
std::list<Nested> l_nested_empty;
std::map<double,double> m_double_empty;
std::multimap<int,u8> mm_int_empty;
std::set<uint> s_uint_empty;
std::multiset<char> ms_char_empty;
std::vector<double> v_double_empty;
std::queue<double> q_double_empty;
std::stack<double> st_double_empty;
#if HAVE_STL_HASH
STL_HASH_MAP<double,double> hm_double_empty;
STL_HASH_MULTIMAP<double,std::wstring> hmm_double_empty;
STL_HASH_SET<double> hs_double_empty;
STL_HASH_MULTISET<double> hms_double_empty;
#endif
#if HAVE_STL_SLIST
STL_SLIST<double> sl_double_empty;
#endif
std::string str_empty;
std::wstring wstr_empty;
test_udt();
// uninitialized
std::deque<u8> d_u8_uninit;
std::list<Nested> l_nested_uninit;
std::map<double,double> m_double_uninit;
std::multimap<int,u8> mm_int_uninit;
std::set<uint> s_uint_uninit;
std::multiset<char> ms_char_uninit;
std::vector<double> v_double_uninit;
std::queue<double> q_double_uninit;
std::stack<double> st_double_uninit;
#if HAVE_STL_HASH
STL_HASH_MAP<double,double> hm_double_uninit;
STL_HASH_MULTIMAP<double,std::wstring> hmm_double_uninit;
STL_HASH_SET<double> hs_double_uninit;
STL_HASH_MULTISET<double> hms_double_uninit;
#endif
#if HAVE_STL_SLIST
STL_SLIST<double> sl_double_uninit;
#endif
std::string str_uninit;
std::wstring wstr_uninit;
}
// also exercises all basic types because we need to display some values
// anyway (to see at a glance whether symbol engine addrs are correct)
static void test_addrs(int p_int, double p_double, char* p_pchar, uintptr_t p_uintptr)
{
debug_printf("\nTEST_ADDRS\n");
uint l_uint = 0x1234;
bool l_bool = true; UNUSED2(l_bool);
wchar_t l_wchars[] = L"wchar string";
enum TestEnum { VAL1=1, VAL2=2 } l_enum = VAL1;
u8 l_u8s[] = { 1,2,3,4 };
void (*l_funcptr)(void) = test_stl;
static double s_double = -2.718;
static char s_chars[] = {'c','h','a','r','s',0};
static void (*s_funcptr)(int, double, char*, uintptr_t) = test_addrs;
static void* s_ptr = (void*)(uintptr_t)0x87654321;
static HDC s_hdc = (HDC)0xff0;
debug_printf("p_int addr=%p val=%d\n", &p_int, p_int);
debug_printf("p_double addr=%p val=%g\n", &p_double, p_double);
debug_printf("p_pchar addr=%p val=%s\n", &p_pchar, p_pchar);
debug_printf("p_uintptr addr=%p val=%lu\n", &p_uintptr, p_uintptr);
debug_printf("l_uint addr=%p val=%u\n", &l_uint, l_uint);
debug_printf("l_wchars addr=%p val=%ws\n", &l_wchars, l_wchars);
debug_printf("l_enum addr=%p val=%d\n", &l_enum, l_enum);
debug_printf("l_u8s addr=%p val=%d\n", &l_u8s, l_u8s);
debug_printf("l_funcptr addr=%p val=%p\n", &l_funcptr, l_funcptr);
test_stl();
int uninit_int; UNUSED2(uninit_int);
float uninit_float; UNUSED2(uninit_float);
double uninit_double; UNUSED2(uninit_double);
bool uninit_bool; UNUSED2(uninit_bool);
HWND uninit_hwnd; UNUSED2(uninit_hwnd);
}
#pragma optimize("", on)
public:
void test()
{
test_addrs(123, 3.1415926535897932384626, "pchar string", 0xf00d);
}
};

View File

@ -0,0 +1,178 @@
#include <cxxtest/TestSuite.h>
class TestRingbuf : public CxxTest::TestSuite
{
const size_t N = 49; // RingBuf capacity
const int S = 100; // number of test items
public:
void test_insert_remove()
{
RingBuf<int, N> buf;
for(int i = 1; i < S; i++)
{
buf.push_back(i);
TS_ASSERT_EQUAL(buf.front(), i);
buf.pop_front();
}
TS_ASSERT(buf.size() == 0 && buf.empty());
}
void test_fill_overwrite_old()
{
RingBuf<int, N> buf;
for(int i = 1; i < S; i++)
buf.push_back(i);
TS_ASSERT_EQUAL(buf.size(), N);
int first = buf.front();
TS_ASSERT_EQUAL(first, (int)(S-1 -N +1));
for(size_t i = 0; i < N; i++)
{
TS_ASSERT_EQUAL(buf.front(), first);
first++;
buf.pop_front();
}
TS_ASSERT(buf.size() == 0 && buf.empty());
}
void test_randomized_insert_remove()
{
srand(1);
RingBuf<int, N> buf;
std::deque<int> deq;
for(uint rep = 0; rep < 1000; rep++)
{
uint rnd_op = rand(0, 10);
// 70% - insert
if(rnd_op >= 3)
{
int item = rand();
buf.push_back(item);
deq.push_back(item);
int excess_items = (int)deq.size() - N;
if(excess_items > 0)
{
for(int i = 0; i < excess_items; i++)
{
deq.pop_front();
}
}
}
// 30% - pop front (only if not empty)
else if(!deq.empty())
{
buf.pop_front();
deq.pop_front();
}
}
TS_ASSERT_EQUAL(buf.size(), deq.size());
RingBuf<int, N>::iterator begin = buf.begin(), end = buf.end();
TS_ASSERT(equal(begin, end, deq.begin()));
}
};
class TestCache: public CxxTest::TestSuite
{
public:
void test_cache_perf()
{
Cache<int, int, Landlord_Naive> c1;
Cache<int, int, Landlord_Naive, Divider_Recip> c1r;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Cached, Divider_Recip> c2r;
Cache<int, int, Landlord_Lazy> c3;
Cache<int, int, Landlord_Lazy, Divider_Recip> c3r;
#if defined(ENABLE_CACHE_POLICY_BENCHMARK) || 0
// set max priority, to reduce interference while measuring.
int old_policy; static sched_param old_param; // (static => 0-init)
pthread_getschedparam(pthread_self(), &old_policy, &old_param);
static sched_param max_param;
max_param.sched_priority = sched_get_priority_max(SCHED_FIFO);
pthread_setschedparam(pthread_self(), SCHED_FIFO, &max_param);
#define MEASURE(c, desc)\
{\
srand(1);\
int cnt = 1;\
TIMER_BEGIN(desc);\
for(int i = 0; i < 30000; i++)\
{\
/* 70% add (random objects) */\
bool add = rand(1,10) < 7;\
if(add)\
{\
int key = cnt++;\
int val = cnt++;\
size_t size = (size_t)rand(1,100);\
uint cost = (uint)rand(1,100);\
c.add(key, val, size, cost);\
}\
else\
{\
size_t size;\
int value;\
c.remove_least_valuable(&value, &size);\
}\
}\
TIMER_END(desc);\
}
MEASURE(c1, "naive")
MEASURE(c1r, "naiverecip")
MEASURE(c2, "cached")
MEASURE(c2r, "cachedrecip")
MEASURE(c3, "lazy")
MEASURE(c3r, "lazyrecip")
// restore previous policy and priority.
pthread_setschedparam(pthread_self(), old_policy, &old_param);
exit(1134);
#endif
}
// ensures all 3 variants of Landlord<> behave the same
void test_cache_policies()
{
Cache<int, int, Landlord_Naive > c1;
Cache<int, int, Landlord_Cached> c2;
Cache<int, int, Landlord_Lazy > c3;
srand(1);
int cnt = 1;
for(int i = 0; i < 1000; i++)
{
// 70% add (random objects)
bool add = rand(1,10) < 7;
if(add)
{
int key = cnt++;
int val = cnt++;
size_t size = (size_t)rand(1,100);
uint cost = (uint)rand(1,100);
c1.add(key, val, size, cost);
c2.add(key, val, size, cost);
c3.add(key, val, size, cost);
}
// 30% delete - make sure "least valuable" was same for all
else
{
size_t size1, size2, size3;
int value1, value2, value3;
bool removed1, removed2, removed3;
removed1 = c1.remove_least_valuable(&value1, &size1);
removed2 = c2.remove_least_valuable(&value2, &size2);
removed3 = c3.remove_least_valuable(&value3, &size3);
TS_ASSERT_EQUAL(removed1, removed2);
TS_ASSERT_EQUAL(removed2, removed3);
if (removed1)
{
TS_ASSERT_EQUAL(size1, size2);
TS_ASSERT_EQUAL(value1, value2);
TS_ASSERT_EQUAL(size2, size3);
TS_ASSERT_EQUAL(value2, value3);
}
} // else
} // for i
}
};

View File

@ -0,0 +1,44 @@
#include <cxxtest/TestSuite.h>
class TestAllocators : public CxxTest::TestSuite
{
public:
void test_da()
{
DynArray da;
// basic test of functionality (not really meaningful)
TS_ASSERT_OK(da_alloc(&da, 1000));
TS_ASSERT_OK(da_set_size(&da, 1000));
TS_ASSERT_OK(da_set_prot(&da, PROT_NONE));
TS_ASSERT_OK(da_free(&da));
// test wrapping existing mem blocks for use with da_read
u8 data[4] = { 0x12, 0x34, 0x56, 0x78 };
TS_ASSERT_OK(da_wrap_fixed(&da, data, sizeof(data)));
u8 buf[4];
TS_ASSERT_OK(da_read(&da, buf, 4));
TS_ASSERT_EQUAL(read_le32(buf), 0x78563412); // read correct value
TS_ASSERT(da_read(&da, buf, 1) < 0); // no more data left
TS_ASSERT_OK(da_free(&da));
}
void test_expand()
{
}
void test_matrix()
{
// not much we can do here; allocate a matrix, write to it and
// make sure it can be freed.
// (note: can't check memory layout because "matrix" is int** -
// array of pointers. the matrix interface doesn't guarantee
// that data comes in row-major order after the row pointers)
int** m = (int**)matrix_alloc(3, 3, sizeof(int));
m[0][0] = 1;
m[0][1] = 2;
m[1][0] = 3;
m[2][2] = 4;
matrix_free((void**)m);
}
};

View File

@ -0,0 +1,38 @@
#include <cxxtest/TestSuite.h>
class TestByteOrder : public CxxTest::TestSuite
{
public:
void test()
{
const u32 x = 0x01234567u;
const u8 LS_byte = *(u8*)&x;
// little endian
if(LS_byte, 0x67)
{
TS_ASSERT_EQUAL(to_le16(0x0123u), 0x0123u);
TS_ASSERT_EQUAL(to_le32(0x01234567u), 0x01234567u);
TS_ASSERT_EQUAL(to_le64(0x0123456789ABCDEFu), 0x0123456789ABCDEFu);
TS_ASSERT_EQUAL(to_be16(0x0123u), 0x2301u);
TS_ASSERT_EQUAL(to_be32(0x01234567u), 0x67452301u);
TS_ASSERT_EQUAL(to_be64(0x0123456789ABCDEFu), 0xEFCDAB8967452301u);
}
// big endian
else if(LS_byte, 0x01)
{
TS_ASSERT_EQUAL(to_le16(0x0123u), 0x2301u);
TS_ASSERT_EQUAL(to_le32(0x01234567u), 0x67452301u);
TS_ASSERT_EQUAL(to_le64(0x0123456789ABCDEFu), 0xEFCDAB8967452301u);
TS_ASSERT_EQUAL(to_be16(0x0123u), 0x0123u);
TS_ASSERT_EQUAL(to_be32(0x01234567u), 0x01234567u);
TS_ASSERT_EQUAL(to_be64(0x0123456789ABCDEFu), 0x0123456789ABCDEFu);
}
else
TS_FAIL("endian determination failed");
// note: no need to test read_?e* / write_?e* - they are
// trivial wrappers on top of to_?e*.
}
};

231
source/lib/tests/test_lib.h Normal file
View File

@ -0,0 +1,231 @@
#include <cxxtest/TestSuite.h>
#include "lib/self_test.h"
#include "lib/lib.h"
class TestLib : public CxxTest::TestSuite
{
public:
void test_fnv_hash()
{
TS_ASSERT_EQUAL(fnv_hash(""), 0x811C9DC5u); // verify initial value
const u32 h1 = fnv_hash("abcdef");
TS_ASSERT_EQUAL(h1, 0xFF478A2A); // verify value for simple string
TS_ASSERT_EQUAL(fnv_hash ("abcdef", 6), h1); // same result if hashing buffer
TS_ASSERT_EQUAL(fnv_lc_hash("ABcDeF", 6), h1); // same result if case differs
TS_ASSERT_EQUAL(fnv_hash64(""), 0xCBF29CE484222325ull); // verify initial value
const u64 h2 = fnv_hash("abcdef");
TS_ASSERT_EQUAL(h2, 0xD80BDA3FBE244A0Aull); // verify value for simple string
TS_ASSERT_EQUAL(fnv_hash64("abcdef", 6), h2); // same result if hashing buffer
}
void test_is_pow2()
{
TS_ASSERT_EQUAL(is_pow2(0u), false);
TS_ASSERT_EQUAL(is_pow2(~0u), false);
TS_ASSERT_EQUAL(is_pow2(0x80000001), false);
TS_ASSERT_EQUAL(is_pow2(1), true);
TS_ASSERT_EQUAL(is_pow2(1u << 31), true);
}
void test_ilog2()
{
TS_ASSERT_EQUAL(ilog2(0u), -1);
TS_ASSERT_EQUAL(ilog2(3u), -1);
TS_ASSERT_EQUAL(ilog2(0xffffffffu), -1);
TS_ASSERT_EQUAL(ilog2(1u), 0);
TS_ASSERT_EQUAL(ilog2(256u), 8);
TS_ASSERT_EQUAL(ilog2(0x80000000u), 31);
}
void test_log2()
{
TS_ASSERT_EQUAL(log2(0u), 1u);
TS_ASSERT_EQUAL(log2(3u), 2u);
TS_ASSERT_EQUAL(log2(0xffffffffu), 32u);
TS_ASSERT_EQUAL(log2(1u), 0u);
TS_ASSERT_EQUAL(log2(256u), 8u);
TS_ASSERT_EQUAL(log2(0x80000000u), 31u);
}
void test_ilog2f()
{
TS_ASSERT_EQUAL(ilog2(0.f), 0);
TS_ASSERT_EQUAL(ilog2(1.f), 0);
TS_ASSERT_EQUAL(ilog2(3.f), 1);
TS_ASSERT_EQUAL(ilog2(256.f), 8);
}
void test_round_next_pow2()
{
TS_ASSERT_EQUAL(round_up_to_pow2(0u), 1u);
TS_ASSERT_EQUAL(round_up_to_pow2(1u), 2u);
TS_ASSERT_EQUAL(round_up_to_pow2(127u), 128u);
TS_ASSERT_EQUAL(round_up_to_pow2(128u), 128u);
TS_ASSERT_EQUAL(round_up_to_pow2(129u), 256u);
}
void test_round_up()
{
TS_ASSERT_EQUAL(round_up( 0u, 16u), 0u);
TS_ASSERT_EQUAL(round_up( 4u, 16u), 16u);
TS_ASSERT_EQUAL(round_up(15u, 16u), 16u);
TS_ASSERT_EQUAL(round_up(20u, 32u), 32u);
TS_ASSERT_EQUAL(round_up(29u, 32u), 32u);
TS_ASSERT_EQUAL(round_up(0x1000u, 0x1000u), 0x1000u);
TS_ASSERT_EQUAL(round_up(0x1001u, 0x1000u), 0x2000u);
TS_ASSERT_EQUAL(round_up(0x1900u, 0x1000u), 0x2000u);
}
void test_round_down()
{
TS_ASSERT_EQUAL(round_down( 0u, 16u), 0u);
TS_ASSERT_EQUAL(round_down( 4u, 16u), 0u);
TS_ASSERT_EQUAL(round_down(15u, 16u), 0u);
TS_ASSERT_EQUAL(round_down(20u, 16u), 16u);
TS_ASSERT_EQUAL(round_down(29u, 16u), 16u);
TS_ASSERT_EQUAL(round_down(0x1900u, 0x1000u), 0x1000u);
TS_ASSERT_EQUAL(round_down(0x2001u, 0x2000u), 0x2000u);
}
// 16-bit saturating arithmetic
void test_addusw()
{
TS_ASSERT_EQUAL(addusw(4u, 0x100u), 0x0104u);
TS_ASSERT_EQUAL(addusw(0u, 0xFFFFu), 0xFFFFu);
TS_ASSERT_EQUAL(addusw(0x8000u, 0x8000u), 0xFFFFu);
TS_ASSERT_EQUAL(addusw(0xFFF0u, 0x0004u), 0xFFF4u);
TS_ASSERT_EQUAL(addusw(0xFFFFu, 0xFFFFu), 0xFFFFu);
}
void test_subusw()
{
TS_ASSERT_EQUAL(subusw(4u, 0x100u), 0u);
TS_ASSERT_EQUAL(subusw(100u, 90u), 10u);
TS_ASSERT_EQUAL(subusw(0x8000u, 0x8000u), 0u);
TS_ASSERT_EQUAL(subusw(0x0FFFu, 0xFFFFu), 0u);
TS_ASSERT_EQUAL(subusw(0xFFFFu, 0x0FFFu), 0xF000u);
}
void test_movzx()
{
const char d1[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 };
const char d2[] = { 0x43, 0x12, 0x23, 0xA4 };
TS_ASSERT_EQUAL(movzx_64le(d1, 1), 0x01ull);
TS_ASSERT_EQUAL(movzx_64le(d1, 2), 0x0201ull);
TS_ASSERT_EQUAL(movzx_64le(d1, 8), 0x0807060504030201ull);
TS_ASSERT_EQUAL(movzx_64le(d2, 4), 0xA4231243ull);
TS_ASSERT_EQUAL(movzx_64le(d2+3, 1), 0xA4ull);
}
void test_movsx()
{
const char d1[] = { 0x09, 0xFE };
const char d2[] = { 0xD9, 0x2C, 0xDD, 0x8F };
const char d3[] = { 0x92, 0x26, 0x88, 0xF1, 0x35, 0xAC, 0x01, 0x83 };
TS_ASSERT_EQUAL(movsx_64le(d1, 1), 0x09ull);
TS_ASSERT_EQUAL(movsx_64le(d1, 2), 0xFFFFFFFFFFFFFE09ull);
TS_ASSERT_EQUAL(movsx_64le(d2, 4), 0xFFFFFFFF8FDD2CD9ull);
TS_ASSERT_EQUAL(movsx_64le(d3, 8), 0x8301AC35F1882692ull);
}
void test_hi_lo()
{
TS_ASSERT_EQUAL(u64_hi(0x0123456789ABCDEFull), 0x01234567u);
TS_ASSERT_EQUAL(u64_hi(0x0000000100000002ull), 0x00000001u);
TS_ASSERT_EQUAL(u64_lo(0x0123456789ABCDEFull), 0x89ABCDEFu);
TS_ASSERT_EQUAL(u64_lo(0x0000000100000002ull), 0x00000002u);
TS_ASSERT_EQUAL(u32_hi(0x01234567u), 0x0123u);
TS_ASSERT_EQUAL(u32_hi(0x00000001u), 0x0000u);
TS_ASSERT_EQUAL(u32_lo(0x01234567u), 0x4567u);
TS_ASSERT_EQUAL(u32_lo(0x00000001u), 0x0001u);
TS_ASSERT_EQUAL(u64_from_u32(0xFFFFFFFFu, 0x80000008u), 0xFFFFFFFF80000008ull);
TS_ASSERT_EQUAL(u32_from_u16(0x8000u, 0xFFFFu), 0x8000FFFFu);
}
// fp_to_u?? already validate the result.
void test_wildcard()
{
TS_ASSERT_EQUAL(match_wildcard("", ""), 1);
TS_ASSERT_EQUAL(match_wildcard("a", 0), 1); // NULL matches everything
TS_ASSERT_EQUAL(match_wildcard("abc", "abc") , 1); // direct match
TS_ASSERT_EQUAL(match_wildcard("abc", "???") , 1); // only ?
TS_ASSERT_EQUAL(match_wildcard("abc", "*" ) , 1); // only *
TS_ASSERT_EQUAL(match_wildcard("ab" , "a?" ) , 1); // trailing ?
TS_ASSERT_EQUAL(match_wildcard("abc", "a?c") , 1); // middle ?
TS_ASSERT_EQUAL(match_wildcard("abc", "?bc") , 1); // leading ?
TS_ASSERT_EQUAL(match_wildcard("abc", "a*" ) , 1); // trailing *
TS_ASSERT_EQUAL(match_wildcard("abcdef", "ab*ef"), 1); // middle *
TS_ASSERT_EQUAL(match_wildcard("abcdef", "*f" ), 1); // leading *
TS_ASSERT_EQUAL(match_wildcard("abcdef", "a?cd*"), 1); // ? and *
TS_ASSERT_EQUAL(match_wildcard("abcdef", "a*d?f"), 1); // * and ?
TS_ASSERT_EQUAL(match_wildcard("abcdef", "a*d*" ), 1); // multiple *
// unicode test pasted from the above; keep in sync!
TS_ASSERT_EQUAL(match_wildcardw(L"", L""), 1);
TS_ASSERT_EQUAL(match_wildcardw(L"a", 0), 1); // NULL matches everything
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"abc") , 1); // direct match
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"???") , 1); // only ?
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"*" ) , 1); // only *
TS_ASSERT_EQUAL(match_wildcardw(L"ab" , L"a?" ) , 1); // trailing ?
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"a?c") , 1); // middle ?
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"?bc") , 1); // leading ?
TS_ASSERT_EQUAL(match_wildcardw(L"abc", L"a*" ) , 1); // trailing *
TS_ASSERT_EQUAL(match_wildcardw(L"abcdef", L"ab*ef"), 1); // middle *
TS_ASSERT_EQUAL(match_wildcardw(L"abcdef", L"*f" ), 1); // leading *
TS_ASSERT_EQUAL(match_wildcardw(L"abcdef", L"a?cd*"), 1); // ? and *
TS_ASSERT_EQUAL(match_wildcardw(L"abcdef", L"a*d?f"), 1); // * and ?
TS_ASSERT_EQUAL(match_wildcardw(L"abcdef", L"a*d*" ), 1); // multiple *
}
void test_base32()
{
// compare against previous output (generated via this base32() call)
const u8 in[] = { 0x12, 0x57, 0x85, 0xA2, 0xF9, 0x41, 0xCD, 0x57, 0xF3 };
u8 out[20] = {0};
base32(ARRAY_SIZE(in), in, out);
const u8 correct_out[] = "CJLYLIXZI";
TS_ASSERT_SAME_DATA(out, correct_out, ARRAY_SIZE(correct_out));
}
void test_rand()
{
// complain if huge interval or min > max
TS_ASSERT_EQUAL(rand(1, 0), 0);
TS_ASSERT_EQUAL(rand(2, ~0u), 0);
// returned number must be in [min, max)
for(int i = 0; i < 100; i++)
{
uint min = rand(), max = min+rand();
uint x = rand(min, max);
TS_ASSERT(min <= x && x < max);
}
// make sure both possible values are hit
uint ones = 0, twos = 0;
for(int i = 0; i < 100; i++)
{
uint x = rand(1, 3);
// paranoia: don't use array (x might not be 1 or 2 - checked below)
if(x, 1) ones++; if(x, 2) twos++;
}
TS_ASSERT_EQUAL(ones+twos, 100);
TS_ASSERT(ones > 10 && twos > 10);
}
};

View File

@ -0,0 +1,211 @@
#include <cxxtest/TestSuite.h>
#include "lib/self_test.h"
#include "lib/posix.h"
#include "lib/lockfree.h"
// make sure the data structures work at all; doesn't test thread-safety.
class TestLockfreeBasic : public CxxTest::TestSuite
{
public:
void test_basic_single_threaded()
{
void* user_data;
const uint ENTRIES = 50;
// should be more than max # retired nodes to test release..() code
uintptr_t key = 0x1000;
uint sig = 10;
LFList list;
TS_ASSERT_OK(lfl_init(&list));
LFHash hash;
TS_ASSERT_OK(lfh_init(&hash, 8));
// add some entries; store "signatures" (ascending int values)
for(uint i = 0; i < ENTRIES; i++)
{
int was_inserted;
user_data = lfl_insert(&list, key+i, sizeof(int), &was_inserted);
TS_ASSERT(user_data != 0 && was_inserted);
*(uint*)user_data = sig+i;
user_data = lfh_insert(&hash, key+i, sizeof(int), &was_inserted);
TS_ASSERT(user_data != 0 && was_inserted);
*(uint*)user_data = sig+i;
}
// make sure all "signatures" are present in list
for(uint i = 0; i < ENTRIES; i++)
{
user_data = lfl_find(&list, key+i);
TS_ASSERT(user_data != 0);
TS_ASSERT_EQUAL(*(uint*)user_data, sig+i);
user_data = lfh_find(&hash, key+i);
TS_ASSERT(user_data != 0);
TS_ASSERT_EQUAL(*(uint*)user_data, sig+i);
}
lfl_free(&list);
lfh_free(&hash);
}
};
// known to fail on P4 due to mem reordering and lack of membars.
class TestMultithread : public CxxTest::TestSuite
{
// poor man's synchronization "barrier"
bool is_complete;
intptr_t num_active_threads;
LFList list;
LFHash hash;
typedef std::set<uintptr_t> KeySet;
typedef KeySet::const_iterator KeySetIt;
KeySet keys;
pthread_mutex_t mutex; // protects <keys>
static void* thread_func(void* arg)
{
debug_set_thread_name("LF_test");
const uintptr_t thread_number = (uintptr_t)arg;
atomic_add(&num_active_threads, 1);
// chosen randomly every iteration (int_value % 4)
enum TestAction
{
TA_FIND = 0,
TA_INSERT = 1,
TA_ERASE = 2,
TA_SLEEP = 3
};
static const char* const action_strings[] =
{
"find", "insert", "erase", "sleep"
};
while(!is_complete)
{
void* user_data;
const int action = rand(0, 4);
const uintptr_t key = rand(0, 100);
const int sleep_duration_ms = rand(0, 100);
debug_printf("thread %d: %s\n", thread_number, action_strings[action]);
//
pthread_mutex_lock(&mutex);
const bool was_in_set = keys.find(key) != keys.end();
if(action == TA_INSERT)
keys.insert(key);
else if(action == TA_ERASE)
keys.erase(key);
pthread_mutex_unlock(&mutex);
switch(action)
{
case TA_FIND:
{
user_data = lfl_find(&list, key);
TS_ASSERT(was_in_set == (user_data != 0));
if(user_data)
TS_ASSERT_EQUAL(*(uintptr_t*)user_data, ~key);
user_data = lfh_find(&hash, key);
// typical failure site if lockfree data structure has bugs.
TS_ASSERT(was_in_set == (user_data != 0));
if(user_data)
TS_ASSERT_EQUAL(*(uintptr_t*)user_data, ~key);
}
break;
case TA_INSERT:
{
int was_inserted;
user_data = lfl_insert(&list, key, sizeof(uintptr_t), &was_inserted);
TS_ASSERT(user_data != 0); // only triggers if out of memory
*(uintptr_t*)user_data = ~key; // checked above
TS_ASSERT(was_in_set == !was_inserted);
user_data = lfh_insert(&hash, key, sizeof(uintptr_t), &was_inserted);
TS_ASSERT(user_data != 0); // only triggers if out of memory
*(uintptr_t*)user_data = ~key; // checked above
TS_ASSERT(was_in_set == !was_inserted);
}
break;
case TA_ERASE:
{
int err;
err = lfl_erase(&list, key);
TS_ASSERT(was_in_set == (err == ERR_OK));
err = lfh_erase(&hash, key);
TS_ASSERT(was_in_set == (err == ERR_OK));
}
break;
case TA_SLEEP:
usleep(sleep_duration_ms*1000);
break;
default:
TS_FAIL(L"invalid TA_* action");
break;
} // switch
} // while !is_complete
atomic_add(&num_active_threads, -1);
TS_ASSERT(num_active_threads >= 0);
return 0;
}
public:
TestMultithread()
: is_complete(false), num_active_threads(0),
list(), hash(),
mutex(0) {}
void test_multithread()
{
// this test is randomized; we need deterministic results.
srand(1);
static const double TEST_LENGTH = 30.; // [seconds]
const double end_time = get_time() + TEST_LENGTH;
is_complete = false;
TS_ASSERT_OK(lfl_init(&list));
TS_ASSERT_OK(lfh_init(&hash, 128));
TS_ASSERT_OK(pthread_mutex_init(&mutex, 0));
// spin off test threads (many, to force preemption)
const uint NUM_THREADS = 16;
for(uintptr_t i = 0; i < NUM_THREADS; i++)
pthread_create(0, 0, thread_func, (void*)i);
// wait until time interval elapsed (if we get that far, all is well).
while(get_time() < end_time)
usleep(10*1000);
// signal and wait for all threads to complete (poor man's barrier -
// those aren't currently implemented in wpthread).
is_complete = true;
while(num_active_threads > 0)
usleep(5*1000);
lfl_free(&list);
lfh_free(&hash);
TS_ASSERT_OK(pthread_mutex_destroy(&mutex));
}
};

View File

@ -0,0 +1,215 @@
#include <cxxtest/TestSuite.h>
#include "lib/self_test.h"
#include "lib/path_util.h"
class TestPathUtil : public CxxTest::TestSuite
{
void TEST_APPEND(const char* path1, const char* path2, uint flags, const char* correct_result)
{
char dst[PATH_MAX] = {0};
TS_ASSERT_OK(path_append(dst, path1, path2, flags));
TS_ASSERT_STR_EQUAL(dst, correct_result);
}
// if correct_ret is ERR_FAIL, ignore correct_result.
void TEST_REPLACE(const char* src, const char* remove, const char* replace,
LibError correct_ret, const char* correct_result)
{
char dst[PATH_MAX] = {0};
TS_ASSERT_EQUAL(path_replace(dst, src, remove, replace), correct_ret);
if(correct_ret != ERR_FAIL)
TS_ASSERT_STR_EQUAL(dst, correct_result);
}
void TEST_NAME_ONLY(const char* path, const char* correct_result)
{
const char* result = path_name_only(path);
TS_ASSERT_STR_EQUAL(result, correct_result);
}
void TEST_LAST_COMPONENT(const char* path, const char* correct_result)
{
const char* result = path_last_component(path);
TS_ASSERT_STR_EQUAL(result, correct_result);
}
void TEST_STRIP_FN(const char* path_readonly, const char* correct_result)
{
char path[PATH_MAX];
path_copy(path, path_readonly);
path_strip_fn(path);
TS_ASSERT_STR_EQUAL(path, correct_result);
}
void TEST_PATH_EXT(const char* path, const char* correct_result)
{
const char* result = path_extension(path);
TS_ASSERT_STR_EQUAL(result, correct_result);
}
void TEST_PATH_PACKAGE(const char* path, const char* fn,
const char* correct_result)
{
PathPackage pp;
TS_ASSERT_OK(path_package_set_dir(&pp, path));
TS_ASSERT_OK(path_package_append_file(&pp, fn));
TS_ASSERT_STR_EQUAL(pp.path, correct_result);
}
public:
void test_subpath()
{
// obvious true
TS_ASSERT(path_is_subpath("abc/def/", "abc/def/") == true); // same
TS_ASSERT(path_is_subpath("abc/def/", "abc/") == true); // 2 is subpath
TS_ASSERT(path_is_subpath("abc/", "abc/def/") == true); // 1 is subpath
// nonobvious true
TS_ASSERT(path_is_subpath("", "") == true);
TS_ASSERT(path_is_subpath("abc/def/", "abc/def") == true); // no '/' !
// obvious false
TS_ASSERT(path_is_subpath("abc", "def") == false); // different, no path
// nonobvious false
TS_ASSERT(path_is_subpath("abc", "") == false); // empty comparand
// .. different but followed by common subdir
TS_ASSERT(path_is_subpath("abc/def/", "ghi/def/") == false);
TS_ASSERT(path_is_subpath("abc/def/", "abc/ghi") == false);
}
// TODO: can't test path validate yet without suppress-error-dialog
void test_append()
{
// simplest case
TEST_APPEND("abc", "def", 0, "abc/def");
// trailing slash
TEST_APPEND("abc", "def", PATH_APPEND_SLASH, "abc/def/");
// intervening slash
TEST_APPEND("abc/", "def", 0, "abc/def");
// nonportable intervening slash
TEST_APPEND("abc\\", "def", 0, "abc\\def");
// mixed path slashes
TEST_APPEND("abc", "def/ghi\\jkl", 0, "abc/def/ghi\\jkl");
// path1 empty
TEST_APPEND("", "abc/def/", 0, "abc/def/");
// path2 empty, no trailing slash
TEST_APPEND("abc/def", "", 0, "abc/def");
// path2 empty, require trailing slash
TEST_APPEND("abc/def", "", PATH_APPEND_SLASH, "abc/def/");
// require trailing slash, already exists
TEST_APPEND("abc/", "def/", PATH_APPEND_SLASH, "abc/def/");
}
void test_replace()
{
// no match
TEST_REPLACE("abc/def", "/def", "xx", ERR_FAIL, 0);
// normal case: match and remove
TEST_REPLACE("abc/def", "abc", "ok", ERR_OK, "ok/def");
// caller also stripping /
TEST_REPLACE("abc/def", "abc/", "ok", ERR_OK, "ok/def");
// empty remove
TEST_REPLACE("abc/def", "", "ok", ERR_OK, "ok/abc/def");
// empty replace
TEST_REPLACE("abc/def", "abc", "", ERR_OK, "def");
// remove entire string
TEST_REPLACE("abc/def", "abc/def", "", ERR_OK, "");
}
void test_name_only()
{
// path with filename
TEST_NAME_ONLY("abc/def", "def");
// nonportable path with filename
TEST_NAME_ONLY("abc\\def\\ghi", "ghi");
// mixed path with filename
TEST_NAME_ONLY("abc/def\\ghi", "ghi");
// mixed path with filename (2)
TEST_NAME_ONLY("abc\\def/ghi", "ghi");
// filename only
TEST_NAME_ONLY("abc", "abc");
// empty
TEST_NAME_ONLY("", "");
}
void test_last_component()
{
// path with filename
TEST_LAST_COMPONENT("abc/def", "def");
// nonportable path with filename
TEST_LAST_COMPONENT("abc\\def\\ghi", "ghi");
// mixed path with filename
TEST_LAST_COMPONENT("abc/def\\ghi", "ghi");
// mixed path with filename (2)
TEST_LAST_COMPONENT("abc\\def/ghi", "ghi");
// filename only
TEST_LAST_COMPONENT("abc", "abc");
// empty
TEST_LAST_COMPONENT("", "");
// now paths (mostly copied from above test series)
// path
TEST_LAST_COMPONENT("abc/def/", "def");
// nonportable path
TEST_LAST_COMPONENT("abc\\def\\ghi\\", "ghi");
// mixed path
TEST_LAST_COMPONENT("abc/def\\ghi/", "ghi");
// mixed path (2)
TEST_LAST_COMPONENT("abc\\def/ghi\\", "ghi");
}
void test_strip_fn()
{
// path with filename
TEST_STRIP_FN("abc/def", "abc/");
// nonportable path with filename
TEST_STRIP_FN("abc\\def\\ghi", "abc\\def\\");
// mixed path with filename
TEST_STRIP_FN("abc/def\\ghi", "abc/def\\");
// mixed path with filename (2)
TEST_STRIP_FN("abc\\def/ghi", "abc\\def/");
// filename only
TEST_STRIP_FN("abc", "");
// empty
TEST_STRIP_FN("", "");
// path
TEST_LAST_COMPONENT("abc/def/", "");
// nonportable path
TEST_LAST_COMPONENT("abc\\def\\ghi\\", "");
}
// note: no need to test path_dir_only - it is implemented exactly as
// done in TEST_STRIP_FN.
void test_path_ext()
{
TEST_PATH_EXT("a/b/c.bmp", "bmp");
TEST_PATH_EXT("a.BmP", "BmP"); // case sensitive
TEST_PATH_EXT("c", ""); // no extension
TEST_PATH_EXT("", ""); // empty
}
// testing path_foreach_component is difficult; currently skipped.
void test_path_package()
{
// normal
TEST_PATH_PACKAGE("a/b", "c", "a/b/c");
// nonportable slash
TEST_PATH_PACKAGE("a\\b", "c", "a\\b/c");
// slash already present
TEST_PATH_PACKAGE("a/b/", "c", "a/b/c");
// nonportable slash already present
TEST_PATH_PACKAGE("a\\b\\", "c", "a\\b\\c");
// mixed slashes
TEST_PATH_PACKAGE("a/b\\c", "d", "a/b\\c/d");
// mixed slashes (2)
TEST_PATH_PACKAGE("a\\b/c", "d", "a\\b/c/d");
}
};

View File

@ -0,0 +1,196 @@
#include <cxxtest/TestSuite.h>
#include "lib/string_s.h"
class TestString_s : public CxxTest::TestSuite
{
// note: avoid 4-byte strings - they would trigger WARN_IF_PTR_LEN.
const tchar* const s0;
const tchar* const s1;
const tchar* const s5;
const tchar* const s10;
tchar d1[1];
tchar d2[2];
tchar d3[3];
tchar d5[5];
tchar d6[6];
tchar d10[10];
tchar d11[11];
tchar no_null[7];
static void TEST_LEN(const char* string, size_t limit, size_t expected)
{
TS_ASSERT_EQUAL(tnlen((string), (limit)), (expected));
}
static void TEST_CPY(char* dst, size_t dst_max, const char* src,
int expected_ret, const char* expected_dst)
{
int ret = tcpy_s((dst), dst_max, (src));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
static void TEST_CPY2(char* dst, const char* src,
int expected_ret, const char* expected_dst)
{
int ret = tcpy_s((dst), ARRAY_SIZE(dst), (src));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
static void TEST_NCPY(char* dst, const char* src, size_t max_src_chars,
int expected_ret, const char* expected_dst)
{
int ret = tncpy_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
static void TEST_CAT(char* dst, size_t dst_max, const char* src,
int expected_ret, const char expected_dst)
{
int ret = tcat_s((dst), dst_max, (src));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
static void TEST_CAT2(char* dst, const char* dst_val, const char* src,
int expected_ret, const char* expected_dst)
{
tcpy(dst, T(dst_val));
int ret = tcat_s((dst), ARRAY_SIZE(dst), (src));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
static void TEST_NCAT(char* dst, const char* dst_val,
const char* src, size_t max_src_chars,
int expected_ret, const char* expected_dst)
{
tcpy(dst, T(dst_val));
int ret = tncat_s((dst), ARRAY_SIZE(dst), (src), (max_src_chars));
TS_ASSERT_EQUAL(ret, expected_ret);
if(dst != 0)
TS_ASSERT(!tcmp(dst, T(expected_dst)));
}
public:
TestString_s()
: s0(T("")), s1(T("a")), s5(T("abcde")), s10(T("abcdefghij"))
{
const tchar no_null_[] = { 'n','o','_','n','u','l','l'};
memcpy(no_null, no_null_tmp, sizeof(no_null));
}
// contains all tests that verify correct behavior for bogus input.
// our implementation suppresses error dialogs while the self-test is active,
// but others (e.g. the functions shipped with VC8) do not.
// since we have no control over their error reporting (which ends up taking
// down the program), we must skip this part of the test if using them.
// this is still preferable to completely disabling the self-test.
void test_param_validation()
{
#if !HAVE_STRING_S
TEST_CPY(0 ,0,0 , EINVAL,""); // all invalid
TEST_CPY(0 ,0,s1, EINVAL,""); // dst = 0, max = 0
TEST_CPY(0 ,1,s1, EINVAL,""); // dst = 0, max > 0
TEST_CPY(d1,1,0 , EINVAL,""); // src = 0
TEST_CPY(d1,0,s1, ERANGE,""); // max_dst_chars = 0
TEST_CPY2(d1 ,s1, ERANGE,"");
TEST_CPY2(d1 ,s5, ERANGE,"");
TEST_CPY2(d5 ,s5, ERANGE,"");
TEST_NCPY(d1 ,s1,1, ERANGE,"");
TEST_NCPY(d1 ,s5,1, ERANGE,"");
TEST_NCPY(d5 ,s5,5, ERANGE,"");
TEST_CAT(0 ,0,0 , EINVAL,""); // all invalid
TEST_CAT(0 ,0,s1, EINVAL,""); // dst = 0, max = 0
TEST_CAT(0 ,1,s1, EINVAL,""); // dst = 0, max > 0
TEST_CAT(d1,1,0 , EINVAL,""); // src = 0
TEST_CAT(d1,0,s1, ERANGE,""); // max_dst_chars = 0
TEST_CAT(no_null,5,s1, ERANGE,""); // dst not terminated
TEST_CAT2(d1 ,"" ,s1, ERANGE,"");
TEST_CAT2(d1 ,"" ,s5, ERANGE,"");
TEST_CAT2(d10,"" ,s10, ERANGE,""); // empty, total overflow
TEST_CAT2(d10,"12345",s5 , ERANGE,""); // not empty, overflow
TEST_CAT2(d10,"12345",s10, ERANGE,""); // not empty, total overflow
TEST_NCAT(d1 ,"" ,s1,1, ERANGE,"");
TEST_NCAT(d1 ,"" ,s5,5, ERANGE,"");
TEST_NCAT(d10,"" ,s10,10, ERANGE,""); // empty, total overflow
TEST_NCAT(d10,"12345",s5 ,5 , ERANGE,""); // not empty, overflow
TEST_NCAT(d10,"12345",s10,10, ERANGE,""); // not empty, total overflow
#endif
}
void test_length()
{
TEST_LEN(s0, 0 , 0 );
TEST_LEN(s0, 1 , 0 );
TEST_LEN(s0, 50, 0 );
TEST_LEN(s1, 0 , 0 );
TEST_LEN(s1, 1 , 1 );
TEST_LEN(s1, 50, 1 );
TEST_LEN(s5, 0 , 0 );
TEST_LEN(s5, 1 , 1 );
TEST_LEN(s5, 50, 5 );
TEST_LEN(s10,9 , 9 );
TEST_LEN(s10,10, 10);
TEST_LEN(s10,11, 10);
}
void test_copy()
{
TEST_CPY2(d2 ,s1, 0,"a");
TEST_CPY2(d6 ,s5, 0,"abcde");
TEST_CPY2(d11,s5, 0,"abcde");
TEST_NCPY(d2 ,s1,1, 0,"a");
TEST_NCPY(d6 ,s5,5, 0,"abcde");
TEST_NCPY(d11,s5,5, 0,"abcde");
tcpy(d5, T("----"));
TEST_NCPY(d5,s5,0 , 0,""); // specified behavior! see 3.6.2.1.1 #4
TEST_NCPY(d5,s5,1 , 0,"a");
TEST_NCPY(d5,s5,4 , 0,"abcd");
TEST_NCPY(d6,s5,5 , 0,"abcde");
TEST_NCPY(d6,s5,10, 0,"abcde");
}
void test_concatenate()
{
TEST_CAT2(d3 ,"1",s1, 0,"1a");
TEST_CAT2(d5 ,"1",s1, 0,"1a");
TEST_CAT2(d6 ,"" ,s5, 0,"abcde");
TEST_CAT2(d10,"" ,s5, 0,"abcde");
TEST_CAT2(d10,"1234" ,s5 , 0,"1234abcde");
TEST_NCAT(d3 ,"1",s1,1, 0,"1a");
TEST_NCAT(d5 ,"1",s1,1, 0,"1a");
TEST_NCAT(d6 ,"" ,s5,5, 0,"abcde");
TEST_NCAT(d10,"" ,s5,5, 0,"abcde");
TEST_NCAT(d10,"1234" ,s5 ,5 , 0,"1234abcde");
TEST_NCAT(d5,"----",s5,0 , 0,"----");
TEST_NCAT(d5,"",s5,1 , 0,"a");
TEST_NCAT(d5,"",s5,4 , 0,"abcd");
TEST_NCAT(d5,"12",s5,2 , 0,"12ab");
TEST_NCAT(d6,"",s5,10, 0,"abcde");
}
};

View File

@ -0,0 +1,64 @@
#include <cxxtest/TestSuite.h>
#include "Maths/Matrix3D.h"
class TestMatrix : public CxxTest::TestSuite
{
public:
void test_inverse()
{
CMatrix3D m;
srand(0);
for (int i = 0; i < 4; ++i)
{
for (int j = 0; j < 16; ++j)
{
m._data[j] = -1.0f + 2.0f*(rand()/(float)RAND_MAX);
}
CMatrix3D n;
m.GetInverse(n);
m *= n;
// verify identity has 1s on diagonal and 0 otherwise
for (int x = 0; x < 4; ++x)
{
for (int y = 0; y < 4; ++y)
{
const float expected = (x==y)? 1.0f : 0.0f;
TS_ASSERT_DELTA(m(x,y), expected, 0.0001f);
}
}
}
}
void test_quats()
{
srand(0);
for (int i = 0; i < 4; ++i)
{
CQuaternion q;
q.FromEulerAngles(
-6.28f + 12.56f*(rand()/(float)RAND_MAX),
-6.28f + 12.56f*(rand()/(float)RAND_MAX),
-6.28f + 12.56f*(rand()/(float)RAND_MAX)
);
CMatrix3D m;
q.ToMatrix(m);
CQuaternion q2 = m.GetRotation();
// I hope there's a good reason why they're sometimes negated, and
// it's not just a bug...
const bool ok_oneway =
feq(q2.m_W, q.m_W) &&
feq(q2.m_V.X, q.m_V.X) &&
feq(q2.m_V.Y, q.m_V.Y) &&
feq(q2.m_V.Z, q.m_V.Z);
const bool ok_otherway =
feq(q2.m_W, -q.m_W) &&
feq(q2.m_V.X, -q.m_V.X) &&
feq(q2.m_V.Y, -q.m_V.Y) &&
feq(q2.m_V.Z, -q.m_V.Z);
TS_ASSERT(ok_oneway ^ ok_otherway);
}
}
};

View File

@ -0,0 +1,56 @@
#include <cxxtest/TestSuite.h>
#include "ps/xml/XML.h"
class TestXmlWriter : public CxxTest::TestSuite
{
public:
void test1()
{
XML_Start("utf-8");
XML_Doctype("Scenario", "/maps/scenario.dtd");
{
XML_Element("Scenario");
{
XML_Comment("Comment test.");
XML_Comment("Comment test again.");
{
XML_Element("a");
XML_Attribute("one", 1);
XML_Attribute("two", "TWO");
XML_Text("b");
XML_Text(" (etc)");
}
{
XML_Element("c");
XML_Text("d");
}
XML_Setting("c2", "d2");
{
XML_Element("e");
{
{
XML_Element("f");
XML_Text("g");
}
{
XML_Element("h");
}
{
XML_Element("i");
XML_Attribute("j", 1.23);
{
XML_Element("k");
XML_Attribute("l", 2.34);
XML_Text("m");
}
}
}
}
}
}
// For this test to be useful, it should actually test something.
}
};

View File

@ -0,0 +1,18 @@
#include <cxxtest/TestSuite.h>
#include "ps/CStr.h"
class TestCStr : public CxxTest::TestSuite
{
public:
void test_utf8_utf16_conversion()
{
const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffe };
const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbe };
CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t));
CStr8 str_utf8 = str_utf16.ToUTF8();
TS_ASSERT_EQUAL(str_utf8.length(), sizeof(chr_utf8));
TS_ASSERT_SAME_DATA(str_utf8.data(), chr_utf8, sizeof(chr_utf8));
TS_ASSERT_EQUAL(str_utf8.FromUTF8(), str_utf16);
}
};

View File

@ -0,0 +1,93 @@
#include <cxxtest/TestSuite.h>
class TestParser : public CxxTest::TestSuite
{
public:
void test1()
{
CParser Parser;
Parser.InputTaskType("test", "_$ident_=_$value_");
std::string str;
int i;
CParserLine Line;
TS_ASSERT(Line.ParseString(Parser, "value=23"));
TS_ASSERT(Line.GetArgString(0, str) && str == "value");
TS_ASSERT(Line.GetArgInt(1, i) && i == 23);
}
void test2()
{
CParser Parser;
Parser.InputTaskType("test", "_$value_[$value]_");
std::string str;
CParserLine Line;
TS_ASSERT(Line.ParseString(Parser, "12 34"));
TS_ASSERT_EQUAL(Line.GetArgCount(), 2);
TS_ASSERT(Line.GetArgString(0, str) && str == "12");
TS_ASSERT(Line.GetArgString(1, str) && str == "34");
TS_ASSERT(Line.ParseString(Parser, "56"));
TS_ASSERT_EQUAL(Line.GetArgCount(), 1);
TS_ASSERT(Line.GetArgString(0, str) && str == "56");
TS_ASSERT(! Line.ParseString(Parser, " "));
}
void test3()
{
CParser Parser;
Parser.InputTaskType("test", "_[$value]_[$value]_[$value]_");
std::string str;
CParserLine Line;
TS_ASSERT(Line.ParseString(Parser, "12 34 56"));
TS_ASSERT_EQUAL(Line.GetArgCount(), 3);
TS_ASSERT(Line.GetArgString(0, str) && str == "12");
TS_ASSERT(Line.GetArgString(1, str) && str == "34");
TS_ASSERT(Line.GetArgString(2, str) && str == "56");
TS_ASSERT(Line.ParseString(Parser, "78 90"));
TS_ASSERT_EQUAL(Line.GetArgCount(), 2);
TS_ASSERT(Line.GetArgString(0, str) && str == "78");
TS_ASSERT(Line.GetArgString(1, str) && str == "90");
TS_ASSERT(Line.ParseString(Parser, "ab"));
TS_ASSERT_EQUAL(Line.GetArgCount(), 1);
TS_ASSERT(Line.GetArgString(0, str) && str == "ab");
TS_ASSERT(Line.ParseString(Parser, " "));
TS_ASSERT_EQUAL(Line.GetArgCount(), 0);
}
void test4()
{
CParser Parser;
Parser.InputTaskType("test", "<[_a_][_b_]_x_>");
std::string str;
CParserLine Line;
TS_ASSERT(Line.ParseString(Parser, "a b x a b x"));
TS_ASSERT(Line.ParseString(Parser, "a x b x"));
TS_ASSERT(Line.ParseString(Parser, "a x"));
TS_ASSERT(Line.ParseString(Parser, "b x"));
TS_ASSERT(Line.ParseString(Parser, "x"));
TS_ASSERT(! Line.ParseString(Parser, "a x c x"));
TS_ASSERT(! Line.ParseString(Parser, "a b a x"));
TS_ASSERT(! Line.ParseString(Parser, "a"));
TS_ASSERT(! Line.ParseString(Parser, "a a x"));
TS_ASSERT(Line.ParseString(Parser, "a x a b x a x b x b x b x b x a x a x a b x a b x b x a x"));
}
};