#include #include #include #include #include #include #include #include #include #include #include namespace { using testing::ElementsAre; using testing::Pair; std::vector> asVector(const LuaUtil::ScriptIdsWithInitializationData& d) { std::vector> res; for (const auto& [k, v] : d) res.emplace_back(k, std::string(v)); return res; } TEST(LuaConfigurationTest, ValidOMWScripts) { ESM::LuaScriptsCfg cfg; LuaUtil::parseOMWScripts(cfg, R"X( # Lines starting with '#' are comments GLOBAL: my_mod/#some_global_script.lua # Script that will be automatically attached to the player PLAYER :my_mod/player.lua CUSTOM : my_mod/some_other_script.lua NPC , CREATURE PLAYER : my_mod/some_other_script.lua)X"); LuaUtil::parseOMWScripts(cfg, ":my_mod/player.LUA \r\nCREATURE,CUSTOM: my_mod/creature.lua\r\n"); ASSERT_EQ(cfg.mScripts.size(), 6); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[0]), "GLOBAL : my_mod/#some_global_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[1]), "PLAYER : my_mod/player.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[2]), "CUSTOM : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[3]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[4]), ": my_mod/player.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(cfg.mScripts[5]), "CUSTOM CREATURE : my_mod/creature.lua"); LuaUtil::ScriptsConfiguration conf; conf.init(std::move(cfg)); ASSERT_EQ(conf.size(), 4); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "GLOBAL : my_mod/#some_global_script.lua"); // cfg.mScripts[1] is overridden by cfg.mScripts[4] // cfg.mScripts[2] is overridden by cfg.mScripts[3] EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "PLAYER NPC CREATURE : my_mod/some_other_script.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[2]), ": my_mod/player.lua"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[3]), "CUSTOM CREATURE : my_mod/creature.lua"); EXPECT_THAT(asVector(conf.getGlobalConf()), ElementsAre(Pair(0, ""))); EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(1, ""))); const ESM::RefId something = ESM::RefId::stringRefId("something"); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, something, ESM::RefNum())), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, something, ESM::RefNum())), ElementsAre(Pair(1, ""))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, something, ESM::RefNum())), ElementsAre(Pair(1, ""), Pair(3, ""))); // Check that initialization cleans old data cfg = ESM::LuaScriptsCfg(); conf.init(std::move(cfg)); EXPECT_EQ(conf.size(), 0); } TEST(LuaConfigurationTest, InvalidOMWScripts) { ESM::LuaScriptsCfg cfg; EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "GLOBAL: something"), "Lua script should have suffix '.lua', got: GLOBAL: something"); EXPECT_ERROR(LuaUtil::parseOMWScripts(cfg, "something.lua"), "No flags found in: something.lua"); cfg.mScripts.clear(); EXPECT_NO_THROW(LuaUtil::parseOMWScripts(cfg, "GLOBAL, PLAYER: something.lua")); LuaUtil::ScriptsConfiguration conf; EXPECT_ERROR(conf.init(std::move(cfg)), "Global script can not have local flags"); } TEST(LuaConfigurationTest, ConfInit) { ESM::LuaScriptsCfg cfg; ESM::LuaScriptCfg& script1 = cfg.mScripts.emplace_back(); script1.mScriptPath = VFS::Path::Normalized("Script1.lua"); script1.mInitializationData = "data1"; script1.mFlags = ESM::LuaScriptCfg::sPlayer; script1.mTypes.push_back(ESM::REC_CREA); script1.mRecords.push_back({ true, ESM::RefId::stringRefId("record1"), "dataRecord1" }); script1.mRefs.push_back({ true, 2, 3, "" }); script1.mRefs.push_back({ true, 2, 4, "" }); ESM::LuaScriptCfg& script2 = cfg.mScripts.emplace_back(); script2.mScriptPath = VFS::Path::Normalized("Script2.lua"); script2.mFlags = ESM::LuaScriptCfg::sCustom; script2.mTypes.push_back(ESM::REC_CONT); ESM::LuaScriptCfg& script1Extra = cfg.mScripts.emplace_back(); script1Extra.mScriptPath = VFS::Path::Normalized("script1.LUA"); script1Extra.mFlags = ESM::LuaScriptCfg::sCustom | ESM::LuaScriptCfg::sMerge; script1Extra.mTypes.push_back(ESM::REC_NPC_); script1Extra.mRecords.push_back({ false, ESM::RefId::stringRefId("rat"), "" }); script1Extra.mRecords.push_back({ true, ESM::RefId::stringRefId("record2"), "" }); script1Extra.mRefs.push_back({ true, 3, 5, "dataRef35" }); script1Extra.mRefs.push_back({ false, 2, 3, "" }); LuaUtil::ScriptsConfiguration conf; conf.init(cfg); ASSERT_EQ(conf.size(), 2); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[0]), "CUSTOM PLAYER CREATURE NPC : script1.lua ; data 5 bytes ; 3 records ; 4 objects"); EXPECT_EQ(LuaUtil::scriptCfgToString(conf[1]), "CUSTOM CONTAINER : script2.lua"); EXPECT_THAT(asVector(conf.getPlayerConf()), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, ESM::RefId::stringRefId("something"), ESM::RefNum())), ElementsAre(Pair(1, ""))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CREA, ESM::RefId::stringRefId("guar"), ESM::RefNum())), ElementsAre(Pair(0, "data1"))); EXPECT_THAT( asVector(conf.getLocalConf(ESM::REC_CREA, ESM::RefId::stringRefId("rat"), ESM::RefNum())), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, ESM::RefId::stringRefId("record1"), ESM::RefNum())), ElementsAre(Pair(0, "dataRecord1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_DOOR, ESM::RefId::stringRefId("record2"), ESM::RefNum())), ElementsAre(Pair(0, "data1"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, ESM::RefId::stringRefId("record3"), { 1, 1 })), ElementsAre(Pair(0, "data1"))); EXPECT_THAT( asVector(conf.getLocalConf(ESM::REC_NPC_, ESM::RefId::stringRefId("record3"), { 2, 3 })), ElementsAre()); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_NPC_, ESM::RefId::stringRefId("record3"), { 3, 5 })), ElementsAre(Pair(0, "dataRef35"))); EXPECT_THAT(asVector(conf.getLocalConf(ESM::REC_CONT, ESM::RefId::stringRefId("record4"), { 2, 4 })), ElementsAre(Pair(0, "data1"), Pair(1, ""))); ESM::LuaScriptCfg& script3 = cfg.mScripts.emplace_back(); script3.mScriptPath = VFS::Path::Normalized("script1.lua"); script3.mFlags = ESM::LuaScriptCfg::sGlobal; EXPECT_ERROR(conf.init(cfg), "Flags mismatch for script1.lua"); } TEST(LuaConfigurationTest, Serialization) { sol::state lua; LuaUtil::BasicSerializer serializer; ESM::ESMWriter writer; writer.setAuthor(""); writer.setDescription(""); writer.setRecordCount(1); writer.setFormatVersion(ESM::CurrentContentFormatVersion); writer.setVersion(); writer.addMaster("morrowind.esm", 0); ESM::LuaScriptsCfg cfg; std::string luaData; { sol::table data(lua, sol::create); data["number"] = 5; data["string"] = "some value"; data["fargoth"] = ESM::RefNum{ 128964, 1 }; luaData = LuaUtil::serialize(data, &serializer); } { ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back(); script.mScriptPath = VFS::Path::Normalized("test_global.lua"); script.mFlags = ESM::LuaScriptCfg::sGlobal; script.mInitializationData = luaData; } { ESM::LuaScriptCfg& script = cfg.mScripts.emplace_back(); script.mScriptPath = VFS::Path::Normalized("test_local.lua"); script.mFlags = ESM::LuaScriptCfg::sMerge; script.mTypes.push_back(ESM::REC_DOOR); script.mTypes.push_back(ESM::REC_MISC); script.mRecords.push_back({ true, ESM::RefId::stringRefId("rat"), luaData }); script.mRecords.push_back({ false, ESM::RefId::stringRefId("chargendoorjournal"), "" }); script.mRefs.push_back({ true, 128964, 1, "" }); script.mRefs.push_back({ true, 128962, 1, luaData }); } std::stringstream stream; writer.save(stream); writer.startRecord(ESM::REC_LUAL); cfg.save(writer); writer.endRecord(ESM::REC_LUAL); writer.close(); std::string serializedOMWAddon = stream.str(); { // Save for manual testing. std::ofstream f(TestingOpenMW::outputFilePath("lua_conf_test.omwaddon"), std::ios::binary); f << serializedOMWAddon; f.close(); } ESM::ESMReader reader; reader.open(std::make_unique(serializedOMWAddon), "lua_conf_test.omwaddon"); ASSERT_EQ(reader.getRecordCount(), 1); ASSERT_EQ(reader.getRecName().toInt(), ESM::REC_LUAL); reader.getRecHeader(); ESM::LuaScriptsCfg loadedCfg; loadedCfg.load(reader); ASSERT_EQ(loadedCfg.mScripts.size(), cfg.mScripts.size()); for (size_t i = 0; i < cfg.mScripts.size(); ++i) { EXPECT_EQ(loadedCfg.mScripts[i].mScriptPath, cfg.mScripts[i].mScriptPath); EXPECT_EQ(loadedCfg.mScripts[i].mFlags, cfg.mScripts[i].mFlags); EXPECT_EQ(loadedCfg.mScripts[i].mInitializationData, cfg.mScripts[i].mInitializationData); ASSERT_EQ(loadedCfg.mScripts[i].mTypes.size(), cfg.mScripts[i].mTypes.size()); for (size_t j = 0; j < cfg.mScripts[i].mTypes.size(); ++j) EXPECT_EQ(loadedCfg.mScripts[i].mTypes[j], cfg.mScripts[i].mTypes[j]); ASSERT_EQ(loadedCfg.mScripts[i].mRecords.size(), cfg.mScripts[i].mRecords.size()); for (size_t j = 0; j < cfg.mScripts[i].mRecords.size(); ++j) { EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mAttach, cfg.mScripts[i].mRecords[j].mAttach); EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mRecordId, cfg.mScripts[i].mRecords[j].mRecordId); EXPECT_EQ(loadedCfg.mScripts[i].mRecords[j].mInitializationData, cfg.mScripts[i].mRecords[j].mInitializationData); } ASSERT_EQ(loadedCfg.mScripts[i].mRefs.size(), cfg.mScripts[i].mRefs.size()); for (size_t j = 0; j < cfg.mScripts[i].mRefs.size(); ++j) { EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mAttach, cfg.mScripts[i].mRefs[j].mAttach); EXPECT_EQ(loadedCfg.mScripts[i].mRefs[j].mRefnumIndex, cfg.mScripts[i].mRefs[j].mRefnumIndex); EXPECT_EQ( loadedCfg.mScripts[i].mRefs[j].mRefnumContentFile, cfg.mScripts[i].mRefs[j].mRefnumContentFile); EXPECT_EQ( loadedCfg.mScripts[i].mRefs[j].mInitializationData, cfg.mScripts[i].mRefs[j].mInitializationData); } } { ESM::ReadersCache readers(4); readers.get(0)->openRaw(std::make_unique("dummyData"), "a.omwaddon"); readers.get(1)->openRaw(std::make_unique("dummyData"), "b.omwaddon"); readers.get(2)->openRaw(std::make_unique("dummyData"), "Morrowind.esm"); readers.get(3)->openRaw(std::make_unique("dummyData"), "c.omwaddon"); reader.setIndex(3); reader.resolveParentFileIndices(readers); } loadedCfg.adjustRefNums(reader); EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumIndex, cfg.mScripts[1].mRefs[0].mRefnumIndex); EXPECT_EQ(loadedCfg.mScripts[1].mRefs[0].mRefnumContentFile, 2); { sol::table data = LuaUtil::deserialize( lua.lua_state(), loadedCfg.mScripts[1].mRefs[1].mInitializationData, &serializer); ESM::RefNum adjustedRef = data["fargoth"].get(); EXPECT_EQ(adjustedRef.mIndex, 128964u); EXPECT_EQ(adjustedRef.mContentFile, 2); } } }