#include #include #include #include #include #include #include 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(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); } }