Merge remote-tracking branch 'scrawl/esm_rewrite'
commit
de98ee0062
@ -1,14 +0,0 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "components/misc/stringops.hpp"
|
||||
|
||||
struct StringOpsTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
virtual void SetUp()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void TearDown()
|
||||
{
|
||||
}
|
||||
};
|
@ -0,0 +1,315 @@
|
||||
#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");
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue