#pragma once #include "test_data_generator.h" #include #include #include #include #include #include #include #include #include namespace yams::test { struct Fixture { std::filesystem::path path; std::string description; std::vector tags; }; struct SearchTopicSpec { std::string name; std::size_t documentsPerTopic{4}; std::string extension{".txt"}; std::vector keywords; std::vector tags; }; struct SearchCorpusSpec { std::string rootDirectory{"search"}; std::size_t bytesPerDocument{512}; std::vector commonTags; std::vector topics; }; struct SearchCorpus { std::vector fixtures; std::map> fixturesByTopic; }; class FixtureManager { public: FixtureManager() : FixtureManager(make_root_path()) {} explicit FixtureManager(std::filesystem::path root) : root_(std::move(root)) { std::error_code ec; std::filesystem::create_directories(root_, ec); } FixtureManager(FixtureManager&&) noexcept = default; FixtureManager& operator=(FixtureManager&&) noexcept = default; FixtureManager(const FixtureManager&) = delete; FixtureManager& operator=(const FixtureManager&) = delete; ~FixtureManager() { cleanupFixtures(); } [[nodiscard]] const std::filesystem::path& root() const noexcept { return root_; } Fixture createTextFixture(std::string_view name, std::string_view content, std::vector tags = {}) { const auto relative = std::filesystem::path(name); const auto target = ensurePath(relative); std::ofstream file(target, std::ios::binary); file.write(content.data(), static_cast(content.size())); file.close(); created_.push_back(target); return Fixture{target, std::string{name}, std::move(tags)}; } Fixture createBinaryFixture(std::string_view name, std::vector data, std::vector tags = {}) { const auto relative = std::filesystem::path(name); const auto target = ensurePath(relative); std::ofstream file(target, std::ios::binary); file.write(reinterpret_cast(data.data()), static_cast(data.size())); file.close(); created_.push_back(target); return Fixture{target, std::string{name}, std::move(tags)}; } std::vector createCorpus(std::size_t count, std::size_t averageSizeBytes, double duplicationRate = 0.1) { std::vector fixtures; fixtures.reserve(count); TestDataGenerator generator; auto corpus = generator.generateCorpus(count, averageSizeBytes, duplicationRate); for (std::size_t i = 0; i < corpus.size(); ++i) { const auto name = "doc_" + std::to_string(i) + ".txt"; fixtures.push_back(createTextFixture(name, corpus[i], {"corpus"})); } return fixtures; } SearchCorpus createSearchCorpus(const SearchCorpusSpec& spec) { SearchCorpus corpus; TestDataGenerator generator; auto make_tags = [&](const SearchTopicSpec& topic) { std::vector tags = spec.commonTags; tags.reserve(tags.size() + topic.tags.size() + 1); tags.insert(tags.end(), topic.tags.begin(), topic.tags.end()); tags.push_back("topic:" + topic.name); return tags; }; for (const auto& topic : spec.topics) { auto tags = make_tags(topic); for (std::size_t i = 0; i < std::max(1, topic.documentsPerTopic); ++i) { auto filename = topic.name + "_" + std::to_string(i) + topic.extension; auto relative = std::filesystem::path(spec.rootDirectory) / filename; auto content = generator.generateTextDocument(spec.bytesPerDocument, topic.name); for (const auto& keyword : topic.keywords) { content.append(" "); content.append(keyword); } auto fixture = createTextFixture(relative.string(), content, tags); corpus.fixtures.push_back(fixture); corpus.fixturesByTopic[topic.name].push_back(fixture); } } return corpus; } void cleanupFixtures() { if (root_.empty()) { return; } std::error_code ec; std::filesystem::remove_all(root_, ec); created_.clear(); root_.clear(); } static Fixture getSimplePDF() { return catalog().simple; } static Fixture getComplexPDF() { return catalog().complex; } static Fixture getCorruptedPDF() { return catalog().corrupted; } static Fixture getLargeTextFile(std::size_t size = 512 * 1024) { auto& cat = catalog(); if (!std::filesystem::exists(cat.largeText.path) || std::filesystem::file_size(cat.largeText.path) != size) { TestDataGenerator generator; cat.largeText = cat.manager->createTextFixture( "large_text.txt", generator.generateTextDocument(size, "fixture"), {"text", "large"}); } return cat.largeText; } private: struct FixtureCatalog { std::unique_ptr manager; Fixture simple; Fixture complex; Fixture corrupted; Fixture largeText; FixtureCatalog() : manager(std::make_unique(make_root_path())) { TestDataGenerator generator; simple = manager->createPdfFixture("simple.pdf", 2, {"pdf", "simple"}); complex = manager->createPdfFixture("complex.pdf", 8, {"pdf", "complex"}); corrupted = manager->createBinaryFixture( "corrupted.pdf", generator.generateCorruptedPDF(), {"pdf", "corrupted"}); largeText = manager->createTextFixture( "large_text.txt", generator.generateTextDocument(512 * 1024, "large fixture"), {"text", "large"}); } }; Fixture createPdfFixture(std::string_view name, std::size_t pages, std::vector tags = {}) { TestDataGenerator generator; auto data = generator.generatePDF(pages); return createBinaryFixture(name, std::move(data), std::move(tags)); } static FixtureCatalog& catalog() { static FixtureCatalog instance; return instance; } static std::filesystem::path make_root_path() { auto base = std::filesystem::temp_directory_path(); auto stamp = std::chrono::steady_clock::now().time_since_epoch().count(); std::uniform_int_distribution dist(0, 9999); thread_local std::mt19937_64 rng{std::random_device{}()}; auto suffix = dist(rng); return base / ("yams_fixtures_" + std::to_string(stamp) + "_" + std::to_string(suffix)); } std::filesystem::path ensurePath(const std::filesystem::path& relative) { auto target = root_ / relative; std::error_code ec; std::filesystem::create_directories(target.parent_path(), ec); return target; } std::filesystem::path root_; std::vector created_; }; } // namespace yams::test