Some checks failed
Secret scan / gitleaks (push) Failing after 8s
Mirror source to Forgejo / mirror (push) Has been skipped
Release Candidate / Validate promotion and preview release (push) Has been cancelled
Tests / Static Analysis (push) Has been cancelled
Tests / Warm Conan & ccache (linux-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-arm64) (push) Has been cancelled
Tests / Warm Conan & ccache (linux-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (macos-x86_64) (push) Has been cancelled
Tests / Warm Conan & ccache (windows-x86_64) (push) Has been cancelled
Tests / warm (push) Has been cancelled
Tests / Tests (linux-arm64) (push) Has been cancelled
Tests / Tests (macos-arm64) (push) Has been cancelled
Tests / Tests (linux-x86_64-asan) (push) Has been cancelled
Tests / Tests (linux-x86_64-ubsan) (push) Has been cancelled
Tests / Tests (linux-x86_64) (push) Has been cancelled
Tests / Tests (windows-x86_64) (push) Has been cancelled
* test(ci): stabilize main matrix (#112) Signed-off-by: trvon <me@trevon.dev> * ci(release): preview experimental promotion (#111) Signed-off-by: trvon <me@trevon.dev> * ci(release): reconcile draft promotion safely (#113) * ci(release): reconcile draft promotion Create promotion visibility independently of release readiness. Leave existing PRs untouched to preserve concurrent maintainer edits. Validate against current main without masking candidate rejection. Signed-off-by: trvon <me@trevon.dev> * ci: isolate hook Git environments Prevent temporary Git fixtures from modifying the calling worktree. Clear repository-local hook variables before the unchanged build gates, and isolate validator and fixture Git subprocesses from caller overrides. Exercise linked-worktree config, ref and index preservation offline. Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev> * fix(ci): reject invalid candidate evidence Explain missing main history without mutating refs or weakening promotion validation. Match experiment boolean and aggregation parsing, respect candidate-limit precedence, and return issues for malformed configuration or metric values. Signed-off-by: trvon <me@trevon.dev> * fix: preserve completion and retrieval contracts Propagate derivation SQL failures, invalidate KG status caches, and avoid unused preview reads. Align explicit hash validation and export standalone client dependencies. Keep secret exceptions fixture-scoped and remove undefined behavior from status fixtures. Signed-off-by: trvon <me@trevon.dev> * fix: fence asynchronous enrichment completion Coordinate graph and NL commits before acknowledging enrichment. Atomically classify embedding reuse, fence repair status writes, and preserve newer vectors on old failures. Upgrade content readiness triggers and keep repository counters coherent. Six affected ASan suites pass; this does not provide restart replay or generation-scoped vector storage. Signed-off-by: trvon <me@trevon.dev> * fix: bound semantic graph output reservation Use retained neighbors for admission and node capacity instead of mixing corpus and pair counts. Preserve single-neighbor output and avoid quadratic reservation. Characterization regression fails three assertions before the fix; full component ASan suite passes afterward. Signed-off-by: trvon <me@trevon.dev> * fix: fence standalone embedding repair completion Mint attempt tokens before reading canonical repair input and publish only current persisted document embeddings. Do not skip stale physical vectors. Make SQLite preparation fault injection connection-local and deterministic. Full component and metadata ASan suites pass. Signed-off-by: trvon <me@trevon.dev> * fix: preserve enrichment failure reporting Admit pending embedding attempts before provider failures while preserving completed results. Route title no-op acknowledgement through the writer so flush reports SQL failures and duplicate jobs can retry. Four affected full ASan suites pass; host configuration hashes unchanged. Signed-off-by: trvon <me@trevon.dev> * test: align explicit hash prefix boundaries Reject five characters and accept the supported six-character minimum. Full hybrid_search suite passes. Separate daemon shutdown sanitizer failure remains under investigation despite a passing targeted rerun. Signed-off-by: trvon <me@trevon.dev> * test: align UI hash validation boundary Signed-off-by: trvon <me@trevon.dev> --------- Signed-off-by: trvon <me@trevon.dev>
220 lines
7.7 KiB
C++
220 lines
7.7 KiB
C++
#include <catch2/catch_test_macros.hpp>
|
|
|
|
#include <exception>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include "common/test_helpers_catch2.h"
|
|
|
|
#include <yams/cli/yams_cli.h>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
namespace {
|
|
|
|
class CaptureStdout {
|
|
public:
|
|
CaptureStdout() : old_(std::cout.rdbuf(buffer_.rdbuf())) {}
|
|
~CaptureStdout() { std::cout.rdbuf(old_); }
|
|
|
|
std::string str() const { return buffer_.str(); }
|
|
|
|
private:
|
|
std::ostringstream buffer_;
|
|
std::streambuf* old_{nullptr};
|
|
};
|
|
|
|
int run_cli(const std::vector<std::string>& args, std::string* output = nullptr,
|
|
std::optional<std::string> stdinData = std::nullopt) {
|
|
std::vector<std::string> effectiveArgs = args;
|
|
const bool hasDataDirFlag =
|
|
std::find(effectiveArgs.begin(), effectiveArgs.end(), "--data-dir") !=
|
|
effectiveArgs.end() ||
|
|
std::find(effectiveArgs.begin(), effectiveArgs.end(), "--storage") != effectiveArgs.end();
|
|
if (!hasDataDirFlag) {
|
|
if (const char* dataDir = std::getenv("YAMS_DATA_DIR"); dataDir && *dataDir) {
|
|
effectiveArgs.insert(effectiveArgs.begin() + 1, std::string(dataDir));
|
|
effectiveArgs.insert(effectiveArgs.begin() + 1, "--data-dir");
|
|
}
|
|
}
|
|
int rc = 0;
|
|
std::string captured;
|
|
try {
|
|
yams::cli::YamsCLI cli;
|
|
std::vector<char*> argv;
|
|
argv.reserve(effectiveArgs.size());
|
|
for (const auto& arg : effectiveArgs) {
|
|
argv.push_back(const_cast<char*>(arg.c_str()));
|
|
}
|
|
|
|
CaptureStdout capture;
|
|
|
|
std::istringstream in;
|
|
std::streambuf* oldIn = nullptr;
|
|
if (stdinData.has_value()) {
|
|
in.str(*stdinData);
|
|
oldIn = std::cin.rdbuf(in.rdbuf());
|
|
}
|
|
|
|
{
|
|
rc = cli.run(static_cast<int>(argv.size()), argv.data());
|
|
}
|
|
|
|
if (oldIn) {
|
|
std::cin.rdbuf(oldIn);
|
|
}
|
|
if (captured.empty()) {
|
|
captured = capture.str();
|
|
}
|
|
} catch (const std::exception& e) {
|
|
rc = -1;
|
|
captured = std::string("EXCEPTION: ") + e.what();
|
|
} catch (...) {
|
|
rc = -1;
|
|
captured = "EXCEPTION: unknown";
|
|
}
|
|
if (output) {
|
|
*output = std::move(captured);
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
nlohmann::json parse_json_output(const std::string& raw) {
|
|
const auto pos = raw.find('{');
|
|
if (pos == std::string::npos) {
|
|
return nlohmann::json{};
|
|
}
|
|
return nlohmann::json::parse(raw.substr(pos));
|
|
}
|
|
|
|
} // namespace
|
|
|
|
TEST_CASE("IntegrationSmoke.AddCommandSandboxOperations", "[smoke][integrationsmoke]") {
|
|
const fs::path root = yams::test::make_temp_dir("yams_add_sandbox_");
|
|
const fs::path dataDir = root / "data";
|
|
const fs::path inputDir = root / "input";
|
|
fs::create_directories(dataDir);
|
|
fs::create_directories(inputDir / "sub");
|
|
|
|
const fs::path fileA = inputDir / "alpha.txt";
|
|
const fs::path fileB = inputDir / "sub" / "beta.md";
|
|
const fs::path fileSkip = inputDir / "sub" / "skip.log";
|
|
const fs::path fileM1 = inputDir / "multi1.txt";
|
|
const fs::path fileM2 = inputDir / "multi2.txt";
|
|
|
|
yams::test::write_file(fileA, "alpha sandbox add content\n");
|
|
yams::test::write_file(fileB, "beta sandbox add content\n");
|
|
yams::test::write_file(fileSkip, "skip me\n");
|
|
yams::test::write_file(fileM1, "multi file one\n");
|
|
yams::test::write_file(fileM2, "multi file two\n");
|
|
|
|
yams::test::ScopedEnvVar embedded("YAMS_EMBEDDED", std::string("1"));
|
|
yams::test::ScopedEnvVar inDaemon("YAMS_IN_DAEMON", std::nullopt);
|
|
yams::test::ScopedEnvVar dataEnv("YAMS_DATA_DIR", dataDir.string());
|
|
yams::test::ScopedEnvVar storageEnv("YAMS_STORAGE", dataDir.string());
|
|
yams::test::ScopedEnvVar disableVectors("YAMS_DISABLE_VECTORS", std::string("1"));
|
|
yams::test::ScopedEnvVar skipModelLoading("YAMS_SKIP_MODEL_LOADING", std::string("1"));
|
|
yams::test::ScopedEnvVar disableWatcher("YAMS_DISABLE_SESSION_WATCHER", std::string("1"));
|
|
yams::test::ScopedEnvVar disableAutoStart("YAMS_CLI_DISABLE_DAEMON_AUTOSTART",
|
|
std::string("1"));
|
|
|
|
std::string out;
|
|
|
|
int rc = run_cli({"yams", "--json", "add", fileA.string(), "--name", "single-alpha.txt",
|
|
"--tags", "sandbox,single", "--metadata", "source=test"},
|
|
&out);
|
|
INFO(out);
|
|
REQUIRE(rc == 0);
|
|
auto j = parse_json_output(out);
|
|
INFO(out);
|
|
REQUIRE(j.is_object());
|
|
REQUIRE(j.contains("summary"));
|
|
CHECK(j["summary"]["failed"].get<int>() == 0);
|
|
|
|
rc = run_cli({"yams", "--json", "add", inputDir.string(), "--recursive", "--include",
|
|
"*.txt,*.md", "--exclude", "*.log", "--tags", "sandbox,dir", "--sync"},
|
|
&out);
|
|
INFO(out);
|
|
REQUIRE(rc == 0);
|
|
j = parse_json_output(out);
|
|
INFO(out);
|
|
REQUIRE(j.is_object());
|
|
REQUIRE(j.contains("summary"));
|
|
CHECK(j["summary"]["failed"].get<int>() == 0);
|
|
CHECK(j.contains("results"));
|
|
CHECK_FALSE(j["results"].empty());
|
|
|
|
rc = run_cli(
|
|
{"yams", "--json", "add", "-", "--name", "stdin-sandbox.txt", "--tags", "sandbox,stdin"},
|
|
&out, std::string("stdin payload for sandbox add\n"));
|
|
INFO(out);
|
|
REQUIRE(rc == 0);
|
|
j = parse_json_output(out);
|
|
INFO(out);
|
|
REQUIRE(j.is_object());
|
|
REQUIRE(j.contains("summary"));
|
|
CHECK(j["summary"]["failed"].get<int>() == 0);
|
|
|
|
rc = run_cli(
|
|
{"yams", "--json", "add", fileM1.string(), fileM2.string(), "--tags", "sandbox,multi"},
|
|
&out);
|
|
INFO(out);
|
|
REQUIRE(rc == 0);
|
|
j = parse_json_output(out);
|
|
INFO(out);
|
|
REQUIRE(j.is_object());
|
|
REQUIRE(j.contains("results"));
|
|
CHECK(static_cast<int>(j["results"].size()) >= 2);
|
|
CHECK(j["summary"]["failed"].get<int>() == 0);
|
|
|
|
std::error_code ec;
|
|
fs::remove_all(root, ec);
|
|
}
|
|
|
|
TEST_CASE("IntegrationSmoke.AddCommandFallsBackToInProcessWhenDaemonStartupUnavailable",
|
|
"[smoke][integrationsmoke]") {
|
|
const fs::path root = yams::test::make_temp_dir("yams_add_fallback_");
|
|
const fs::path dataDir = root / "data";
|
|
const fs::path blockedSocketDir = root / "blocked-socket";
|
|
const fs::path inputFile = root / "fallback.txt";
|
|
fs::create_directories(dataDir);
|
|
fs::create_directories(blockedSocketDir);
|
|
|
|
yams::test::write_file(inputFile, "fallback add content\n");
|
|
|
|
yams::test::ScopedEnvVar embedded("YAMS_EMBEDDED", std::nullopt);
|
|
yams::test::ScopedEnvVar inDaemon("YAMS_IN_DAEMON", std::nullopt);
|
|
yams::test::ScopedEnvVar dataEnv("YAMS_DATA_DIR", dataDir.string());
|
|
yams::test::ScopedEnvVar storageEnv("YAMS_STORAGE", dataDir.string());
|
|
yams::test::ScopedEnvVar disableVectors("YAMS_DISABLE_VECTORS", std::string("1"));
|
|
yams::test::ScopedEnvVar skipModelLoading("YAMS_SKIP_MODEL_LOADING", std::string("1"));
|
|
yams::test::ScopedEnvVar disableWatcher("YAMS_DISABLE_SESSION_WATCHER", std::string("1"));
|
|
yams::test::ScopedEnvVar daemonSocket("YAMS_DAEMON_SOCKET",
|
|
(blockedSocketDir / "daemon.sock").string());
|
|
|
|
std::error_code ec;
|
|
fs::permissions(blockedSocketDir, fs::perms::none, fs::perm_options::replace, ec);
|
|
|
|
std::string out;
|
|
const int rc =
|
|
run_cli({"yams", "--json", "add", inputFile.string(), "--tags", "fallback"}, &out);
|
|
|
|
fs::permissions(blockedSocketDir, fs::perms::owner_all, fs::perm_options::replace, ec);
|
|
|
|
INFO(out);
|
|
REQUIRE(rc == 0);
|
|
auto j = parse_json_output(out);
|
|
INFO(out);
|
|
REQUIRE(j.is_object());
|
|
REQUIRE(j.contains("summary"));
|
|
CHECK(j["summary"]["failed"].get<int>() == 0);
|
|
REQUIRE(j.contains("results"));
|
|
REQUIRE_FALSE(j["results"].empty());
|
|
CHECK(j["results"][0]["success"].get<bool>());
|
|
}
|