yams/tests/unit/plugins/symbol_extractor_query_test.cpp
2026-01-08 23:05:57 -05:00

156 lines
4.7 KiB
C++

#include <cstring>
#include <string>
#include <vector>
#include <catch2/catch_test_macros.hpp>
#include <yams/compat/dlfcn.h>
#include <yams/plugins/abi.h>
#include <yams/plugins/symbol_extractor_v1.h>
namespace {
/**
* @brief RAII wrapper for plugin handle
*/
struct PluginHandle {
void* h{};
PluginHandle() = default;
PluginHandle(const PluginHandle&) = delete;
PluginHandle& operator=(const PluginHandle&) = delete;
PluginHandle(PluginHandle&& o) noexcept : h(o.h) { o.h = nullptr; }
PluginHandle& operator=(PluginHandle&& o) noexcept {
if (this != &o) {
if (h)
dlclose(h);
h = o.h;
o.h = nullptr;
}
return *this;
}
~PluginHandle() {
if (h)
dlclose(h);
}
};
void free_result(yams_symbol_extractor_v1* api, yams_symbol_extraction_result_v1* res) {
if (!api || !res)
return;
api->free_result(api->self, res);
}
yams_symbol_extractor_v1* load_plugin(const char* so_path, PluginHandle& ph) {
ph.h = dlopen(so_path, RTLD_LAZY | RTLD_LOCAL);
if (!ph.h)
return nullptr;
auto getabi = (int (*)())dlsym(ph.h, "yams_plugin_get_abi_version");
auto getiface =
(int (*)(const char*, uint32_t, void**))dlsym(ph.h, "yams_plugin_get_interface");
auto init = (int (*)(const char*, const void*))dlsym(ph.h, "yams_plugin_init");
if (!getabi || !getiface || !init)
return nullptr;
if (getabi() <= 0)
return nullptr;
if (init("{\n \"languages\": [\"cpp\", \"python\"]\n}", nullptr) != 0)
return nullptr;
void* ptr = nullptr;
int rc = getiface(YAMS_IFACE_SYMBOL_EXTRACTOR_V1, YAMS_IFACE_SYMBOL_EXTRACTOR_V1_VERSION, &ptr);
if (rc != 0 || !ptr)
return nullptr;
return reinterpret_cast<yams_symbol_extractor_v1*>(ptr);
}
const char* get_plugin_path() {
#if defined(_WIN32)
return "builddir/plugins/symbol_extractor_treesitter/yams_symbol_extractor.dll";
#elif defined(__APPLE__)
return "builddir/plugins/symbol_extractor_treesitter/yams_symbol_extractor.dylib";
#else
return "builddir/plugins/symbol_extractor_treesitter/yams_symbol_extractor.so";
#endif
}
} // namespace
TEST_CASE("Symbol extractor C++ detection", "[plugins][symbol-extractor][cpp]") {
#if defined(_WIN32)
SKIP("Symbol extractor plugin not available on Windows in test environment");
#endif
PluginHandle ph;
auto* api = load_plugin(get_plugin_path(), ph);
if (!api) {
SKIP("Symbol extractor plugin not available");
}
SECTION("detects function and class") {
const char* code = R"CPP(
class Foo { public: void bar(); };
int baz(int x) { return x+1; }
)CPP";
yams_symbol_extraction_result_v1* out = nullptr;
int rc =
api->extract_symbols(api->self, code, std::strlen(code), "/tmp/test.cpp", "cpp", &out);
if (rc != 0 || !out) {
SKIP("Symbol grammar not available");
}
size_t funcs = 0, classes = 0;
bool has_scope = false;
for (size_t i = 0; i < out->symbol_count; ++i) {
if (out->symbols[i].kind && std::strcmp(out->symbols[i].kind, "function") == 0)
++funcs;
if (out->symbols[i].kind && (std::strcmp(out->symbols[i].kind, "class") == 0 ||
std::strcmp(out->symbols[i].kind, "struct") == 0))
++classes;
if (out->symbols[i].scope && std::strlen(out->symbols[i].scope) > 0) {
has_scope = true;
}
}
CHECK(funcs >= 1u);
CHECK(classes >= 1u);
CHECK(has_scope);
free_result(api, out);
}
}
TEST_CASE("Symbol extractor Python detection", "[plugins][symbol-extractor][python]") {
#if defined(_WIN32)
SKIP("Symbol extractor plugin not available on Windows in test environment");
#endif
PluginHandle ph;
auto* api = load_plugin(get_plugin_path(), ph);
if (!api) {
SKIP("Symbol extractor plugin not available");
}
SECTION("detects function") {
const char* code = R"PY(
def foo(x):
return x+1
)PY";
yams_symbol_extraction_result_v1* out = nullptr;
int rc = api->extract_symbols(api->self, code, std::strlen(code), "/tmp/test.py", "python",
&out);
if (rc != 0 || !out) {
SKIP("Symbol grammar not available");
}
size_t funcs = 0;
for (size_t i = 0; i < out->symbol_count; ++i) {
if (out->symbols[i].kind && std::strcmp(out->symbols[i].kind, "function") == 0)
++funcs;
}
CHECK(funcs >= 1u);
free_result(api, out);
}
}