yams/tests/unit/extraction/text_basic_handler_catch2_test.cpp
trvon 8a1f753ba3
fix(extraction): build TextBasicHandler capabilities from one extension set
supportedExtensions() returned its static set by value, so capabilities()
constructed the extension vector from begin() of one temporary and end()
of another. That range is undefined behavior. libstdc++ and libc++ release
iterators happened to walk the first copy to completion, but the MSVC debug
STL rejects it in _Verify_range with a modal "Microsoft Visual C++ Runtime
Library" assertion dialog. HandlerRegistry::selectBest calls capabilities()
for every candidate, so every lines: search and every applyExtractScope call
hung forever on Windows Debug builds.

Return the shared set by const reference and take begin()/end() from that
one instance. New text_basic_handler unit tests pin the shared instance,
the capability list, registry selection and line-range extraction; the old
code also fails them under -D_GLIBCXX_DEBUG with "function requires a valid
iterator range".

Signed-off-by: trvon <me@trevon.dev>
2026-10-01 10:35:32 -06:00

85 lines
3.2 KiB
C++

// SPDX-License-Identifier: GPL-3.0-or-later
// TextBasicHandler unit tests: capability reporting, format selection, and line-range extraction
// (the handler behind `get --range` and the search `lines:` qualifier).
#include <catch2/catch_test_macros.hpp>
#include <yams/extraction/format_handlers/text_basic_handler.hpp>
#include <cstring>
#include <set>
#include <string>
#include <string_view>
#include <vector>
using yams::extraction::format::ExtractionQuery;
using yams::extraction::format::HandlerRegistry;
using yams::extraction::format::Scope;
using yams::extraction::format::TextBasicHandler;
namespace {
std::vector<std::byte> bytesOf(std::string_view text) {
std::vector<std::byte> out(text.size());
std::memcpy(out.data(), text.data(), text.size());
return out;
}
} // namespace
// capabilities() built its extension list from begin()/end() of two different temporary sets.
// libstdc++/libc++ release iterators happened to walk the first set to completion, but the MSVC
// debug STL rejects the mismatched range with a modal assertion dialog, which hung every
// `lines:` search on Windows Debug builds.
TEST_CASE("TextBasicHandler: the supported extension set is one shared instance",
"[unit][extraction][text-basic-handler]") {
const auto& first = TextBasicHandler::supportedExtensions();
const auto& second = TextBasicHandler::supportedExtensions();
CHECK((&first == &second));
CHECK(first.contains(".md"));
CHECK(first.contains(".txt"));
}
TEST_CASE("TextBasicHandler: capabilities list every supported extension exactly once",
"[unit][extraction][text-basic-handler]") {
TextBasicHandler handler;
const auto caps = handler.capabilities();
const auto& supported = TextBasicHandler::supportedExtensions();
REQUIRE((caps.extensions.size() == supported.size()));
const std::set<std::string> listed(caps.extensions.begin(), caps.extensions.end());
CHECK((listed.size() == caps.extensions.size()));
for (const auto& ext : caps.extensions) {
CHECK(supported.contains(ext));
CHECK(handler.supports("", ext));
}
CHECK(caps.supportsRange);
}
TEST_CASE("TextBasicHandler: registry selects it for text and not for binary formats",
"[unit][extraction][text-basic-handler]") {
HandlerRegistry registry;
yams::extraction::format::registerTextBasicHandler(registry);
CHECK(registry.selectBest("text/markdown", ".md"));
CHECK(registry.selectBest("", ".TXT"));
CHECK_FALSE(registry.selectBest("application/pdf", ".pdf"));
}
TEST_CASE("TextBasicHandler: a line range selects those lines",
"[unit][extraction][text-basic-handler]") {
HandlerRegistry registry;
yams::extraction::format::registerTextBasicHandler(registry);
ExtractionQuery query;
query.scope = Scope::Range;
query.range = "2-3";
auto lines = registry.extract("text/plain", ".txt", bytesOf("one\ntwo\nthree\nfour\n"), query);
REQUIRE(lines);
REQUIRE(lines.value().text);
CHECK((*lines.value().text == "two\nthree"));
query.range = "two-five";
auto invalid = registry.extract("text/plain", ".txt", bytesOf("one\ntwo\n"), query);
REQUIRE_FALSE(invalid);
CHECK((invalid.error().code == yams::ErrorCode::InvalidArgument));
}