test: add unit test for ImageIPakPostProcessor

This commit is contained in:
Jan
2025-01-07 23:48:17 +01:00
parent e0f8b3d3ca
commit 8c8ceae0bd
10 changed files with 378 additions and 12 deletions

View File

@ -1,24 +1,98 @@
#include "MockOutputPath.h"
#include <sstream>
#include "Utils/ObjStream.h"
namespace
{
class MockFileWrapper final : public std::ostringstream
class MockFileBuffer final : public std::streambuf
{
public:
MockFileBuffer()
: m_pos(0u)
{
}
std::vector<std::uint8_t> data()
{
return std::move(m_data);
}
protected:
int_type overflow(const int_type b) override
{
if (!std::char_traits<char>::eq_int_type(b, std::char_traits<char>::eof()))
{
m_data.insert(m_data.begin() + static_cast<decltype(m_data)::difference_type>(m_pos), static_cast<std::uint8_t>(b));
++m_pos;
}
return b;
}
std::streamsize xsputn(const char* ptr, const std::streamsize count) override
{
const auto curSize = m_data.size();
const auto overrideCount = m_pos < curSize ? std::min(curSize - m_pos, static_cast<size_t>(count)) : 0u;
const auto insertCount = count - overrideCount;
if (overrideCount > 0)
{
memcpy(&m_data[m_pos], ptr, overrideCount);
m_pos += overrideCount;
ptr += overrideCount;
}
if (insertCount > 0)
{
m_data.insert(m_data.begin() + static_cast<decltype(m_data)::difference_type>(m_pos), ptr, ptr + insertCount);
m_pos += static_cast<size_t>(insertCount);
}
return count;
}
pos_type seekoff(const off_type off, const std::ios_base::seekdir dir, const std::ios_base::openmode mode) override
{
if (dir == std::ios::beg)
return seekpos(off, mode);
if (dir == std::ios::cur)
return seekpos(static_cast<pos_type>(m_pos) + off, mode);
if (off < static_cast<off_type>(m_data.size()))
return seekpos(static_cast<off_type>(m_data.size()) - off, mode);
return std::char_traits<char>::eof();
}
pos_type seekpos(const pos_type pos, std::ios_base::openmode) override
{
if (pos > m_data.size())
m_data.resize(static_cast<decltype(m_data)::size_type>(pos));
m_pos = static_cast<size_t>(pos);
return pos;
}
private:
std::vector<std::uint8_t> m_data;
std::size_t m_pos;
};
class MockFileWrapper final : public std::ostream
{
public:
MockFileWrapper(std::string name, std::vector<MockOutputFile>& files)
: m_name(std::move(name)),
: std::ostream(&m_buf),
m_name(std::move(name)),
m_files(files)
{
}
~MockFileWrapper() override
{
m_files.emplace_back(std::move(m_name), str());
m_files.emplace_back(std::move(m_name), m_buf.data());
}
private:
MockFileBuffer m_buf;
std::string m_name;
std::vector<MockOutputFile>& m_files;
};
@ -26,7 +100,7 @@ namespace
MockOutputFile::MockOutputFile() = default;
MockOutputFile::MockOutputFile(std::string name, std::string data)
MockOutputFile::MockOutputFile(std::string name, std::vector<std::uint8_t> data)
: m_name(std::move(name)),
m_data(std::move(data))
{

View File

@ -2,6 +2,7 @@
#include "SearchPath/IOutputPath.h"
#include <cstdint>
#include <sstream>
#include <string>
#include <vector>
@ -10,10 +11,10 @@ class MockOutputFile
{
public:
std::string m_name;
std::string m_data;
std::vector<std::uint8_t> m_data;
MockOutputFile();
MockOutputFile(std::string name, std::string data);
MockOutputFile(std::string name, std::vector<std::uint8_t> data);
};
class MockOutputPath final : public IOutputPath

View File

@ -0,0 +1,278 @@
#include "Image/ImageIPakPostProcessor.h"
#include "Game/T6/T6.h"
#include "KeyValuePairs/KeyValuePairsCreator.h"
#include "SearchPath/MockOutputPath.h"
#include "SearchPath/MockSearchPath.h"
#include <catch2/catch_test_macros.hpp>
#include <catch2/generators/catch_generators.hpp>
#include <catch2/matchers/catch_matchers_floating_point.hpp>
#include <filesystem>
#include <memory>
using namespace T6;
using namespace std::string_literals;
namespace
{
class TestContext
{
public:
TestContext()
: m_zone("test", 0, IGame::GetGameById(GameId::T6)),
m_zone_definition(),
m_zone_definition_context(m_zone_definition),
m_zone_states(m_zone),
m_creators(m_zone),
m_ignored_assets(),
m_out_dir(),
m_context(m_zone, &m_creators, &m_ignored_assets),
m_ipak_creator(m_zone_states.GetZoneAssetCreationState<IPakCreator>())
{
}
std::unique_ptr<IAssetPostProcessor> CreateSut()
{
return std::make_unique<ImageIPakPostProcessor<AssetImage>>(m_zone_definition_context, m_search_path, m_zone_states, m_out_dir);
}
Zone m_zone;
ZoneDefinition m_zone_definition;
ZoneDefinitionContext m_zone_definition_context;
MockSearchPath m_search_path;
ZoneAssetCreationStateContainer m_zone_states;
AssetCreatorCollection m_creators;
IgnoredAssetLookup m_ignored_assets;
MockOutputPath m_out_dir;
AssetCreationContext m_context;
IPakCreator& m_ipak_creator;
};
} // namespace
namespace test::image
{
TEST_CASE("ImageIPakPostProcessor: Handles asset type of specified asset", "[image]")
{
TestContext testContext;
const auto sut = testContext.CreateSut();
REQUIRE(sut->GetHandlingAssetType() == ASSET_TYPE_IMAGE);
}
TEST_CASE("ImageIPakPostProcessor: Adds images to ipak when obj container is specified", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage0");
REQUIRE(imageNames[1] == "testImage1");
}
TEST_CASE("ImageIPakPostProcessor: Respects lower obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage1");
REQUIRE(imageNames[1] == "testImage2");
}
TEST_CASE("ImageIPakPostProcessor: Respects upper obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage0");
REQUIRE(imageNames[1] == "testImage1");
}
TEST_CASE("ImageIPakPostProcessor: Respects upper and lower obj container boundary when adding images to ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 1, 3);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result = testContext.m_ipak_creator.GetOrAddIPak("testIpak");
REQUIRE(result);
const auto& imageNames = result->GetImageNames();
REQUIRE(imageNames.size() == 2u);
REQUIRE(imageNames[0] == "testImage1");
REQUIRE(imageNames[1] == "testImage2");
}
TEST_CASE("ImageIPakPostProcessor: Can add images to multiple ipak", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak0", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak1", ZoneDefinitionObjContainerType::IPAK, 2, 4);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
auto result0 = testContext.m_ipak_creator.GetOrAddIPak("testIpak0");
REQUIRE(result0);
const auto& imageNames0 = result0->GetImageNames();
REQUIRE(imageNames0.size() == 2u);
REQUIRE(imageNames0[0] == "testImage0");
REQUIRE(imageNames0[1] == "testImage1");
auto result1 = testContext.m_ipak_creator.GetOrAddIPak("testIpak1");
REQUIRE(result1);
const auto& imageNames1 = result1->GetImageNames();
REQUIRE(imageNames1.size() == 2u);
REQUIRE(imageNames1[0] == "testImage2");
REQUIRE(imageNames1[1] == "testImage3");
}
TEST_CASE("ImageIPakPostProcessor: Can add images to multiple ipak with gap between", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak0", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak1", ZoneDefinitionObjContainerType::IPAK, 3, 5);
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset2(ASSET_TYPE_IMAGE, "testImage2", nullptr);
sut->PostProcessAsset(imageAsset2, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset3(ASSET_TYPE_IMAGE, "testImage3", nullptr);
sut->PostProcessAsset(imageAsset3, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset4(ASSET_TYPE_IMAGE, "testImage4", nullptr);
sut->PostProcessAsset(imageAsset4, testContext.m_context);
auto result0 = testContext.m_ipak_creator.GetOrAddIPak("testIpak0");
REQUIRE(result0);
const auto& imageNames0 = result0->GetImageNames();
REQUIRE(imageNames0.size() == 2u);
REQUIRE(imageNames0[0] == "testImage0");
REQUIRE(imageNames0[1] == "testImage1");
auto result1 = testContext.m_ipak_creator.GetOrAddIPak("testIpak1");
REQUIRE(result1);
const auto& imageNames1 = result1->GetImageNames();
REQUIRE(imageNames1.size() == 2u);
REQUIRE(imageNames1[0] == "testImage3");
REQUIRE(imageNames1[1] == "testImage4");
}
TEST_CASE("ImageIPakPostProcessor: Writes IPak when finalizing", "[image]")
{
TestContext testContext;
testContext.m_zone_definition.m_obj_containers.emplace_back("testIpak", ZoneDefinitionObjContainerType::IPAK, 0, 2);
testContext.m_search_path.AddFileData("images/testImage0.iwi", "asdf0");
testContext.m_search_path.AddFileData("images/testImage1.iwi", "asdf1");
const auto sut = testContext.CreateSut();
XAssetInfo<GfxImage> imageAsset0(ASSET_TYPE_IMAGE, "testImage0", nullptr);
sut->PostProcessAsset(imageAsset0, testContext.m_context);
++testContext.m_zone_definition_context.m_asset_index_in_definition;
XAssetInfo<GfxImage> imageAsset1(ASSET_TYPE_IMAGE, "testImage1", nullptr);
sut->PostProcessAsset(imageAsset1, testContext.m_context);
sut->FinalizeZone(testContext.m_context);
const auto* mockFile = testContext.m_out_dir.GetMockedFile("testIpak.ipak");
REQUIRE(mockFile);
}
} // namespace test::image