#include #include #include #include #include #include #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 readerList; readerList.resize(mContentFiles.size()); int index=0; for (std::vector::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()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) ("content", boost::program_options::value >()->default_value(std::vector(), "") ->multitoken(), "content file(s): esm/esp, or omwgame/omwaddon") ("data-local", boost::program_options::value()->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()); } std::string local = variables["data-local"].as(); 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 contentFiles = variables["content"].as >(); for (std::vector::iterator it = contentFiles.begin(); it != contentFiles.end(); ++it) mContentFiles.push_back(collections.getPath(*it)); } protected: Files::ConfigurationManager mConfigurationManager; MWWorld::ESMStore mEsmStore; std::vector 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& dialStore = mEsmStore.get(); for (MWWorld::Store::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(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); \ func(arg1, arg2); template void printRecords(MWWorld::ESMStore& esmStore, std::ostream& outStream) { const MWWorld::Store& store = esmStore.get(); outStream << store.getSize() << " " << T::getRecordType() << " records" << std::endl; for (typename MWWorld::Store::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 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 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().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().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().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 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().search(recordId); ASSERT_TRUE (overwrittenRec != NULL); ASSERT_TRUE (overwrittenRec && overwrittenRec->mModel == "the_new_model"); }