Adding verification for tests

This commit is contained in:
Sergio Martin 2024-01-12 21:13:51 +01:00
parent 147d0d4f52
commit 1952f8a622
9 changed files with 143 additions and 28 deletions

View File

@ -35,7 +35,7 @@ quickerNESCoreSrc = [
'source/core/Multi_Buffer.cpp',
'source/core/Nes_Emu.cpp',
'source/core/nes_ntsc.cpp',
'source/core/Nes_Vrc7.cpp'
'source/core/Nes_Vrc7.cpp',
]
# quickerNES Core Configuration
@ -43,7 +43,7 @@ quickerNESCoreSrc = [
quickerNESCoreDependency = declare_dependency(
compile_args : [ '-Wfatal-errors','-Wall' ],
include_directories : include_directories(['source', 'source/core', 'extern']),
sources : quickerNESCoreSrc,
sources : [ quickerNESCoreSrc, 'extern/metrohash128/metrohash128.cpp' ]
)
# Building playback tool
@ -53,7 +53,7 @@ quickerNESPlayerSrc = [
'extern/hqn/hqn_gui_controller.cpp',
'extern/hqn/hqn_surface.cpp',
'extern/hqn/hqn_util.cpp',
'extern/hqn/options.cpp'
'extern/hqn/options.cpp',
]
executable('player',

View File

@ -12,12 +12,6 @@
#include "Nes_Core.h"
class Nes_State;
// Register optional mappers included with Nes_Emu
void register_optional_mappers();
void register_extra_mappers();
extern const char unsupported_mapper []; // returned when cartridge uses unsupported mapper
class Nes_Emu {
public:
Nes_Emu();

View File

@ -8,6 +8,7 @@
#define _LOW_MEM_SIZE 0x800
#define _HIGH_MEM_SIZE 0x2000
#define _NAMETABLES_MEM_SIZE 0x1000
class EmuInstance
{
@ -49,8 +50,11 @@ class EmuInstance
uint8_t* getLowMem() { return _nes->low_mem(); };
uint8_t* getNametableMem() { return _nes->nametable_mem(); };
uint8_t* getHighMem() { return _nes->high_mem();};
const uint8_t* getChrMem() { return _nes->chr_mem();};
size_t getChrMemSize() { return _nes->chr_size();};
const std::string getRomSHA1() const { return _romSHA1String; };
void loadStateFile(const std::string& stateFilePath)
{
// Loading state data
@ -70,6 +74,23 @@ class EmuInstance
inline size_t getStateSize() const { return _stateSize; }
inline hash_t getStateHash()
{
MetroHash128 hash;
uint8_t stateData[_stateSize];
serializeState(stateData);
hash.Update(getLowMem(), _LOW_MEM_SIZE);
hash.Update(getHighMem(), _HIGH_MEM_SIZE);
hash.Update(getNametableMem(), _NAMETABLES_MEM_SIZE);
hash.Update(getChrMem(), getChrMemSize());
hash_t result;
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
return result;
}
void saveStateFile(const std::string& stateFilePath) const
{
std::string stateData;

View File

@ -16,6 +16,7 @@ struct stepData_t
std::string input;
uint8_t* stateData;
int32_t curBlit[BLIT_SIZE];
hash_t hash;
};
class PlaybackInstance
@ -28,6 +29,7 @@ class PlaybackInstance
step.input = input;
step.stateData = (uint8_t*) malloc(_emu->getStateSize());
_emu->serializeState(step.stateData);
step.hash = _emu->getStateHash();
saveBlit(_emu->getInternalEmulator(), step.curBlit, hqn::HQNState::NES_VIDEO_PALETTE, 0, 0, 0, 0);
// Adding the step into the sequence
@ -179,6 +181,30 @@ class PlaybackInstance
return step.stateData;
}
const hash_t getStateHash(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.hash;
}
const std::string getStateInput(const size_t stepId) const
{
// Checking the required step id does not exceed contents of the sequence
if (stepId > _stepSequence.size()) EXIT_WITH_ERROR("[Error] Attempting to render a step larger than the step sequence");
// Getting step information
const auto& step = _stepSequence[stepId];
// Returning step input
return step.input;
}
private:

View File

@ -72,14 +72,19 @@ int main(int argc, char *argv[])
// Creating emulator instance
auto e = EmuInstance(romFilePath, stateFilePath);
auto stateHash = e.getStateHash();
// Creating playback instance
auto p = PlaybackInstance(&e, sequence);
// Getting state size
auto stateSize = e.getStateSize();
// Flag to continue running playback
bool continueRunning = true;
// Variable for current step in view
ssize_t sequenceLength = sequence.size();
ssize_t sequenceLength = p.getSequenceLength();
ssize_t currentStep = 0;
// Flag to display frame information
@ -92,10 +97,13 @@ int main(int argc, char *argv[])
if (disableRender == false) p.renderFrame(currentStep);
// Getting input
const auto& input = sequence[currentStep];
const auto& input = p.getStateInput(currentStep);
// Getting state hash
const auto hash = p.getStateHash(currentStep);
// Getting state data
//const auto stateData = p.getStateData(currentStep);
const auto stateData = p.getStateData(currentStep);
// Printing data and commands
if (showFrameInfo)
@ -104,7 +112,8 @@ int main(int argc, char *argv[])
printw("[] ----------------------------------------------------------------\n");
printw("[] Current Step #: %lu / %lu\n", currentStep + 1, sequenceLength);
printw("[] Input: %s\n", input.c_str());
printw("[] Input: %s\n", input.c_str());
printw("[] State Hash: 0x%lX%lX\n", hash.first, hash.second);
// Only print commands if not in reproduce mode
if (isReproduce == false) printw("[] Commands: n: -1 m: +1 | h: -10 | j: +10 | y: -100 | u: +100 | k: -1000 | i: +1000 | s: quicksave | p: play | q: quit\n");
@ -137,7 +146,11 @@ int main(int argc, char *argv[])
{
// Storing state file
std::string saveFileName = "quicksave.state";
e.saveStateFile(saveFileName);
std::string saveData;
saveData.resize(stateSize);
memcpy(saveData.data(), stateData, stateSize);
if (saveStringToFile(saveData, saveFileName.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not save state file: %s\n", saveFileName.c_str());
printw("[] Saved state to %s\n", saveFileName.c_str());
// Do no show frame info again after this action

View File

@ -39,6 +39,11 @@ int main(int argc, char *argv[])
if (scriptJson["Initial State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Initial State File' entry is not a string\n");
std::string initialStateFilePath = scriptJson["Initial State File"].get<std::string>();
// Getting verification state file path
if (scriptJson.contains("Verification State File") == false) EXIT_WITH_ERROR("Script file missing 'Verification State File' entry\n");
if (scriptJson["Verification State File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Verification State File' entry is not a string\n");
std::string verificationStateFilePath = scriptJson["Verification State File"].get<std::string>();
// Getting sequence file path
if (scriptJson.contains("Sequence File") == false) EXIT_WITH_ERROR("Script file missing 'Sequence File' entry\n");
if (scriptJson["Sequence File"].is_string() == false) EXIT_WITH_ERROR("Script file 'Sequence File' entry is not a string\n");
@ -51,6 +56,12 @@ int main(int argc, char *argv[])
// Creating emulator instance
auto e = EmuInstance(romFilePath, initialStateFilePath);
auto initialHash = e.getStateHash();
printf("[] Initial State Hash: 0x%lX%lX\n", initialHash.first, initialHash.second);
// Getting state size
const auto stateSize = e.getStateSize();
// Getting actual ROM SHA1
auto romSHA1 = e.getRomSHA1();
@ -58,9 +69,21 @@ int main(int argc, char *argv[])
// Checking with the expected SHA1 hash
if (romSHA1 != expectedROMSHA1) EXIT_WITH_ERROR("Wrong ROM SHA1. Found: '%s', Expected: '%s'\n", romSHA1.c_str(), expectedROMSHA1.c_str());
// Loading state file for verication, if provided
std::string verificationState;
if (verificationStateFilePath != "")
{
// Loading file
if (loadStringFromFile(verificationState, verificationStateFilePath) == false)
EXIT_WITH_ERROR("[ERROR] Could not find or read from verification state file: %s\n", verificationStateFilePath.c_str());
// Checking size
if (verificationState.length() != stateSize) EXIT_WITH_ERROR("[ERROR] The provided verification state has different size from expected: %lu vs %lu\n", verificationState.length(), stateSize);
}
// Loading sequence file
std::string sequenceRaw;
if (loadStringFromFile(sequenceRaw, sequenceFilePath.c_str()) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
if (loadStringFromFile(sequenceRaw, sequenceFilePath) == false) EXIT_WITH_ERROR("[ERROR] Could not find or read from input sequence file: %s\n", sequenceFilePath.c_str());
// Building sequence information
const auto sequence = split(sequenceRaw, ' ');
@ -69,11 +92,14 @@ int main(int argc, char *argv[])
const auto sequenceLength = sequence.size();
// Printing test information
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
printf("[] ROM File: '%s'\n", romFilePath.c_str());
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
printf("[] Sequence Length: %lu\n", sequenceLength);
printf("[] Running Script: '%s'\n", scriptFilePath.c_str());
printf("[] ROM File: '%s'\n", romFilePath.c_str());
printf("[] ROM SHA1: '%s'\n", romSHA1.c_str());
printf("[] Verification State File: '%s'\n", verificationStateFilePath.c_str());
printf("[] Sequence File: '%s'\n", sequenceFilePath.c_str());
printf("[] Sequence Length: %lu\n", sequenceLength);
printf("[] State Size: %lu bytes\n", stateSize);
fflush(stdout);
// Actually running the sequence
@ -85,10 +111,33 @@ int main(int argc, char *argv[])
auto dt = std::chrono::duration_cast<std::chrono::nanoseconds>(tf - t0).count();
double elapsedTimeSeconds = (double)dt * 1.0e-9;
// Printing time information
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
// Calculating final state hash
const auto finalStateHash = e.getStateHash();
return 0;
// If provided, verify result against the passed verification movie
bool verificationPassed = true;
hash_t verificationStateHash;
if (verificationStateFilePath != "")
{
// Loading verification state into the emulator
e.deserializeState((uint8_t*)verificationState.data());
// Calculating hash for the verification state
verificationStateHash = e.getStateHash();
// Comparing hashes
if (verificationStateHash != finalStateHash) verificationPassed = false;
}
// Printing time information
printf("[] -----------------------------------------\n");
printf("[] Elapsed time: %3.3fs\n", (double)dt * 1.0e-9);
printf("[] Performance: %.3f steps / s\n", (double)sequenceLength / elapsedTimeSeconds);
printf("[] Final State Hash: 0x%lX%lX\n", finalStateHash.first, finalStateHash.second);
if (verificationStateFilePath != "")
printf("[] Verification Hash: 0x%lX%lX (%s)\n", verificationStateHash.first, verificationStateHash.second, verificationPassed ? "Passed" : "Failed");
// Fail if verification didn't pass
return verificationPassed ? 0 : -1;
}

View File

@ -9,6 +9,7 @@
#include <stdio.h>
#include <unistd.h>
#include <algorithm>
#include <metrohash128/metrohash128.h>
// If we use NCurses, we need to use the appropriate printing function
#ifdef NCURSES
@ -85,6 +86,17 @@ void refreshTerminal()
#endif // NCURSES
typedef _uint128_t hash_t;
inline hash_t calculateMetroHash(uint8_t* data, size_t size)
{
MetroHash128 hash;
hash.Update(data, size);
hash_t result;
hash.Finalize(reinterpret_cast<uint8_t *>(&result));
return result;
}
// Function to split a string into a sub-strings delimited by a character
// Taken from stack overflow answer to https://stackoverflow.com/questions/236129/how-do-i-iterate-over-the-words-of-a-string
// By Evan Teran
@ -137,9 +149,9 @@ inline void exitWithError [[noreturn]] (const char *fileName, const int lineNum
}
// Loads a string from a given file
inline bool loadStringFromFile(std::string &dst, const char *fileName)
inline bool loadStringFromFile(std::string &dst, std::string path)
{
std::ifstream fi(fileName);
std::ifstream fi(path);
// If file not found or open, return false
if (fi.good() == false) return false;

Binary file not shown.

View File

@ -2,6 +2,6 @@
"Rom File": "Castlevania (U) (PRG0) [!].nes",
"Expected ROM SHA1": "A31B8BD5B370A9103343C866F3C2B2998E889341",
"Initial State File": "",
"Final State File": "anyPercent.final.state",
"Verification State File": "anyPercent.final.state",
"Sequence File": "anyPercent.sol"
}