mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-04 10:48:20 +00:00
Merge pull request #2517 from DeusData/fix/uninstall-keeps-indexes-v2
fix(cli): keep project indexes by default during uninstall
This commit is contained in:
5 files changed
+405
-37
No files matched your search
@@ -185,7 +185,14 @@ Installed through **npm or pip**? Update with your package manager on every plat
|
||||
codebase-memory-mcp uninstall
|
||||
```
|
||||
|
||||
Removes owned agent config entries, skills, hooks, instructions, and the installed binary. Existing graph indexes are listed and deleted only after confirmation.
|
||||
Removes owned agent config entries, skills, hooks, instructions, and the installed binary. Existing graph indexes are listed and **kept by default**. `-y`/`--yes`, `-n`/`--no`, and noninteractive input keep them unless `--delete-indexes` is explicitly given. An interactive terminal without that flag is asked separately; the default answer is to keep them.
|
||||
|
||||
```bash
|
||||
codebase-memory-mcp uninstall -y --delete-indexes # also delete every project index
|
||||
codebase-memory-mcp uninstall --dry-run --delete-indexes # preview; change nothing
|
||||
```
|
||||
|
||||
`--delete-indexes` is explicit consent and takes precedence over `--no`; `--dry-run` always preserves the files. When indexes are kept, uninstall prints their cache directory (`${CBM_CACHE_DIR:-~/.cache/codebase-memory-mcp}`) and how to remove them.
|
||||
|
||||
The install script placed beside the binary is **reported, not deleted** — uninstall prints its path and the `rm` command for it. It is left alone on purpose: it may be your own copy, a symlink into a checkout, or managed by a package manager, and an uninstaller should not delete a file it cannot prove it owns.
|
||||
|
||||
|
||||
+3
-1
@@ -55,9 +55,11 @@ codebase-memory-mcp install # configure all detected coding agents
|
||||
codebase-memory-mcp --version
|
||||
codebase-memory-mcp --help
|
||||
codebase-memory-mcp update # update to latest release
|
||||
codebase-memory-mcp uninstall # remove agent configs
|
||||
codebase-memory-mcp uninstall # remove agent configs; keep indexes by default
|
||||
```
|
||||
|
||||
Uninstall keeps project indexes with `--yes`, `--no`, or noninteractive input unless you explicitly add `--delete-indexes`. An interactive terminal without the flag asks separately and defaults to keeping them. `--delete-indexes` overrides `--no`; add `--dry-run` to preview without changing files. Kept indexes remain in the cache directory printed by uninstall.
|
||||
|
||||
### CLI Mode
|
||||
|
||||
Every MCP tool is also available directly from the command line:
|
||||
|
||||
+54
-26
@@ -12943,6 +12943,41 @@ static int cli_uninstall_activate(void *opaque) {
|
||||
return CLI_OK;
|
||||
}
|
||||
|
||||
/* A generic auto-answer is not consent to erase project indexes. An explicit
|
||||
* --delete-indexes takes precedence even over --no; otherwise only a separate
|
||||
* interactive answer can select deletion. This helper decides and reports,
|
||||
* but all deletion remains in the final guarded uninstall activation. */
|
||||
static bool uninstall_decide_index_deletion(const char *home, bool requested, bool dry_run) {
|
||||
int index_count = count_db_indexes(home);
|
||||
if (index_count <= 0) {
|
||||
return false;
|
||||
}
|
||||
printf("\nFound %d index(es):\n", index_count);
|
||||
cbm_list_indexes(home);
|
||||
bool delete_indexes = requested;
|
||||
if (!requested) {
|
||||
bool can_ask = g_auto_answer == 0;
|
||||
#ifdef _WIN32
|
||||
can_ask = can_ask && _isatty(_fileno(stdin));
|
||||
#else
|
||||
can_ask = can_ask && isatty(fileno(stdin));
|
||||
#endif
|
||||
delete_indexes = can_ask && prompt_yn("Delete these indexes? (default: no)");
|
||||
}
|
||||
if (!delete_indexes) {
|
||||
const char *cache_dir = get_cache_dir(home);
|
||||
printf("Indexes kept in %s. To remove them, uninstall with --delete-indexes "
|
||||
"or delete the project .db files there.\n",
|
||||
cache_dir ? cache_dir : "the cache directory");
|
||||
return false;
|
||||
}
|
||||
if (dry_run) {
|
||||
printf("(dry-run — indexes would be deleted)\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int cbm_cmd_uninstall(int argc, char **argv) {
|
||||
/* `uninstall --help` used to UNINSTALL.
|
||||
*
|
||||
@@ -12956,20 +12991,25 @@ int cbm_cmd_uninstall(int argc, char **argv) {
|
||||
* cannot auto-confirm the destruction we are trying to prevent. */
|
||||
for (int i = 0; i < argc; i++) {
|
||||
if (argv && argv[i] && (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0)) {
|
||||
printf("Usage: codebase-memory-mcp uninstall [options]\n\n"
|
||||
"Removes the codebase-memory-mcp binary, its agent configurations and,\n"
|
||||
"with confirmation, its indexes. THIS IS DESTRUCTIVE.\n\n"
|
||||
"Options:\n"
|
||||
" --dry-run Show what would be removed, change nothing\n"
|
||||
" --dir=PATH Uninstall from a custom install directory\n"
|
||||
" -y, --yes Do not prompt for confirmation\n"
|
||||
" -h, --help Show this help and exit\n\n"
|
||||
"Run with --dry-run first if you are unsure.\n");
|
||||
printf(
|
||||
"Usage: codebase-memory-mcp uninstall [options]\n\n"
|
||||
"Removes the codebase-memory-mcp binary and its agent configurations.\n"
|
||||
"THIS IS DESTRUCTIVE. Project indexes are kept by default. An interactive\n"
|
||||
"terminal is asked separately; the default answer is to keep them.\n\n"
|
||||
"Options:\n"
|
||||
" --dry-run Show what would be removed, change nothing\n"
|
||||
" --dir=PATH Uninstall from a custom install directory\n"
|
||||
" --delete-indexes Also delete every project index (overrides --no)\n"
|
||||
" -y, --yes Do not prompt; indexes are kept unless explicitly deleted\n"
|
||||
" -n, --no Decline prompts; --delete-indexes still takes precedence\n"
|
||||
" -h, --help Show this help and exit\n\n"
|
||||
"Run with --dry-run first if you are unsure.\n");
|
||||
return CLI_OK;
|
||||
}
|
||||
}
|
||||
parse_auto_answer(argc, argv);
|
||||
bool dry_run = false;
|
||||
bool delete_indexes_requested = false;
|
||||
/* An install into a custom --dir must be removable from that same dir:
|
||||
* without this, anyone who installed outside ~/.local/bin has no supported
|
||||
* uninstall path at all. Mirrors cbm_cmd_install's parsing. */
|
||||
@@ -12982,6 +13022,8 @@ int cbm_cmd_uninstall(int argc, char **argv) {
|
||||
}
|
||||
if (strcmp(argv[i], "--dry-run") == 0) {
|
||||
dry_run = true;
|
||||
} else if (strcmp(argv[i], "--delete-indexes") == 0) {
|
||||
delete_indexes_requested = true;
|
||||
} else if (strncmp(argv[i], "--dir=", SLEN("--dir=")) == 0) {
|
||||
requested_bin_dir = argv[i] + SLEN("--dir=");
|
||||
if (!requested_bin_dir[0]) {
|
||||
@@ -13013,23 +13055,9 @@ int cbm_cmd_uninstall(int argc, char **argv) {
|
||||
agent_uninstall_failures_reset();
|
||||
cbm_detected_agents_t agents = cbm_detect_agents(home);
|
||||
|
||||
/* Confirm index removal outside the startup lock, but defer the mutation
|
||||
* until the final guarded activation. Dry-run never removes indexes. */
|
||||
bool delete_indexes = false;
|
||||
int index_count = count_db_indexes(home);
|
||||
if (index_count > 0) {
|
||||
printf("\nFound %d index(es):\n", index_count);
|
||||
cbm_list_indexes(home);
|
||||
if (prompt_yn("Delete these indexes?")) {
|
||||
if (dry_run) {
|
||||
printf("(dry-run — indexes would be deleted)\n");
|
||||
} else {
|
||||
delete_indexes = true;
|
||||
}
|
||||
} else {
|
||||
printf("Indexes kept.\n");
|
||||
}
|
||||
}
|
||||
/* Decide outside the startup lock; mutate only in the final guarded
|
||||
* activation. Dry-run never authorizes actual index removal. */
|
||||
bool delete_indexes = uninstall_decide_index_deletion(home, delete_indexes_requested, dry_run);
|
||||
|
||||
char bin_path_storage[CLI_BUF_1K];
|
||||
const char *bin_path = bin_path_storage;
|
||||
|
||||
+1
-1
@@ -1107,7 +1107,7 @@ static void print_help(void) {
|
||||
"[--dir=<path>] [--skip-config]\n");
|
||||
printf(" [--clients=<tokens>] Run "
|
||||
"'install --clients' to list tokens\n");
|
||||
printf(" codebase-memory-mcp uninstall [-y|-n] [--dry-run]\n");
|
||||
printf(" codebase-memory-mcp uninstall [-y|-n] [--dry-run] [--delete-indexes]\n");
|
||||
printf(" codebase-memory-mcp update [-y|-n]\n");
|
||||
printf(" codebase-memory-mcp config <list|get|set|reset>\n");
|
||||
printf(" codebase-memory-mcp --version Print version\n");
|
||||
|
||||
+339
-8
@@ -39,6 +39,9 @@
|
||||
#include <stdio.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#endif
|
||||
#ifndef _WIN32
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
@@ -3011,8 +3014,8 @@ TEST(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index) {
|
||||
};
|
||||
cbm_cli_activation_ops_t ops = cli_activation_fake_ops(&fake);
|
||||
cbm_cli_set_activation_ops_for_test(&ops);
|
||||
char *argv[] = {"--yes"};
|
||||
int rc = cli_test_cmd_uninstall(1, argv);
|
||||
char *argv[] = {"--yes", "--delete-indexes"};
|
||||
int rc = cli_test_cmd_uninstall(2, argv);
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
cbm_set_auto_answer_for_test(0);
|
||||
|
||||
@@ -3072,8 +3075,8 @@ TEST(cli_uninstall_preserves_binary_and_index_when_cohort_does_not_drain) {
|
||||
};
|
||||
cbm_cli_activation_ops_t ops = cli_activation_fake_ops(&fake);
|
||||
cbm_cli_set_activation_ops_for_test(&ops);
|
||||
char *argv[] = {"--yes"};
|
||||
int rc = cli_test_cmd_uninstall(1, argv);
|
||||
char *argv[] = {"--yes", "--delete-indexes"};
|
||||
int rc = cli_test_cmd_uninstall(2, argv);
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
cbm_set_auto_answer_for_test(0);
|
||||
|
||||
@@ -3094,6 +3097,329 @@ TEST(cli_uninstall_preserves_binary_and_index_when_cohort_does_not_drain) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Uninstall requires a separate choice before deleting project data. Keep
|
||||
* fixture state and activation coordination independent of the developer's
|
||||
* installation, and validate setup before absence can count as deletion. */
|
||||
enum { UNINSTALL_INDEX_COUNT = 3, UNINSTALL_FILES_PER_INDEX = 6 };
|
||||
|
||||
typedef struct {
|
||||
char tmpdir[256];
|
||||
char cache_dir[512];
|
||||
char bin_target[640];
|
||||
char internal_store[640];
|
||||
char index_paths[UNINSTALL_INDEX_COUNT * UNINSTALL_FILES_PER_INDEX][640];
|
||||
char index_bodies[UNINSTALL_INDEX_COUNT * UNINSTALL_FILES_PER_INDEX][64];
|
||||
char *old_home;
|
||||
char *old_cache;
|
||||
char *old_path;
|
||||
bool created;
|
||||
} cli_uninstall_index_fixture_t;
|
||||
|
||||
static bool cli_uninstall_index_fixture_intact(const cli_uninstall_index_fixture_t *fx) {
|
||||
for (int i = 0; i < UNINSTALL_INDEX_COUNT * UNINSTALL_FILES_PER_INDEX; i++) {
|
||||
const char *body = read_test_file(fx->index_paths[i]);
|
||||
if (!body || strcmp(body, fx->index_bodies[i]) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool cli_uninstall_test_file_is(const char *path, const char *expected) {
|
||||
const char *body = read_test_file(path);
|
||||
return body && strcmp(body, expected) == 0;
|
||||
}
|
||||
|
||||
static bool cli_uninstall_test_absent(const char *path) {
|
||||
struct stat st;
|
||||
errno = 0;
|
||||
return stat(path, &st) != 0 && errno == ENOENT;
|
||||
}
|
||||
|
||||
static bool cli_uninstall_index_fixture_setup(cli_uninstall_index_fixture_t *fx, const char *tag) {
|
||||
memset(fx, 0, sizeof(*fx));
|
||||
snprintf(fx->tmpdir, sizeof(fx->tmpdir), "/tmp/cli-uninstall-%s-XXXXXX", tag);
|
||||
if (!cbm_mkdtemp(fx->tmpdir)) {
|
||||
return false;
|
||||
}
|
||||
fx->created = true;
|
||||
cli_activation_save_env(&fx->old_home, &fx->old_cache);
|
||||
fx->old_path = save_test_env("PATH");
|
||||
cbm_setenv("HOME", fx->tmpdir, 1);
|
||||
cbm_setenv("PATH", fx->tmpdir, 1);
|
||||
snprintf(fx->cache_dir, sizeof(fx->cache_dir), "%s/cache", fx->tmpdir);
|
||||
cbm_setenv("CBM_CACHE_DIR", fx->cache_dir, 1);
|
||||
const char *home = getenv("HOME");
|
||||
const char *path = getenv("PATH");
|
||||
const char *cache = getenv("CBM_CACHE_DIR");
|
||||
if (!home || !path || !cache || strcmp(home, fx->tmpdir) != 0 ||
|
||||
strcmp(path, fx->tmpdir) != 0 || strcmp(cache, fx->cache_dir) != 0) {
|
||||
return false;
|
||||
}
|
||||
test_mkdirp(fx->cache_dir);
|
||||
static const char *const suffixes[UNINSTALL_FILES_PER_INDEX] = {
|
||||
".db", ".db-wal", ".db-shm", ".db-journal", ".db.tmp", ".db.tmp-wal"};
|
||||
for (int i = 0; i < UNINSTALL_INDEX_COUNT; i++) {
|
||||
for (int j = 0; j < UNINSTALL_FILES_PER_INDEX; j++) {
|
||||
int k = i * UNINSTALL_FILES_PER_INDEX + j;
|
||||
snprintf(fx->index_paths[k], sizeof(fx->index_paths[k]), "%s/project-%d%s",
|
||||
fx->cache_dir, i, suffixes[j]);
|
||||
snprintf(fx->index_bodies[k], sizeof(fx->index_bodies[k]), "index %d file %d %s", i, j,
|
||||
tag);
|
||||
write_test_file(fx->index_paths[k], fx->index_bodies[k]);
|
||||
}
|
||||
}
|
||||
snprintf(fx->internal_store, sizeof(fx->internal_store), "%s/_config.db", fx->cache_dir);
|
||||
write_test_file(fx->internal_store, "internal store sentinel");
|
||||
char bin_dir[512];
|
||||
snprintf(bin_dir, sizeof(bin_dir), "%s/.local/bin", fx->tmpdir);
|
||||
test_mkdirp(bin_dir);
|
||||
#ifdef _WIN32
|
||||
snprintf(fx->bin_target, sizeof(fx->bin_target), "%s/codebase-memory-mcp.exe", bin_dir);
|
||||
#else
|
||||
snprintf(fx->bin_target, sizeof(fx->bin_target), "%s/codebase-memory-mcp", bin_dir);
|
||||
#endif
|
||||
write_test_file(fx->bin_target, "uninstall fixture binary");
|
||||
return cli_uninstall_index_fixture_intact(fx) &&
|
||||
cli_uninstall_test_file_is(fx->internal_store, "internal store sentinel") &&
|
||||
cli_uninstall_test_file_is(fx->bin_target, "uninstall fixture binary");
|
||||
}
|
||||
|
||||
static void cli_uninstall_index_fixture_teardown(cli_uninstall_index_fixture_t *fx) {
|
||||
if (fx->created) {
|
||||
cli_activation_restore_env(fx->old_home, fx->old_cache);
|
||||
restore_test_env("PATH", fx->old_path);
|
||||
test_rmdir_r(fx->tmpdir);
|
||||
}
|
||||
}
|
||||
|
||||
static int cli_uninstall_index_fixture_run(char **argv, int argc, int *reservations) {
|
||||
cli_activation_fake_t fake = {.mutation_reserve_result = 1};
|
||||
cbm_cli_activation_ops_t ops = cli_activation_fake_ops(&fake);
|
||||
cbm_set_auto_answer_for_test(0);
|
||||
cbm_cli_set_activation_ops_for_test(&ops);
|
||||
int rc = cli_test_cmd_uninstall(argc, argv);
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
cbm_set_auto_answer_for_test(0);
|
||||
if (reservations) {
|
||||
*reservations = fake.mutation_reserve_count;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* These local redirect helpers also exercise the Windows CRT descriptor path;
|
||||
* unlike the older POSIX capture fixture, neither test is compiled out there. */
|
||||
static int cli_uninstall_redirect_stream(FILE *stream, FILE *replacement) {
|
||||
if (stream != stdin && fflush(stream) != 0) {
|
||||
return -1;
|
||||
}
|
||||
#ifdef _WIN32
|
||||
int target = _fileno(stream);
|
||||
int saved = _dup(target);
|
||||
if (saved >= 0 && _dup2(_fileno(replacement), target) != 0) {
|
||||
_close(saved);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
int target = fileno(stream);
|
||||
int saved = dup(target);
|
||||
if (saved >= 0 && dup2(fileno(replacement), target) < 0) {
|
||||
close(saved);
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
clearerr(stream);
|
||||
return saved;
|
||||
}
|
||||
|
||||
static bool cli_uninstall_restore_stream(FILE *stream, int saved) {
|
||||
if (saved < 0) {
|
||||
return false;
|
||||
}
|
||||
bool flushed = stream == stdin || fflush(stream) == 0;
|
||||
#ifdef _WIN32
|
||||
bool restored = _dup2(saved, _fileno(stream)) == 0;
|
||||
_close(saved);
|
||||
#else
|
||||
bool restored = dup2(saved, fileno(stream)) >= 0;
|
||||
close(saved);
|
||||
#endif
|
||||
clearerr(stream);
|
||||
return flushed && restored;
|
||||
}
|
||||
|
||||
static bool cli_uninstall_capture_text(FILE *file, char *out, size_t capacity) {
|
||||
if (fseek(file, 0, SEEK_SET) != 0) {
|
||||
return false;
|
||||
}
|
||||
size_t n = fread(out, 1, capacity - 1, file);
|
||||
out[n] = '\0';
|
||||
return !ferror(file);
|
||||
}
|
||||
|
||||
TEST(cli_uninstall_auto_answers_keep_indexes_without_delete_indexes) {
|
||||
char *answers[] = {"--yes", "-y", "--no", "-n"};
|
||||
for (size_t i = 0; i < sizeof(answers) / sizeof(answers[0]); i++) {
|
||||
cli_uninstall_index_fixture_t fx;
|
||||
if (!cli_uninstall_index_fixture_setup(&fx, "keep")) {
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
FAIL("uninstall fixture setup failed");
|
||||
}
|
||||
char *argv[] = {answers[i]};
|
||||
int rc = cli_uninstall_index_fixture_run(argv, 1, NULL);
|
||||
bool kept = cli_uninstall_index_fixture_intact(&fx);
|
||||
bool binary_gone = cli_uninstall_test_absent(fx.bin_target);
|
||||
bool internal_kept =
|
||||
cli_uninstall_test_file_is(fx.internal_store, "internal store sentinel");
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_TRUE(kept);
|
||||
ASSERT_TRUE(binary_gone);
|
||||
ASSERT_TRUE(internal_kept);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_uninstall_delete_indexes_is_explicit_and_overrides_no) {
|
||||
char *argvs[][2] = {{"--delete-indexes", NULL},
|
||||
{"--yes", "--delete-indexes"},
|
||||
{"--no", "--delete-indexes"},
|
||||
{"--delete-indexes", "--no"}};
|
||||
for (size_t i = 0; i < sizeof(argvs) / sizeof(argvs[0]); i++) {
|
||||
cli_uninstall_index_fixture_t fx;
|
||||
if (!cli_uninstall_index_fixture_setup(&fx, "delete")) {
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
FAIL("uninstall fixture setup failed");
|
||||
}
|
||||
int reservations = 0;
|
||||
int rc = cli_uninstall_index_fixture_run(argvs[i], i == 0 ? 1 : 2, &reservations);
|
||||
bool absent = true;
|
||||
for (int k = 0; k < UNINSTALL_INDEX_COUNT * UNINSTALL_FILES_PER_INDEX; k++) {
|
||||
absent = cli_uninstall_test_absent(fx.index_paths[k]) && absent;
|
||||
}
|
||||
bool binary_gone = cli_uninstall_test_absent(fx.bin_target);
|
||||
bool internal_kept =
|
||||
cli_uninstall_test_file_is(fx.internal_store, "internal store sentinel");
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_EQ(reservations, 1);
|
||||
ASSERT_TRUE(absent);
|
||||
ASSERT_TRUE(binary_gone);
|
||||
ASSERT_TRUE(internal_kept);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_uninstall_delete_indexes_dry_run_keeps_files) {
|
||||
cli_uninstall_index_fixture_t fx;
|
||||
if (!cli_uninstall_index_fixture_setup(&fx, "dry")) {
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
FAIL("uninstall fixture setup failed");
|
||||
}
|
||||
FILE *output = tmpfile();
|
||||
int saved = output ? cli_uninstall_redirect_stream(stdout, output) : -1;
|
||||
int rc = -1;
|
||||
int reservations = -1;
|
||||
if (saved >= 0) {
|
||||
char *argv[] = {"--dry-run", "--delete-indexes", "--no"};
|
||||
rc = cli_uninstall_index_fixture_run(argv, 3, &reservations);
|
||||
}
|
||||
bool restored = saved >= 0 && cli_uninstall_restore_stream(stdout, saved);
|
||||
char text[8192] = "";
|
||||
bool captured = output && cli_uninstall_capture_text(output, text, sizeof(text));
|
||||
if (output) {
|
||||
fclose(output);
|
||||
}
|
||||
bool kept = cli_uninstall_index_fixture_intact(&fx) &&
|
||||
cli_uninstall_test_file_is(fx.bin_target, "uninstall fixture binary") &&
|
||||
cli_uninstall_test_file_is(fx.internal_store, "internal store sentinel");
|
||||
bool named = strstr(text, "indexes would be deleted") != NULL;
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
ASSERT_TRUE(restored && captured);
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_EQ(reservations, 0);
|
||||
ASSERT_TRUE(kept);
|
||||
ASSERT_TRUE(named);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_uninstall_non_tty_affirmative_input_keeps_indexes) {
|
||||
cli_uninstall_index_fixture_t fx;
|
||||
if (!cli_uninstall_index_fixture_setup(&fx, "notty")) {
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
FAIL("uninstall fixture setup failed");
|
||||
}
|
||||
FILE *input = tmpfile();
|
||||
FILE *output = tmpfile();
|
||||
FILE *errors = tmpfile();
|
||||
bool input_ready =
|
||||
input && fputs("y\n", input) >= 0 && fflush(input) == 0 && fseek(input, 0, SEEK_SET) == 0;
|
||||
int saved_in = input_ready ? cli_uninstall_redirect_stream(stdin, input) : -1;
|
||||
int saved_out = output ? cli_uninstall_redirect_stream(stdout, output) : -1;
|
||||
int saved_err = errors ? cli_uninstall_redirect_stream(stderr, errors) : -1;
|
||||
int rc = -1;
|
||||
if (saved_in >= 0 && saved_out >= 0 && saved_err >= 0 && fseek(stdin, 0, SEEK_SET) == 0) {
|
||||
char *argv[] = {"uninstall"};
|
||||
rc = cli_uninstall_index_fixture_run(argv, 1, NULL);
|
||||
}
|
||||
bool restored_err = saved_err >= 0 && cli_uninstall_restore_stream(stderr, saved_err);
|
||||
bool restored_out = saved_out >= 0 && cli_uninstall_restore_stream(stdout, saved_out);
|
||||
bool restored_in = saved_in >= 0 && cli_uninstall_restore_stream(stdin, saved_in);
|
||||
char out_text[8192] = "";
|
||||
char err_text[8192] = "";
|
||||
bool captured = output && errors &&
|
||||
cli_uninstall_capture_text(output, out_text, sizeof(out_text)) &&
|
||||
cli_uninstall_capture_text(errors, err_text, sizeof(err_text));
|
||||
bool unread = input && fgetc(input) == 'y';
|
||||
if (input) {
|
||||
fclose(input);
|
||||
}
|
||||
if (output) {
|
||||
fclose(output);
|
||||
}
|
||||
if (errors) {
|
||||
fclose(errors);
|
||||
}
|
||||
bool kept = cli_uninstall_index_fixture_intact(&fx) &&
|
||||
cli_uninstall_test_file_is(fx.internal_store, "internal store sentinel");
|
||||
bool binary_gone = cli_uninstall_test_absent(fx.bin_target);
|
||||
bool explained = strstr(out_text, "--delete-indexes") && strstr(out_text, fx.cache_dir) &&
|
||||
!strstr(out_text, "Delete these indexes?") &&
|
||||
!strstr(err_text, "requires a terminal");
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
ASSERT_TRUE(restored_in && restored_out && restored_err && captured);
|
||||
ASSERT_EQ(rc, 0);
|
||||
ASSERT_TRUE(unread);
|
||||
ASSERT_TRUE(kept);
|
||||
ASSERT_TRUE(binary_gone);
|
||||
ASSERT_TRUE(explained);
|
||||
PASS();
|
||||
}
|
||||
|
||||
TEST(cli_uninstall_delete_indexes_help_and_invalid_options_preserve_files) {
|
||||
char *argvs[][3] = {{"--yes", "--delete-indexes", "--help"},
|
||||
{"--delete-indexes", "--unknown", NULL},
|
||||
{"--delete-indexes", "--dir=", NULL},
|
||||
{"--delete-indexes", "--dir", NULL}};
|
||||
for (size_t i = 0; i < sizeof(argvs) / sizeof(argvs[0]); i++) {
|
||||
cli_uninstall_index_fixture_t fx;
|
||||
if (!cli_uninstall_index_fixture_setup(&fx, "early")) {
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
FAIL("uninstall fixture setup failed");
|
||||
}
|
||||
int reservations = -1;
|
||||
int rc = cli_uninstall_index_fixture_run(argvs[i], i == 0 ? 3 : 2, &reservations);
|
||||
bool kept = cli_uninstall_index_fixture_intact(&fx) &&
|
||||
cli_uninstall_test_file_is(fx.bin_target, "uninstall fixture binary") &&
|
||||
cli_uninstall_test_file_is(fx.internal_store, "internal store sentinel");
|
||||
cli_uninstall_index_fixture_teardown(&fx);
|
||||
ASSERT_EQ(rc, i == 0 ? 0 : 1);
|
||||
ASSERT_EQ(reservations, 0);
|
||||
ASSERT_TRUE(kept);
|
||||
}
|
||||
PASS();
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
/* #1954: an agent config that cannot be cleaned must not hold the executable
|
||||
* and the indexes hostage. The fixture's ~/.cursor/mcp.json is a dangling
|
||||
@@ -3148,8 +3474,8 @@ TEST(cli_uninstall_removes_binary_and_index_when_agent_config_cleanup_fails) {
|
||||
cli_fd_capture_t err_capture;
|
||||
cli_fd_capture_begin(&out_capture, stdout, STDOUT_FILENO);
|
||||
cli_fd_capture_begin(&err_capture, stderr, STDERR_FILENO);
|
||||
char *argv[] = {"--yes"};
|
||||
int rc = cli_test_cmd_uninstall(1, argv);
|
||||
char *argv[] = {"--yes", "--delete-indexes"};
|
||||
int rc = cli_test_cmd_uninstall(2, argv);
|
||||
char *err_text = cli_fd_capture_end(&err_capture);
|
||||
char *out_text = cli_fd_capture_end(&out_capture);
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
@@ -3231,8 +3557,8 @@ TEST(cli_uninstall_cleans_user_owned_symlinked_config) {
|
||||
cli_fd_capture_t err_capture;
|
||||
cli_fd_capture_begin(&out_capture, stdout, STDOUT_FILENO);
|
||||
cli_fd_capture_begin(&err_capture, stderr, STDERR_FILENO);
|
||||
char *argv[] = {"--yes"};
|
||||
int rc = cli_test_cmd_uninstall(1, argv);
|
||||
char *argv[] = {"--yes", "--delete-indexes"};
|
||||
int rc = cli_test_cmd_uninstall(2, argv);
|
||||
char *err_text = cli_fd_capture_end(&err_capture);
|
||||
char *out_text = cli_fd_capture_end(&out_capture);
|
||||
cbm_cli_set_activation_ops_for_test(NULL);
|
||||
@@ -17386,6 +17712,11 @@ SUITE(cli) {
|
||||
RUN_TEST(cli_update_agent_configs_finish_before_guard_release);
|
||||
RUN_TEST(cli_uninstall_quiesces_active_cohort_before_removing_binary_and_index);
|
||||
RUN_TEST(cli_uninstall_preserves_binary_and_index_when_cohort_does_not_drain);
|
||||
RUN_TEST(cli_uninstall_auto_answers_keep_indexes_without_delete_indexes);
|
||||
RUN_TEST(cli_uninstall_delete_indexes_is_explicit_and_overrides_no);
|
||||
RUN_TEST(cli_uninstall_delete_indexes_dry_run_keeps_files);
|
||||
RUN_TEST(cli_uninstall_non_tty_affirmative_input_keeps_indexes);
|
||||
RUN_TEST(cli_uninstall_delete_indexes_help_and_invalid_options_preserve_files);
|
||||
#ifndef _WIN32
|
||||
RUN_TEST(cli_uninstall_removes_binary_and_index_when_agent_config_cleanup_fails);
|
||||
RUN_TEST(cli_uninstall_cleans_user_owned_symlinked_config);
|
||||
|
||||
Reference in new issue
Block a user