// game::config manifest tests: the token-pair loop (comments, CRLF, the dropped trailing // entry, atoi typing, truncation), the path spellings, and the MSVC 2010 sort replica. // All samples are hand-written. #include #include #include #include #include #include #include "game/config/manifest_loader.h" #include "game/config/msvc_sort.h" using namespace game::config; static int g_fail = 0, g_pass = 0; #define CHECK(cond) \ do { \ if (cond) ++g_pass; \ else { ++g_fail; std::printf("FAIL %s:%d: %s\n", __FILE__, __LINE__, #cond); } \ } while (0) static void test_atoi() { CHECK(manifest_atoi("12") == 12); CHECK(manifest_atoi("-7") == -7); CHECK(manifest_atoi("+3") == 3); CHECK(manifest_atoi(" 41x") == 41); CHECK(manifest_atoi("abc") == 0); CHECK(manifest_atoi("") == 0); CHECK(manifest_atoi("-") == 0); CHECK(manifest_atoi("12.9") == 12); CHECK(manifest_atoi("4294967297") == 1); // wraps modulo 2^32 } static void test_pairs_basic() { const std::string text = "// Only add to this list: always use new IDs\r\n" "// ID's need not be ordered here, but they must be unique.\r\n" "\r\n" "1 alpha.weapon\r\n" "2 beta.weapon\r\n" "// DELETED - 3\r\n" "4 gamma.weapon\r\n" "// REMOVED - 5 delta.weapon\r\n" "6 epsilon.weapon\r\n"; const auto p = manifest_pairs(text); CHECK(p.size() == 4); if (p.size() == 4) { CHECK(p[0].id == 1 && p[0].file == "alpha.weapon"); CHECK(p[1].id == 2 && p[1].file == "beta.weapon"); CHECK(p[2].id == 4 && p[2].file == "gamma.weapon"); CHECK(p[3].id == 6 && p[3].file == "epsilon.weapon"); } } static void test_pairs_trailing_newline_rule() { // The last value token touches the end of the input: read as AtEnd, so the entry is lost. const auto dropped = manifest_pairs("1 a.weapon\r\n2 b.weapon"); CHECK(dropped.size() == 1); CHECK(dropped.empty() || dropped[0].id == 1); const auto kept = manifest_pairs("1 a.weapon\r\n2 b.weapon\r\n"); CHECK(kept.size() == 2); const auto kept_space = manifest_pairs("1 a.weapon 2 b.weapon "); CHECK(kept_space.size() == 2); // any trailing whitespace will do const auto lf_only = manifest_pairs("7 x.weapon\n"); CHECK(lf_only.size() == 1 && lf_only[0].id == 7); CHECK(manifest_pairs("").empty()); CHECK(manifest_pairs(" \r\n\r\n").empty()); CHECK(manifest_pairs("// just a comment\r\n").empty()); } static void test_pairs_odd_tokens() { // An odd token count: the trailing id is read (Ok), then no file -> loop ends. const auto p = manifest_pairs("1 a.weapon\r\n2\r\n"); CHECK(p.size() == 1); // Non-numeric first token: atoi gives 0, the pair is still produced. const auto z = manifest_pairs("zz a.weapon\r\n"); CHECK(z.size() == 1 && z[0].id == 0 && z[0].file == "a.weapon"); // Tokens are whatever the tokenizer yields: a file name is any non-whitespace run. const auto q = manifest_pairs("5 \"quoted name.weapon\"\r\n"); CHECK(q.size() == 1 && q[0].file == "quoted name.weapon"); // A comment glued to the id: `//` only counts at a token start. const auto g = manifest_pairs("9//c x.weapon\r\n"); CHECK(g.size() == 1 && g[0].id == 9 && g[0].file == "x.weapon"); } static void test_pairs_truncation() { std::string longname(300, 'n'); const auto p = manifest_pairs("3 " + longname + "\r\n"); CHECK(p.size() == 1 && p[0].file.size() == kManifestTokenMax); std::string longid = "12" + std::string(300, '9'); const auto q = manifest_pairs(longid + " f\r\n"); CHECK(q.size() == 1); // the id is atoi of the truncated digits (wraps); the pair survives } static void test_paths() { CHECK(weapon_path("can_am.weapon") == "Weapons/can_am.weapon"); CHECK(section_manifest_path("Species/Human/sections") == "Species/Human/sections/_shipsections.txt"); CHECK(section_path("Species/Human/sections", "CRArmor.shipsection") == "Species/Human/sections/CRArmor.shipsection"); CHECK(std::strcmp(kWeaponManifest, "Weapons/_weapons.txt") == 0); CHECK(kSpeciesCount == 7); } // ---- the sort replica ---------------------------------------------------------------------- struct Item { int key; int tag; // identity, to see where ties land }; static bool key_less(const Item& a, const Item& b) { return a.key < b.key; } static bool is_sorted_by_key(const std::vector& v) { for (std::size_t i = 1; i < v.size(); ++i) if (v[i].key < v[i - 1].key) return false; return true; } static void test_sort_distinct() { std::mt19937 rng(12345); for (int n : {0, 1, 2, 3, 31, 32, 33, 40, 41, 64, 123, 500, 1000}) { std::vector v; for (int i = 0; i < n; ++i) v.push_back(Item{i, i}); std::shuffle(v.begin(), v.end(), rng); std::vector ref = v; msvc10::sort(v.begin(), v.end(), key_less); std::sort(ref.begin(), ref.end(), key_less); bool same = v.size() == ref.size(); for (std::size_t i = 0; same && i < v.size(); ++i) same = v[i].key == ref[i].key && v[i].tag == ref[i].tag; CHECK(same); } } static void test_sort_ties() { std::mt19937 rng(777); for (int n : {5, 32, 33, 123, 300}) { std::vector v; for (int i = 0; i < n; ++i) v.push_back(Item{(i * 7) % 11, i}); std::shuffle(v.begin(), v.end(), rng); std::vector again = v; msvc10::sort(v.begin(), v.end(), key_less); msvc10::sort(again.begin(), again.end(), key_less); CHECK(is_sorted_by_key(v)); bool same = true; for (std::size_t i = 0; i < v.size(); ++i) same = same && v[i].tag == again[i].tag; CHECK(same); // deterministic std::vector tags; for (const Item& it : v) tags.push_back(it.tag); std::sort(tags.begin(), tags.end()); bool perm = true; for (int i = 0; i < n; ++i) perm = perm && tags[static_cast(i)] == i; CHECK(perm); // a permutation, nothing lost or duplicated } // At or below 32 elements the replica is an insertion sort: ties keep their input order. std::vector small; for (int i = 0; i < 20; ++i) small.push_back(Item{i % 3, i}); msvc10::sort(small.begin(), small.end(), key_less); bool stable = true; for (std::size_t i = 1; i < small.size(); ++i) if (small[i].key == small[i - 1].key && small[i].tag < small[i - 1].tag) stable = false; CHECK(stable); } static void test_sort_already_sorted_and_reversed() { for (int n : {33, 100, 123}) { std::vector v; for (int i = 0; i < n; ++i) v.push_back(Item{i, i}); msvc10::sort(v.begin(), v.end(), key_less); CHECK(is_sorted_by_key(v)); std::reverse(v.begin(), v.end()); msvc10::sort(v.begin(), v.end(), key_less); CHECK(is_sorted_by_key(v)); std::vector all_same(static_cast(n), Item{4, 0}); for (int i = 0; i < n; ++i) all_same[static_cast(i)].tag = i; msvc10::sort(all_same.begin(), all_same.end(), key_less); CHECK(is_sorted_by_key(all_same)); } } int main() { test_atoi(); test_pairs_basic(); test_pairs_trailing_newline_rule(); test_pairs_odd_tokens(); test_pairs_truncation(); test_paths(); test_sort_distinct(); test_sort_ties(); test_sort_already_sorted_and_reversed(); std::printf("%d passed, %d failed\n", g_pass, g_fail); return g_fail ? 1 : 0; }