forked from teamnwah/openmw-tes3coop
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
316 lines
10 KiB
C++
316 lines
10 KiB
C++
#include <gtest/gtest.h>
|
|
|
|
#include <boost/filesystem/fstream.hpp>
|
|
|
|
#include <components/files/configurationmanager.hpp>
|
|
#include <components/esm/esmreader.hpp>
|
|
#include <components/esm/esmwriter.hpp>
|
|
#include <components/loadinglistener/loadinglistener.hpp>
|
|
|
|
#include "apps/openmw/mwworld/esmstore.hpp"
|
|
|
|
static Loading::Listener dummyListener;
|
|
|
|
/// Base class for tests of ESMStore that rely on external content files to produce the test results
|
|
struct ContentFileTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
|
|
virtual void SetUp()
|
|
{
|
|
readContentFiles();
|
|
|
|
// load the content files
|
|
std::vector<ESM::ESMReader> readerList;
|
|
readerList.resize(mContentFiles.size());
|
|
|
|
int index=0;
|
|
for (std::vector<boost::filesystem::path>::const_iterator it = mContentFiles.begin(); it != mContentFiles.end(); ++it)
|
|
{
|
|
ESM::ESMReader lEsm;
|
|
lEsm.setEncoder(NULL);
|
|
lEsm.setIndex(index);
|
|
lEsm.setGlobalReaderList(&readerList);
|
|
lEsm.open(it->string());
|
|
readerList[index] = lEsm;
|
|
mEsmStore.load(readerList[index], &dummyListener);
|
|
|
|
++index;
|
|
}
|
|
|
|
mEsmStore.setUp();
|
|
}
|
|
|
|
virtual void TearDown()
|
|
{
|
|
}
|
|
|
|
// read absolute path to content files from openmw.cfg
|
|
void readContentFiles()
|
|
{
|
|
boost::program_options::variables_map variables;
|
|
|
|
boost::program_options::options_description desc("Allowed options");
|
|
desc.add_options()
|
|
("data", boost::program_options::value<Files::PathContainer>()->default_value(Files::PathContainer(), "data")->multitoken()->composing())
|
|
("content", boost::program_options::value<std::vector<std::string> >()->default_value(std::vector<std::string>(), "")
|
|
->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon")
|
|
("data-local", boost::program_options::value<std::string>()->default_value(""));
|
|
|
|
boost::program_options::notify(variables);
|
|
|
|
mConfigurationManager.readConfiguration(variables, desc, true);
|
|
|
|
Files::PathContainer dataDirs, dataLocal;
|
|
if (!variables["data"].empty()) {
|
|
dataDirs = Files::PathContainer(variables["data"].as<Files::PathContainer>());
|
|
}
|
|
|
|
std::string local = variables["data-local"].as<std::string>();
|
|
if (!local.empty()) {
|
|
dataLocal.push_back(Files::PathContainer::value_type(local));
|
|
}
|
|
|
|
mConfigurationManager.processPaths (dataDirs);
|
|
mConfigurationManager.processPaths (dataLocal, true);
|
|
|
|
if (!dataLocal.empty())
|
|
dataDirs.insert (dataDirs.end(), dataLocal.begin(), dataLocal.end());
|
|
|
|
Files::Collections collections (dataDirs, true);
|
|
|
|
std::vector<std::string> contentFiles = variables["content"].as<std::vector<std::string> >();
|
|
for (std::vector<std::string>::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it)
|
|
mContentFiles.push_back(collections.getPath(*it));
|
|
}
|
|
|
|
protected:
|
|
Files::ConfigurationManager mConfigurationManager;
|
|
MWWorld::ESMStore mEsmStore;
|
|
std::vector<boost::filesystem::path> mContentFiles;
|
|
};
|
|
|
|
/// Print results of the dialogue merging process, i.e. the resulting linked list.
|
|
TEST_F(ContentFileTest, dialogue_merging_test)
|
|
{
|
|
if (mContentFiles.empty())
|
|
{
|
|
std::cout << "No content files found, skipping test" << std::endl;
|
|
return;
|
|
}
|
|
|
|
const std::string file = "test_dialogue_merging.txt";
|
|
|
|
boost::filesystem::ofstream stream;
|
|
stream.open(file);
|
|
|
|
const MWWorld::Store<ESM::Dialogue>& dialStore = mEsmStore.get<ESM::Dialogue>();
|
|
for (MWWorld::Store<ESM::Dialogue>::iterator it = dialStore.begin(); it != dialStore.end(); ++it)
|
|
{
|
|
const ESM::Dialogue& dial = *it;
|
|
stream << "Dialogue: " << dial.mId << std::endl;
|
|
|
|
for (ESM::Dialogue::InfoContainer::const_iterator infoIt = dial.mInfo.begin(); infoIt != dial.mInfo.end(); ++infoIt)
|
|
{
|
|
const ESM::DialInfo& info = *infoIt;
|
|
stream << info.mId << std::endl;
|
|
}
|
|
stream << std::endl;
|
|
}
|
|
|
|
std::cout << "dialogue_merging_test successful, results printed to " << file << std::endl;
|
|
}
|
|
|
|
// Note: here we don't test records that don't use string names (e.g. Land, Pathgrid, Cell)
|
|
#define RUN_TEST_FOR_TYPES(func, arg1, arg2) \
|
|
func<ESM::Activator>(arg1, arg2); \
|
|
func<ESM::Apparatus>(arg1, arg2); \
|
|
func<ESM::Armor>(arg1, arg2); \
|
|
func<ESM::BirthSign>(arg1, arg2); \
|
|
func<ESM::BodyPart>(arg1, arg2); \
|
|
func<ESM::Book>(arg1, arg2); \
|
|
func<ESM::Class>(arg1, arg2); \
|
|
func<ESM::Clothing>(arg1, arg2); \
|
|
func<ESM::Container>(arg1, arg2); \
|
|
func<ESM::Creature>(arg1, arg2); \
|
|
func<ESM::CreatureLevList>(arg1, arg2); \
|
|
func<ESM::Dialogue>(arg1, arg2); \
|
|
func<ESM::Door>(arg1, arg2); \
|
|
func<ESM::Enchantment>(arg1, arg2); \
|
|
func<ESM::Faction>(arg1, arg2); \
|
|
func<ESM::GameSetting>(arg1, arg2); \
|
|
func<ESM::Global>(arg1, arg2); \
|
|
func<ESM::Ingredient>(arg1, arg2); \
|
|
func<ESM::ItemLevList>(arg1, arg2); \
|
|
func<ESM::Light>(arg1, arg2); \
|
|
func<ESM::Lockpick>(arg1, arg2); \
|
|
func<ESM::Miscellaneous>(arg1, arg2); \
|
|
func<ESM::NPC>(arg1, arg2); \
|
|
func<ESM::Potion>(arg1, arg2); \
|
|
func<ESM::Probe>(arg1, arg2); \
|
|
func<ESM::Race>(arg1, arg2); \
|
|
func<ESM::Region>(arg1, arg2); \
|
|
func<ESM::Repair>(arg1, arg2); \
|
|
func<ESM::Script>(arg1, arg2); \
|
|
func<ESM::Sound>(arg1, arg2); \
|
|
func<ESM::SoundGenerator>(arg1, arg2); \
|
|
func<ESM::Spell>(arg1, arg2); \
|
|
func<ESM::StartScript>(arg1, arg2); \
|
|
func<ESM::Weapon>(arg1, arg2);
|
|
|
|
template <typename T>
|
|
void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream)
|
|
{
|
|
const MWWorld::Store<T>& store = esmStore.get<T>();
|
|
outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl;
|
|
|
|
for (typename MWWorld::Store<T>::iterator it = store.begin(); it != store.end(); ++it)
|
|
{
|
|
const T& record = *it;
|
|
outStream << record.mId << std::endl;
|
|
}
|
|
|
|
outStream << std::endl;
|
|
}
|
|
|
|
/// Print some basic diagnostics about the loaded content files, e.g. number of records and names of those records
|
|
/// Also used to test the iteration order of records
|
|
TEST_F(ContentFileTest, content_diagnostics_test)
|
|
{
|
|
if (mContentFiles.empty())
|
|
{
|
|
std::cout << "No content files found, skipping test" << std::endl;
|
|
return;
|
|
}
|
|
|
|
const std::string file = "test_content_diagnostics.txt";
|
|
|
|
boost::filesystem::ofstream stream;
|
|
stream.open(file);
|
|
|
|
RUN_TEST_FOR_TYPES(printRecords, mEsmStore, stream);
|
|
|
|
std::cout << "diagnostics_test successful, results printed to " << file << std::endl;
|
|
}
|
|
|
|
// TODO:
|
|
/// Print results of autocalculated NPC spell lists. Also serves as test for attribute/skill autocalculation which the spell autocalculation heavily relies on
|
|
/// - even incorrect rounding modes can completely change the resulting spell lists.
|
|
/*
|
|
TEST_F(ContentFileTest, autocalc_test)
|
|
{
|
|
if (mContentFiles.empty())
|
|
{
|
|
std::cout << "No content files found, skipping test" << std::endl;
|
|
return;
|
|
}
|
|
|
|
|
|
}
|
|
*/
|
|
|
|
/// Base class for tests of ESMStore that do not rely on external content files
|
|
struct StoreTest : public ::testing::Test
|
|
{
|
|
protected:
|
|
MWWorld::ESMStore mEsmStore;
|
|
};
|
|
|
|
|
|
/// Create an ESM file in-memory containing the specified record.
|
|
/// @param deleted Write record with deleted flag?
|
|
template <typename T>
|
|
Files::IStreamPtr getEsmFile(T record, bool deleted)
|
|
{
|
|
ESM::ESMWriter writer;
|
|
std::stringstream* stream = new std::stringstream;
|
|
writer.setFormat(0);
|
|
writer.save(*stream);
|
|
writer.startRecord(T::sRecordId);
|
|
record.save(writer, deleted);
|
|
writer.endRecord(T::sRecordId);
|
|
|
|
return Files::IStreamPtr(stream);
|
|
}
|
|
|
|
/// Tests deletion of records.
|
|
TEST_F(StoreTest, delete_test)
|
|
{
|
|
const std::string recordId = "foobar";
|
|
|
|
typedef ESM::Apparatus RecordType;
|
|
|
|
RecordType record;
|
|
record.blank();
|
|
record.mId = recordId;
|
|
|
|
ESM::ESMReader reader;
|
|
std::vector<ESM::ESMReader> readerList;
|
|
readerList.push_back(reader);
|
|
reader.setGlobalReaderList(&readerList);
|
|
|
|
// master file inserts a record
|
|
Files::IStreamPtr file = getEsmFile(record, false);
|
|
reader.open(file, "filename");
|
|
mEsmStore.load(reader, &dummyListener);
|
|
mEsmStore.setUp();
|
|
|
|
ASSERT_TRUE (mEsmStore.get<RecordType>().getSize() == 1);
|
|
|
|
// now a plugin deletes it
|
|
file = getEsmFile(record, true);
|
|
reader.open(file, "filename");
|
|
mEsmStore.load(reader, &dummyListener);
|
|
mEsmStore.setUp();
|
|
|
|
ASSERT_TRUE (mEsmStore.get<RecordType>().getSize() == 0);
|
|
|
|
// now another plugin inserts it again
|
|
// expected behaviour is the record to reappear rather than staying deleted
|
|
file = getEsmFile(record, false);
|
|
reader.open(file, "filename");
|
|
mEsmStore.load(reader, &dummyListener);
|
|
mEsmStore.setUp();
|
|
|
|
ASSERT_TRUE (mEsmStore.get<RecordType>().getSize() == 1);
|
|
}
|
|
|
|
/// Tests overwriting of records.
|
|
TEST_F(StoreTest, overwrite_test)
|
|
{
|
|
const std::string recordId = "foobar";
|
|
const std::string recordIdUpper = "Foobar";
|
|
|
|
typedef ESM::Apparatus RecordType;
|
|
|
|
RecordType record;
|
|
record.blank();
|
|
record.mId = recordId;
|
|
|
|
ESM::ESMReader reader;
|
|
std::vector<ESM::ESMReader> readerList;
|
|
readerList.push_back(reader);
|
|
reader.setGlobalReaderList(&readerList);
|
|
|
|
// master file inserts a record
|
|
Files::IStreamPtr file = getEsmFile(record, false);
|
|
reader.open(file, "filename");
|
|
mEsmStore.load(reader, &dummyListener);
|
|
mEsmStore.setUp();
|
|
|
|
// now a plugin overwrites it with changed data
|
|
record.mId = recordIdUpper; // change id to uppercase, to test case smashing while we're at it
|
|
record.mModel = "the_new_model";
|
|
file = getEsmFile(record, false);
|
|
reader.open(file, "filename");
|
|
mEsmStore.load(reader, &dummyListener);
|
|
mEsmStore.setUp();
|
|
|
|
// verify that changes were actually applied
|
|
const RecordType* overwrittenRec = mEsmStore.get<RecordType>().search(recordId);
|
|
|
|
ASSERT_TRUE (overwrittenRec != NULL);
|
|
|
|
ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model");
|
|
}
|