Added commandline-operated test for verifying cooker output
This commit is contained in:
parent
9d23d9550a
commit
1360202ee5
|
@ -1 +1 @@
|
||||||
Subproject commit c7e755f3089b5c2cedbd5f9963220d784e137f8e
|
Subproject commit c98f4b3dcef68724627af91ac4103de7f28d2a2b
|
|
@ -255,7 +255,8 @@ HEADERS += \
|
||||||
Resource/Cooker/CStringCooker.h \
|
Resource/Cooker/CStringCooker.h \
|
||||||
Resource/Scan/CScan.h \
|
Resource/Scan/CScan.h \
|
||||||
Resource/Scan/SScanParametersMP1.h \
|
Resource/Scan/SScanParametersMP1.h \
|
||||||
Resource/Scan/ELogbookCategory.h
|
Resource/Scan/ELogbookCategory.h \
|
||||||
|
NCoreTests.h
|
||||||
|
|
||||||
# Source Files
|
# Source Files
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
@ -372,7 +373,8 @@ SOURCES += \
|
||||||
Tweaks/CTweakLoader.cpp \
|
Tweaks/CTweakLoader.cpp \
|
||||||
Tweaks/CTweakCooker.cpp \
|
Tweaks/CTweakCooker.cpp \
|
||||||
Resource/Cooker/CStringCooker.cpp \
|
Resource/Cooker/CStringCooker.cpp \
|
||||||
Resource/Scan/CScan.cpp
|
Resource/Scan/CScan.cpp \
|
||||||
|
NCoreTests.cpp
|
||||||
|
|
||||||
# Codegen
|
# Codegen
|
||||||
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
CODEGEN_DIR = $$EXTERNALS_DIR/CodeGen
|
||||||
|
|
|
@ -6,8 +6,10 @@
|
||||||
class IUIRelay
|
class IUIRelay
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
virtual void ShowMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
||||||
|
virtual void ShowMessageBoxAsync(const TString& rkInfoBoxTitle, const TString& rkMessage) = 0;
|
||||||
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
|
virtual bool AskYesNoQuestion(const TString& rkInfoBoxTitle, const TString& rkQuestion) = 0;
|
||||||
|
virtual bool OpenProject(const TString& kPath = "") = 0;
|
||||||
};
|
};
|
||||||
extern IUIRelay *gpUIRelay;
|
extern IUIRelay *gpUIRelay;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,200 @@
|
||||||
|
#include "NCoreTests.h"
|
||||||
|
#include "IUIRelay.h"
|
||||||
|
#include "Core/GameProject/CGameProject.h"
|
||||||
|
#include "Core/GameProject/CResourceEntry.h"
|
||||||
|
#include "Core/GameProject/CResourceIterator.h"
|
||||||
|
#include "Core/Resource/Cooker/CResourceCooker.h"
|
||||||
|
|
||||||
|
namespace NCoreTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Checks for a parameter in the commandline stream */
|
||||||
|
const char* ParseParameter(const char* pkParmName, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
const uint kParmLen = strlen(pkParmName);
|
||||||
|
|
||||||
|
for (int i=0; i<argc; i++)
|
||||||
|
{
|
||||||
|
if( strncmp(argv[i], pkParmName, kParmLen) == 0 )
|
||||||
|
{
|
||||||
|
// Found the parameter. Make sure there is enough space in the
|
||||||
|
// string for the parameter value before returning it.
|
||||||
|
if( strlen(argv[i]) >= kParmLen+2 &&
|
||||||
|
argv[i][kParmLen] == '=')
|
||||||
|
{
|
||||||
|
return &argv[i][kParmLen+1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find the parameter.
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Checks for the existence of a token in the commandline stream */
|
||||||
|
bool ParseToken(const char* pkToken, int argc, char* argv[])
|
||||||
|
{
|
||||||
|
for (int i=0; i<argc; i++)
|
||||||
|
{
|
||||||
|
if( strcmp(argv[i], pkToken) == 0 )
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't find the token.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Check commandline input to see if the user is running a test */
|
||||||
|
bool RunTests(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
if( ParseToken("ValidateCooker", argc, argv) )
|
||||||
|
{
|
||||||
|
// Fetch parameters
|
||||||
|
const char* pkType = ParseParameter("-type", argc, argv);
|
||||||
|
EResourceType Type = TEnumReflection<EResourceType>::ConvertStringToValue(pkType);
|
||||||
|
bool AllowDump = ParseToken("-allowdump", argc, argv);
|
||||||
|
|
||||||
|
if( Type == EResourceType::Invalid )
|
||||||
|
{
|
||||||
|
gpUIRelay->ShowMessageBox("ValidateCooker", "Usage: ValidateCooker -type=<ResourceType> [-allowdump] [-project=<Project>]");
|
||||||
|
}
|
||||||
|
else if( gpUIRelay->OpenProject(ParseParameter("-project", argc, argv)) )
|
||||||
|
{
|
||||||
|
ValidateCooker(Type, AllowDump);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No test being run.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Validate all cooker output for the given resource type matches the original asset data */
|
||||||
|
bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents)
|
||||||
|
{
|
||||||
|
debugf( "Validating output of %s cooker...",
|
||||||
|
TEnumReflection<EResourceType>::ConvertValueToString(ResourceType) );
|
||||||
|
|
||||||
|
// There must be a project loaded
|
||||||
|
CResourceStore* pStore = gpResourceStore;
|
||||||
|
CGameProject* pProject = (pStore ? pStore->Project() : nullptr);
|
||||||
|
|
||||||
|
if (!pProject)
|
||||||
|
{
|
||||||
|
errorf("Cooker unit test failed; no project loaded");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TString ResourcesDir = pProject->ResourcesDir(false);
|
||||||
|
uint NumValid = 0, NumInvalid = 0;
|
||||||
|
|
||||||
|
// Iterate through all resources
|
||||||
|
for (CResourceIterator It(pStore); It; ++It)
|
||||||
|
{
|
||||||
|
if (It->ResourceType() != ResourceType || !It->HasCookedVersion())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Get original cooked data
|
||||||
|
TString CookedPath = It->CookedAssetPath(true);
|
||||||
|
CFileInStream FileStream(ResourcesDir / CookedPath, EEndian::BigEndian);
|
||||||
|
|
||||||
|
if (!FileStream.IsValid())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<uint8> OriginalData( FileStream.Size() );
|
||||||
|
FileStream.ReadBytes(OriginalData.data(), OriginalData.size());
|
||||||
|
FileStream.Close();
|
||||||
|
|
||||||
|
// Generate new cooked data
|
||||||
|
std::vector<char> NewData;
|
||||||
|
CVectorOutStream MemoryStream(&NewData, EEndian::BigEndian);
|
||||||
|
CResourceCooker::CookResource(*It, MemoryStream);
|
||||||
|
|
||||||
|
// Start our comparison by making sure the sizes match up
|
||||||
|
const uint kAlignment = (It->Game() >= EGame::Corruption ? 64 : 32);
|
||||||
|
const uint kAlignedOriginalSize = ALIGN( (uint) OriginalData.size(), kAlignment );
|
||||||
|
const uint kAlignedNewSize = ALIGN( (uint) NewData.size(), kAlignment );
|
||||||
|
const char* pkInvalidReason = "";
|
||||||
|
bool IsValid = false;
|
||||||
|
|
||||||
|
if( kAlignedOriginalSize == kAlignedNewSize &&
|
||||||
|
OriginalData.size() >= NewData.size() )
|
||||||
|
{
|
||||||
|
// Compare actual data. Note that the original asset can have alignment padding
|
||||||
|
// at the end, which is applied by the pak but usually preserved in extracted
|
||||||
|
// files. We do not include this in the comparison as missing padding does not
|
||||||
|
// indicate malformed data.
|
||||||
|
uint DataSize = Math::Min(OriginalData.size(), NewData.size());
|
||||||
|
|
||||||
|
if( memcmp(OriginalData.data(), NewData.data(), DataSize) == 0 )
|
||||||
|
{
|
||||||
|
// Verify any missing data at the end is padding.
|
||||||
|
bool MissingData = false;
|
||||||
|
|
||||||
|
if( OriginalData.size() > NewData.size() )
|
||||||
|
{
|
||||||
|
for( uint i=DataSize; i<OriginalData.size(); i++ )
|
||||||
|
{
|
||||||
|
if( OriginalData[i] != 0xFF )
|
||||||
|
{
|
||||||
|
MissingData = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if( !MissingData )
|
||||||
|
{
|
||||||
|
// All tests passed!
|
||||||
|
IsValid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "missing data";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "data mismatch";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pkInvalidReason = "size mismatch";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Print test results
|
||||||
|
if( IsValid )
|
||||||
|
{
|
||||||
|
debugf( "[SUCCESS] %s", *CookedPath );
|
||||||
|
NumValid++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
debugf( "[FAILED: %s] %s", pkInvalidReason, *CookedPath );
|
||||||
|
NumInvalid++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if( DumpInvalidFileContents )
|
||||||
|
{
|
||||||
|
TString DumpPath = "dump" / CookedPath;
|
||||||
|
FileUtil::MakeDirectory( DumpPath.GetFileDirectory() );
|
||||||
|
|
||||||
|
CFileOutStream DumpFile(DumpPath, EEndian::BigEndian);
|
||||||
|
DumpFile.WriteBytes( NewData.data(), NewData.size() );
|
||||||
|
DumpFile.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test complete
|
||||||
|
bool TestSuccess = (NumInvalid == 0);
|
||||||
|
debugf( "Test %s; checked %d resources, %d passed, %d failed",
|
||||||
|
TestSuccess ? "SUCCEEDED" : "FAILED",
|
||||||
|
NumValid + NumInvalid, NumValid, NumInvalid );
|
||||||
|
|
||||||
|
return TestSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // end namespace NCoreTests
|
|
@ -0,0 +1,18 @@
|
||||||
|
#ifndef NCORETESTS_H
|
||||||
|
#define NCORETESTS_H
|
||||||
|
|
||||||
|
#include "Core/Resource/EResType.h"
|
||||||
|
|
||||||
|
/** Unit tests for Core */
|
||||||
|
namespace NCoreTests
|
||||||
|
{
|
||||||
|
|
||||||
|
/** Check commandline input to see if the user is running a unit test */
|
||||||
|
bool RunTests(int argc, char *argv[]);
|
||||||
|
|
||||||
|
/** Validate all cooker output for the given resource type matches the original asset data */
|
||||||
|
bool ValidateCooker(EResourceType ResourceType, bool DumpInvalidFileContents);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NCORETESTS_H
|
|
@ -218,7 +218,7 @@ void CPropertyNameGenerator::Generate(const SPropertyNameGenerationParameters& r
|
||||||
// If we have too many saved results, then to avoid crashing we will force enable log output.
|
// If we have too many saved results, then to avoid crashing we will force enable log output.
|
||||||
if (mGeneratedNames.size() > 9999)
|
if (mGeneratedNames.size() > 9999)
|
||||||
{
|
{
|
||||||
gpUIRelay->AsyncMessageBox("Warning", "There are over 10,000 results. Results will no longer print to the screen. Check the log for the remaining output.");
|
gpUIRelay->ShowMessageBoxAsync("Warning", "There are over 10,000 results. Results will no longer print to the screen. Check the log for the remaining output.");
|
||||||
WriteToLog = true;
|
WriteToLog = true;
|
||||||
SaveResults = false;
|
SaveResults = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ CEditorApplication::CEditorApplication(int& rArgc, char **ppArgv)
|
||||||
, mpActiveProject(nullptr)
|
, mpActiveProject(nullptr)
|
||||||
, mpWorldEditor(nullptr)
|
, mpWorldEditor(nullptr)
|
||||||
, mpProjectDialog(nullptr)
|
, mpProjectDialog(nullptr)
|
||||||
|
, mInitialized(false)
|
||||||
{
|
{
|
||||||
mLastUpdate = CTimer::GlobalTime();
|
mLastUpdate = CTimer::GlobalTime();
|
||||||
|
|
||||||
|
@ -40,10 +41,14 @@ void CEditorApplication::InitEditor()
|
||||||
mpWorldEditor = new CWorldEditor();
|
mpWorldEditor = new CWorldEditor();
|
||||||
mpProjectDialog = new CProjectSettingsDialog(mpWorldEditor);
|
mpProjectDialog = new CProjectSettingsDialog(mpWorldEditor);
|
||||||
mpWorldEditor->showMaximized();
|
mpWorldEditor->showMaximized();
|
||||||
|
mInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CEditorApplication::CloseAllEditors()
|
bool CEditorApplication::CloseAllEditors()
|
||||||
{
|
{
|
||||||
|
if (!mInitialized)
|
||||||
|
return true;
|
||||||
|
|
||||||
// Close active editor windows.
|
// Close active editor windows.
|
||||||
foreach (IEditor *pEditor, mEditorWindows)
|
foreach (IEditor *pEditor, mEditorWindows)
|
||||||
{
|
{
|
||||||
|
|
|
@ -25,6 +25,7 @@ class CEditorApplication : public QApplication
|
||||||
CProjectSettingsDialog *mpProjectDialog;
|
CProjectSettingsDialog *mpProjectDialog;
|
||||||
QVector<IEditor*> mEditorWindows;
|
QVector<IEditor*> mEditorWindows;
|
||||||
QMap<CResourceEntry*,IEditor*> mEditingMap;
|
QMap<CResourceEntry*,IEditor*> mEditingMap;
|
||||||
|
bool mInitialized;
|
||||||
|
|
||||||
QTimer mRefreshTimer;
|
QTimer mRefreshTimer;
|
||||||
double mLastUpdate;
|
double mLastUpdate;
|
||||||
|
|
|
@ -25,9 +25,16 @@ public:
|
||||||
|
|
||||||
// Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure
|
// Note: All function calls should be deferred with QMetaObject::invokeMethod to ensure
|
||||||
// that they run on the UI thread instead of whatever thread we happen to be on.
|
// that they run on the UI thread instead of whatever thread we happen to be on.
|
||||||
virtual void AsyncMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
virtual void ShowMessageBox(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
||||||
{
|
{
|
||||||
QMetaObject::invokeMethod(this, "AsyncMessageBoxSlot", Qt::QueuedConnection,
|
QMetaObject::invokeMethod(this, "MessageBoxSlot", GetConnectionType(),
|
||||||
|
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
||||||
|
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void ShowMessageBoxAsync(const TString& rkInfoBoxTitle, const TString& rkMessage)
|
||||||
|
{
|
||||||
|
QMetaObject::invokeMethod(this, "MessageBoxSlot", Qt::QueuedConnection,
|
||||||
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
Q_ARG(QString, TO_QSTRING(rkInfoBoxTitle)),
|
||||||
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
Q_ARG(QString, TO_QSTRING(rkMessage)) );
|
||||||
}
|
}
|
||||||
|
@ -42,8 +49,17 @@ public:
|
||||||
return RetVal;
|
return RetVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public slots:
|
virtual bool OpenProject(const TString& kPath = "")
|
||||||
void AsyncMessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
|
{
|
||||||
|
bool RetVal;
|
||||||
|
QMetaObject::invokeMethod(this, "OpenProjectSlot", GetConnectionType(),
|
||||||
|
Q_RETURN_ARG(bool, RetVal),
|
||||||
|
Q_ARG(QString, TO_QSTRING(kPath)) );
|
||||||
|
return RetVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void MessageBoxSlot(const QString& rkInfoBoxTitle, const QString& rkMessage)
|
||||||
{
|
{
|
||||||
UICommon::InfoMsg(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkMessage);
|
UICommon::InfoMsg(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkMessage);
|
||||||
}
|
}
|
||||||
|
@ -52,6 +68,11 @@ public slots:
|
||||||
{
|
{
|
||||||
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);
|
return UICommon::YesNoQuestion(gpEdApp->WorldEditor(), rkInfoBoxTitle, rkQuestion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool OpenProjectSlot(const QString& kPath)
|
||||||
|
{
|
||||||
|
return !kPath.isEmpty() ? gpEdApp->OpenProject(kPath) : UICommon::OpenProject();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CUIRELAY_H
|
#endif // CUIRELAY_H
|
||||||
|
|
|
@ -141,6 +141,13 @@ inline bool YesNoQuestion(QWidget *pParent, QString InfoBoxTitle, QString Questi
|
||||||
return Button == QMessageBox::Yes;
|
return Button == QMessageBox::Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline bool OpenProject()
|
||||||
|
{
|
||||||
|
QWidget* pMainWindow = (QWidget*) gpEdApp->WorldEditor();
|
||||||
|
QString ProjPath = UICommon::OpenFileDialog(pMainWindow, "Open Project", "Game Project (*.prj)");
|
||||||
|
return ProjPath.isEmpty() ? false : gpEdApp->OpenProject(ProjPath);
|
||||||
|
}
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const QColor kImportantButtonColor(36, 100, 100);
|
const QColor kImportantButtonColor(36, 100, 100);
|
||||||
|
|
||||||
|
|
|
@ -408,8 +408,7 @@ void CWorldEditor::Paste()
|
||||||
|
|
||||||
void CWorldEditor::OpenProject()
|
void CWorldEditor::OpenProject()
|
||||||
{
|
{
|
||||||
QString ProjPath = UICommon::OpenFileDialog(this, "Open Project", "Game Project (*.prj)");
|
UICommon::OpenProject();
|
||||||
if (!ProjPath.isEmpty()) gpEdApp->OpenProject(ProjPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CWorldEditor::OpenRecentProject()
|
void CWorldEditor::OpenRecentProject()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "UICommon.h"
|
#include "UICommon.h"
|
||||||
#include <Common/Log.h>
|
#include <Common/Log.h>
|
||||||
|
|
||||||
|
#include <Core/NCoreTests.h>
|
||||||
#include <Core/Resource/Script/NGameList.h>
|
#include <Core/Resource/Script/NGameList.h>
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
|
@ -60,6 +61,12 @@ public:
|
||||||
gpEditorStore->ConditionalSaveStore();
|
gpEditorStore->ConditionalSaveStore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for unit tests being run
|
||||||
|
if ( NCoreTests::RunTests(argc, argv) )
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
// Execute application
|
// Execute application
|
||||||
App.InitEditor();
|
App.InitEditor();
|
||||||
return App.exec();
|
return App.exec();
|
||||||
|
|
Loading…
Reference in New Issue