You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
5.7 KiB
315 lines
5.7 KiB
#include "xxhash.h" |
|
#include <fstream> |
|
#include <initializer_list> |
|
#include <iostream> |
|
#include <random> |
|
#include <stddef.h> |
|
#include <tuple> |
|
#include <valgrind/memcheck.h> |
|
|
|
#include "../types.h" |
|
#include "asserts.h" |
|
#include "sanitize_coverage.h" |
|
#include "stubs.h" |
|
#include "test_utils.h" |
|
|
|
#ifdef HAVE_HARNESS |
|
#include "Absolute/absolute.h" |
|
|
|
static bool use_harness; |
|
|
|
#define H(name) (use_harness ? d_##name : name) |
|
#endif |
|
|
|
#ifndef H |
|
#define H(name) name |
|
#endif |
|
|
|
using std::endl; |
|
using std::ostream; |
|
|
|
static void valgrind_init() |
|
{ |
|
#ifdef HAVE_HARNESS |
|
char *start = &__start_bss; |
|
unsigned len = (char *)&__end_bss - start; |
|
VALGRIND_MAKE_MEM_UNDEFINED(start, len); |
|
|
|
// Global destructors in msgcmd_cleanup_chatcmd() read this |
|
VALGRIND_MAKE_MEM_DEFINED(&sgChat_Cmd, sizeof(sgChat_Cmd)); |
|
#endif |
|
} |
|
|
|
static void oprintf(ostream &os, const char *format, ...) |
|
{ |
|
va_list args; |
|
va_start(args, format); |
|
|
|
char buf[256]; |
|
vsnprintf(buf, sizeof(buf), format, args); |
|
buf[sizeof(buf) - 1] = 0; |
|
os << buf; |
|
} |
|
|
|
#define O(x) out << #x << "=" << x << endl |
|
|
|
static void print_dungeon(ostream &out) |
|
{ |
|
if (leveltype != DTYPE_CATHEDRAL) { |
|
out << "predungeon:" << endl; |
|
for (int y = 0; y < DMAXY; y++) { |
|
for (int x = 0; x < DMAXX; x++) { |
|
auto v = predungeon[x][y]; |
|
out << (v ? v : '~'); |
|
} |
|
out << endl; |
|
} |
|
} else { |
|
O(VR1); |
|
O(VR2); |
|
O(VR3); |
|
O(HR1); |
|
O(HR2); |
|
O(HR3); |
|
|
|
out << "L5dungeon:" << endl; |
|
for (int y = 0; y < 80; y++) { |
|
oprintf(out, "%2d: ", y); |
|
for (int x = 0; x < 80; x++) { |
|
BYTE v = L5dungeon[x][y]; |
|
if (!v) |
|
out << '.'; |
|
else |
|
oprintf(out, "%X", (int)v); |
|
} |
|
out << endl; |
|
} |
|
} |
|
|
|
out << "dungeon:" << endl; |
|
for (int y = 0; y < DMAXY; y++) { |
|
oprintf(out, "%2d: ", y); |
|
for (int x = 0; x < DMAXX; x++) { |
|
BYTE v = dungeon[x][y]; |
|
if (v == 0x16) |
|
out << ".. "; |
|
else |
|
oprintf(out, "%02X ", (int)v); |
|
} |
|
out << endl; |
|
} |
|
|
|
out << "mydflags:" << endl; |
|
for (int y = 0; y < DMAXY; y++) { |
|
oprintf(out, "%2d: ", y); |
|
for (int x = 0; x < DMAXX; x++) { |
|
BYTE v = mydflags[x][y]; |
|
if (!v) |
|
out << " ."; |
|
else |
|
oprintf(out, "%02X", (int)v); |
|
} |
|
out << endl; |
|
} |
|
|
|
O(setpc_w); |
|
O(setpc_h); |
|
O(setpc_x); |
|
O(setpc_y); |
|
} |
|
|
|
static uint32_t mix32(uint32_t x) |
|
{ |
|
x ^= x >> 16; |
|
x *= UINT32_C(0x85ebca6b); |
|
x ^= x >> 13; |
|
x *= UINT32_C(0xc2b2ae35); |
|
x ^= x >> 16; |
|
return x; |
|
} |
|
|
|
template <typename T> static uint32_t hash(const T &obj) |
|
{ |
|
return XXH32(&obj, sizeof(obj), 123); |
|
} |
|
|
|
struct DGHashes { |
|
uint32_t h_predungeon = hash(predungeon); |
|
uint32_t h_dungeon = hash(dungeon); |
|
uint32_t h_dPiece = hash(dPiece); |
|
|
|
auto to_tuple() { return std::tie(h_predungeon, h_dungeon, h_dPiece); } |
|
void print() { printf("H: predungeon=%08X dungeon=%08X dPiece=%08X\n", h_predungeon, h_dungeon, h_dPiece); } |
|
}; |
|
|
|
struct DGTestCase { |
|
int currlevel; |
|
int seed; |
|
int entry; |
|
|
|
void run() |
|
{ |
|
::currlevel = currlevel; |
|
|
|
test_set_seeds(seed); |
|
|
|
H(InitLighting)(); |
|
|
|
H(InitQuests)(); |
|
|
|
for (int q = 0; q < MAXQUESTS; q++) { |
|
if (quests[q]._qactive) { |
|
// printf("Quest %2d active, status = %d\n", q, QuestStatus(q)); |
|
} |
|
} |
|
|
|
// VALGRIND: Initialize the d* arrays. This is also called by DRLG_L2() but *after* it touches them. |
|
H(DRLG_Init_Globals)(); |
|
|
|
H(CreateLevel)(entry); |
|
|
|
ASSERT(pHallList == 0); |
|
}; |
|
|
|
void run_original() |
|
{ |
|
#ifdef HAVE_HARNESS |
|
use_harness = true; |
|
run(); |
|
use_harness = false; |
|
#else |
|
fprintf(stderr, "Comparison disabled\b"); |
|
#endif |
|
} |
|
|
|
bool run_original_and_compare() |
|
{ |
|
DGHashes h1; |
|
|
|
run_original(); |
|
|
|
DGHashes horig; |
|
|
|
if (h1.to_tuple() != horig.to_tuple()) { |
|
h1.print(); |
|
horig.print(); |
|
|
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
void run_and_print_dungeons() |
|
{ |
|
run(); |
|
{ |
|
std::ofstream fnew("dungeon.new.txt"); |
|
print_dungeon(fnew); |
|
} |
|
|
|
run_original(); |
|
{ |
|
std::ofstream fnew("dungeon.original.txt"); |
|
print_dungeon(fnew); |
|
} |
|
} |
|
|
|
void print(std::ostream &os) const { os << "currlevel=" << currlevel << " entry=" << entry << " seed=" << seed; }; |
|
}; |
|
|
|
template <class T> auto operator<<(std::ostream &os, const T &t) -> decltype(t.print(os), os) |
|
{ |
|
t.print(os); |
|
return os; |
|
} |
|
|
|
static void test_init_drlg(dungeon_type dt) |
|
{ |
|
setlevel = 0; // VALGRIND |
|
|
|
leveltype = dt; // This is loaded from gnLevelTypeTbl[] in the game |
|
|
|
pHallList = 0; // VALGRIND: Linked list initialized to 0 |
|
pSetPiece_2 = 0; // VALGRIND: Always freed, even when it is not loaded |
|
|
|
nSx1 = nSx2 = nSy1 = nSy2 = 0; // VALGRIND: Looks like the DRLG code assumes these can be 0 |
|
|
|
H(LoadLvlGFX)(); |
|
} |
|
|
|
static void test_levelgen() |
|
{ |
|
// std::minstd_rand gen(123); |
|
// std::uniform_int_distribution<> dis; |
|
|
|
test_init_common(); |
|
|
|
// test_init_drlg(DTYPE_CATACOMBS); |
|
// std::vector<int> levels{2, 5, 6, 7, 8}; |
|
test_init_drlg(DTYPE_CATHEDRAL); |
|
std::vector<int> levels{1, 2, 3, 4}; |
|
|
|
DGTestCase testcase; |
|
for (int iter = 0; iter < 1000000; iter++) { |
|
testcase.seed = mix32(iter); |
|
for (auto currlevel : levels) { |
|
testcase.currlevel = currlevel; |
|
for (auto entry : {0, 1}) { |
|
testcase.entry = entry; |
|
|
|
coverage_start_round(); |
|
|
|
testcase.run(); |
|
|
|
if (coverage_found_new()) { |
|
std::cout << "new coverage, " << testcase << std::endl; |
|
} |
|
|
|
testcase.run_original_and_compare(); |
|
} |
|
} |
|
} |
|
|
|
// LoadGameLevel(TRUE, 0); |
|
} |
|
|
|
static void test_newgame() |
|
{ |
|
test_init_common(); |
|
|
|
start_game(WM_DIABNEWGAME); |
|
} |
|
|
|
static void test_levelgen_example() |
|
{ |
|
test_init_common(); |
|
test_init_drlg(DTYPE_CATHEDRAL); |
|
|
|
DGTestCase testcase; |
|
testcase.currlevel = 5; |
|
testcase.entry = 0; |
|
testcase.seed = 1364076727; |
|
|
|
testcase.run(); |
|
testcase.run_original_and_compare(); |
|
testcase.run_and_print_dungeons(); |
|
} |
|
|
|
int main(int argc, char **argv) |
|
{ |
|
#ifdef HAVE_HARNESS |
|
init_absolute(); |
|
#endif |
|
|
|
valgrind_init(); |
|
|
|
coverage_init(); |
|
|
|
// test_levelgen(); |
|
test_levelgen_example(); |
|
// test_newgame(); |
|
|
|
eprintf("Done!\n"); |
|
|
|
return 0; |
|
}
|
|
|