diff --git a/source/lib/res/file/tests/test_archive_builder.h b/source/lib/res/file/tests/test_archive_builder.h new file mode 100644 index 0000000000..283d332a07 --- /dev/null +++ b/source/lib/res/file/tests/test_archive_builder.h @@ -0,0 +1,105 @@ +#include + +#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 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)); + } +}; diff --git a/source/lib/res/file/tests/test_compression.h b/source/lib/res/file/tests/test_compression.h new file mode 100644 index 0000000000..55ef944028 --- /dev/null +++ b/source/lib/res/file/tests/test_compression.h @@ -0,0 +1,47 @@ +#include + +#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); + } +}; diff --git a/source/lib/res/file/tests/test_file_cache.h b/source/lib/res/file/tests/test_file_cache.h new file mode 100644 index 0000000000..55cca6a45c --- /dev/null +++ b/source/lib/res/file/tests/test_file_cache.h @@ -0,0 +1,63 @@ +#include + +#include "lib/res/file/file_cache.h" + +class TestFileCache : public CxxTest::TestSuite +{ +public: + void test_cache_allocator() + { + // allocated address -> its size + typedef std::map 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. + } +}; diff --git a/source/lib/res/file/tests/test_path.h b/source/lib/res/file/tests/test_path.h new file mode 100644 index 0000000000..5a153cfe29 --- /dev/null +++ b/source/lib/res/file/tests/test_path.h @@ -0,0 +1,64 @@ +#include + +#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); + } +}; diff --git a/source/lib/res/file/tests/test_zip.h b/source/lib/res/file/tests/test_zip.h new file mode 100644 index 0000000000..2e9c3d804a --- /dev/null +++ b/source/lib/res/file/tests/test_zip.h @@ -0,0 +1,23 @@ +#include + +#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); + } +}; diff --git a/source/lib/res/graphics/tests/test_tex.h b/source/lib/res/graphics/tests/test_tex.h new file mode 100644 index 0000000000..91429385e1 --- /dev/null +++ b/source/lib/res/graphics/tests/test_tex.h @@ -0,0 +1,130 @@ +#include + +#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); + } +}; diff --git a/source/lib/sysdep/tests/test_sysdep.h b/source/lib/sysdep/tests/test_sysdep.h new file mode 100644 index 0000000000..24f052e538 --- /dev/null +++ b/source/lib/sysdep/tests/test_sysdep.h @@ -0,0 +1,54 @@ +#include + +#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); + } +}; diff --git a/source/lib/sysdep/win/tests/test_ia32.h b/source/lib/sysdep/win/tests/test_ia32.h new file mode 100644 index 0000000000..4bd6d25871 --- /dev/null +++ b/source/lib/sysdep/win/tests/test_ia32.h @@ -0,0 +1,28 @@ +#include + +#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)); + } +}; diff --git a/source/lib/sysdep/win/tests/test_wdbg_sym.h b/source/lib/sysdep/win/tests/test_wdbg_sym.h new file mode 100644 index 0000000000..eb01cbe5b9 --- /dev/null +++ b/source/lib/sysdep/win/tests/test_wdbg_sym.h @@ -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 + +#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 v_wstring; + v_wstring.push_back(L"ws1"); v_wstring.push_back(L"ws2"); + + std::deque d_int; + d_int.push_back(1); d_int.push_back(2); d_int.push_back(3); + std::deque d_string; + d_string.push_back("a"); d_string.push_back("b"); d_string.push_back("c"); + + std::list 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 m_string_int; + m_string_int.insert(std::make_pair("s5", 5)); + m_string_int.insert(std::make_pair("s6", 6)); + m_string_int.insert(std::make_pair("s7", 7)); + std::map m_int_string; + m_int_string.insert(std::make_pair(1, "s1")); + m_int_string.insert(std::make_pair(2, "s2")); + m_int_string.insert(std::make_pair(3, "s3")); + std::map m_int_int; + m_int_int.insert(std::make_pair(1, 1)); + m_int_int.insert(std::make_pair(2, 2)); + m_int_int.insert(std::make_pair(3, 3)); + + STL_HASH_MAP hm_string_int; + hm_string_int.insert(std::make_pair("s5", 5)); + hm_string_int.insert(std::make_pair("s6", 6)); + hm_string_int.insert(std::make_pair("s7", 7)); + STL_HASH_MAP hm_int_string; + hm_int_string.insert(std::make_pair(1, "s1")); + hm_int_string.insert(std::make_pair(2, "s2")); + hm_int_string.insert(std::make_pair(3, "s3")); + STL_HASH_MAP hm_int_int; + hm_int_int.insert(std::make_pair(1, 1)); + hm_int_int.insert(std::make_pair(2, 2)); + hm_int_int.insert(std::make_pair(3, 3)); + + + std::set s_uintptr; + s_uintptr.insert(0x123); s_uintptr.insert(0x456); + + // empty + std::deque d_u8_empty; + std::list l_nested_empty; + std::map m_double_empty; + std::multimap mm_int_empty; + std::set s_uint_empty; + std::multiset ms_char_empty; + std::vector v_double_empty; + std::queue q_double_empty; + std::stack st_double_empty; +#if HAVE_STL_HASH + STL_HASH_MAP hm_double_empty; + STL_HASH_MULTIMAP hmm_double_empty; + STL_HASH_SET hs_double_empty; + STL_HASH_MULTISET hms_double_empty; +#endif +#if HAVE_STL_SLIST + STL_SLIST sl_double_empty; +#endif + std::string str_empty; + std::wstring wstr_empty; + + test_udt(); + + // uninitialized + std::deque d_u8_uninit; + std::list l_nested_uninit; + std::map m_double_uninit; + std::multimap mm_int_uninit; + std::set s_uint_uninit; + std::multiset ms_char_uninit; + std::vector v_double_uninit; + std::queue q_double_uninit; + std::stack st_double_uninit; +#if HAVE_STL_HASH + STL_HASH_MAP hm_double_uninit; + STL_HASH_MULTIMAP hmm_double_uninit; + STL_HASH_SET hs_double_uninit; + STL_HASH_MULTISET hms_double_uninit; +#endif +#if HAVE_STL_SLIST + STL_SLIST 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); + } +}; diff --git a/source/lib/tests/test_adts.h b/source/lib/tests/test_adts.h new file mode 100644 index 0000000000..738a2fe614 --- /dev/null +++ b/source/lib/tests/test_adts.h @@ -0,0 +1,178 @@ +#include + +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 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 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 buf; + std::deque 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::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 c1; + Cache c1r; + Cache c2; + Cache c2r; + Cache c3; + Cache 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 c1; + Cache c2; + Cache 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 + } +}; diff --git a/source/lib/tests/test_allocators.h b/source/lib/tests/test_allocators.h new file mode 100644 index 0000000000..819ed3bc0a --- /dev/null +++ b/source/lib/tests/test_allocators.h @@ -0,0 +1,44 @@ +#include + +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); + } +}; diff --git a/source/lib/tests/test_byte_order.h b/source/lib/tests/test_byte_order.h new file mode 100644 index 0000000000..abeba9daeb --- /dev/null +++ b/source/lib/tests/test_byte_order.h @@ -0,0 +1,38 @@ +#include + +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*. + } +}; diff --git a/source/lib/tests/test_lib.h b/source/lib/tests/test_lib.h new file mode 100644 index 0000000000..bf869ef9c4 --- /dev/null +++ b/source/lib/tests/test_lib.h @@ -0,0 +1,231 @@ +#include + +#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); + } +}; diff --git a/source/lib/tests/test_lockfree.h b/source/lib/tests/test_lockfree.h new file mode 100644 index 0000000000..a088adab6a --- /dev/null +++ b/source/lib/tests/test_lockfree.h @@ -0,0 +1,211 @@ +#include + +#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 KeySet; + typedef KeySet::const_iterator KeySetIt; + KeySet keys; + pthread_mutex_t mutex; // protects + + 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)); + } +}; diff --git a/source/lib/tests/test_path_util.h b/source/lib/tests/test_path_util.h new file mode 100644 index 0000000000..2df3b12abe --- /dev/null +++ b/source/lib/tests/test_path_util.h @@ -0,0 +1,215 @@ +#include + +#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"); + } +}; diff --git a/source/lib/tests/test_string_s.h b/source/lib/tests/test_string_s.h new file mode 100644 index 0000000000..e797f81ba2 --- /dev/null +++ b/source/lib/tests/test_string_s.h @@ -0,0 +1,196 @@ +#include + +#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"); + } +}; diff --git a/source/maths/tests/test_Matrix3d.h b/source/maths/tests/test_Matrix3d.h new file mode 100644 index 0000000000..66327976af --- /dev/null +++ b/source/maths/tests/test_Matrix3d.h @@ -0,0 +1,64 @@ +#include + +#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); + } + } +}; + diff --git a/source/ps/XML/tests/test_XMLWriter.h b/source/ps/XML/tests/test_XMLWriter.h new file mode 100644 index 0000000000..66b216c754 --- /dev/null +++ b/source/ps/XML/tests/test_XMLWriter.h @@ -0,0 +1,56 @@ +#include + +#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. + } +}; \ No newline at end of file diff --git a/source/ps/tests/test_CStr.h b/source/ps/tests/test_CStr.h new file mode 100644 index 0000000000..624db540df --- /dev/null +++ b/source/ps/tests/test_CStr.h @@ -0,0 +1,18 @@ +#include + +#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); + } +}; diff --git a/source/ps/tests/test_Parser.h b/source/ps/tests/test_Parser.h new file mode 100644 index 0000000000..345758ccb1 --- /dev/null +++ b/source/ps/tests/test_Parser.h @@ -0,0 +1,93 @@ +#include + +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")); + } +};