codecortex 0.24.0__tar.gz → 0.24.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {codecortex-0.24.0/src/codecortex.egg-info → codecortex-0.24.1}/PKG-INFO +1 -1
- {codecortex-0.24.0 → codecortex-0.24.1/src/codecortex.egg-info}/PKG-INFO +1 -1
- codecortex-0.24.1/src/codeintel/__init__.py +1 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/index.py +36 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/filelock.py +41 -1
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_bench_corpus_typed.py +20 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cli_commands.py +90 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_reindex_lock.py +59 -1
- codecortex-0.24.0/src/codeintel/__init__.py +0 -1
- {codecortex-0.24.0 → codecortex-0.24.1}/LICENSE +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/README.md +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/pyproject.toml +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/setup.cfg +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codecortex.egg-info/SOURCES.txt +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codecortex.egg-info/dependency_links.txt +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codecortex.egg-info/entry_points.txt +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codecortex.egg-info/requires.txt +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codecortex.egg-info/top_level.txt +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/__main__.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/agent_prompt.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/auth.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/c4.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/c4_check.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/c4_layers.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/cache.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/__init__.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/_common.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/c4.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/doctor.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/gen_token.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/graph.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/install.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/map.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/prompt.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/query.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/reset.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/serve.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/serve_http.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/setup.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/status.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/commands/uninstall.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/config.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/containment.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/doctor.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/gateway.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_answer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_backend.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_confidence.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_edges.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_ops.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_render.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_resolution.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/graph_targets.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/grapher.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/http_server.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/indexer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/injector.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/installer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/lang_config.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/loc.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/logconfig.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/mapper.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/metrics.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/onboarding.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/outcome.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/paths.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/per_thread.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/policy.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/progress.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/provider.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/providers/__init__.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/providers/graph.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/providers/lsp.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/providers/none.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/providers/semantic.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/py.typed +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/query_ops.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/redact.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/reindexer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/reset.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/searcher.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/semantic_db.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/server.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/source_kind.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/term.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/verify.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/viewer/__init__.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/viewer/graph_template.html +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/src/codeintel/wire_text.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_agent_prompt.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_backend_reaper.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_background_index_failure.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_bench_gate.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_bench_oracle.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_bench_oracle_ts.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_c4.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_c4_check.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_c4_layers.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_c4_model.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_c4_output.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cache.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_chunking.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cli_help.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cli_install_dry_run.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cli_install_offer_injection.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_cold_process.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_config.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_corpus.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_could_not_ask_semantic.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_docs_ci_claims.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_docs_deadcode_withdrawal.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_docs_trust_claims.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_doctor.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_e2e.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_edge_confidence.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_engine_adoption.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_enterprise.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_gateway.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_graph_failure_population.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_graph_provider.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_graph_real.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_graph_stdin.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_grapher.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_hard_exit.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_hardening.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_http_auth.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_http_server.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_incompleteness.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_index_failure_reason.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_index_incompleteness.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_index_progress.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_indexer_binary.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_installer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_integration.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_lang_config.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_loc_census.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_lsp_language_coverage.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_lsp_provider.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_lsp_real.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_mapper.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_mcp_handshake.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_mcp_server.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_missing_target.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_model_dimension.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_never_raise.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_null_reason_and_ripple.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_onboarding.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_per_thread.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_query_budget.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_rbac.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_redaction_boundary.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_reindexer.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_rerank.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_reset.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_semantic_provider.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_source_kind.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_staleness.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_summary_integrity.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_symbol_context.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_term.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_treesitter.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_uninstall.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_upgrade_path.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_verify.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_verify_call.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_version_skew.py +0 -0
- {codecortex-0.24.0 → codecortex-0.24.1}/tests/test_wire_text.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.1
|
|
4
4
|
Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
|
|
5
5
|
Author: Shammai Hamilton
|
|
6
6
|
License-Expression: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codecortex
|
|
3
|
-
Version: 0.24.
|
|
3
|
+
Version: 0.24.1
|
|
4
4
|
Summary: Local-first, MCP-native code-intelligence server — graph, LSP, and semantic search behind one safe code.query tool for coding agents.
|
|
5
5
|
Author: Shammai Hamilton
|
|
6
6
|
License-Expression: MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.24.1"
|
|
@@ -6,6 +6,11 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from codeintel.commands._common import resolve_root
|
|
8
8
|
|
|
9
|
+
# How long a foreground `codeintel index` waits for another process's pass over the same repository
|
|
10
|
+
# before indexing anyway. Long enough to outlast a cold index of a large repo (measured at ~10 min
|
|
11
|
+
# for 30k chunks, docs/benchmarks.md); short enough that a hung holder cannot block it forever.
|
|
12
|
+
_LOCK_WAIT_S = 900.0
|
|
13
|
+
|
|
9
14
|
|
|
10
15
|
class _ProgressLogBridge(logging.Handler):
|
|
11
16
|
"""Routes WARNING+ log records from the indexer to a live progress counter's ``notice()``, so a
|
|
@@ -52,6 +57,35 @@ def run(args: Any) -> int:
|
|
|
52
57
|
quiet = getattr(args, "quiet", False)
|
|
53
58
|
counter = None if quiet else term.LiveCounter(console)
|
|
54
59
|
|
|
60
|
+
# Take the same per-repository lock the background reindexer takes, so a terminal `index` and
|
|
61
|
+
# a running server's background pass do not embed the whole repository twice. It covers the
|
|
62
|
+
# semantic AND graph passes, as the background pass does; the map refresh is not indexing.
|
|
63
|
+
import contextlib
|
|
64
|
+
|
|
65
|
+
from codeintel.filelock import exclusive_waiting, lock_path
|
|
66
|
+
|
|
67
|
+
def _say_waiting() -> None:
|
|
68
|
+
if not quiet: # --quiet promises the result line only, contention or not
|
|
69
|
+
print("waiting for another codeintel process to finish indexing this repository…",
|
|
70
|
+
flush=True)
|
|
71
|
+
|
|
72
|
+
lock = contextlib.ExitStack()
|
|
73
|
+
# Resolving the lock path reads the codeintel home, which raises where there is no resolvable
|
|
74
|
+
# home directory (a container UID with no passwd entry and no CODEINTEL_HOME). That must not
|
|
75
|
+
# turn into a traceback before the semantic pass's own error handling below — which reports
|
|
76
|
+
# exactly that failure as "index failed: …". A lock we cannot place is a lock we do without,
|
|
77
|
+
# by the rule in `filelock`: it may dedupe work, never stop it.
|
|
78
|
+
try:
|
|
79
|
+
path = lock_path(os.path.realpath(project_root))
|
|
80
|
+
except Exception:
|
|
81
|
+
path = None
|
|
82
|
+
if path is not None:
|
|
83
|
+
got_lock = lock.enter_context(
|
|
84
|
+
exclusive_waiting(path, _LOCK_WAIT_S, on_wait=_say_waiting))
|
|
85
|
+
if not got_lock and not quiet:
|
|
86
|
+
print(f"still locked after {int(_LOCK_WAIT_S)}s — indexing anyway; the other pass "
|
|
87
|
+
"may repeat some of this work", flush=True)
|
|
88
|
+
|
|
55
89
|
failed = False
|
|
56
90
|
# Wrap the whole semantic pass so a setup failure (e.g. an unresolvable home dir →
|
|
57
91
|
# Path.home() raising) degrades with a message, like every other subcommand, not a traceback.
|
|
@@ -146,6 +180,8 @@ def run(args: Any) -> int:
|
|
|
146
180
|
if beat:
|
|
147
181
|
beat.stop("warn", "skipped")
|
|
148
182
|
|
|
183
|
+
lock.close()
|
|
184
|
+
|
|
149
185
|
# best-effort map refresh after index (fast, kept silent — not worth a checklist row)
|
|
150
186
|
try:
|
|
151
187
|
from codeintel.mapper import MapGenerator
|
|
@@ -33,7 +33,8 @@ import logging
|
|
|
33
33
|
import os
|
|
34
34
|
import pathlib
|
|
35
35
|
import re
|
|
36
|
-
|
|
36
|
+
import time
|
|
37
|
+
from collections.abc import Callable, Iterator
|
|
37
38
|
|
|
38
39
|
logger = logging.getLogger(__name__)
|
|
39
40
|
|
|
@@ -99,6 +100,45 @@ def exclusive(path: pathlib.Path) -> Iterator[bool]:
|
|
|
99
100
|
os.close(fd) # closing would release it anyway; explicit is clearer
|
|
100
101
|
|
|
101
102
|
|
|
103
|
+
@contextlib.contextmanager
|
|
104
|
+
def exclusive_waiting(
|
|
105
|
+
path: pathlib.Path,
|
|
106
|
+
timeout_s: float,
|
|
107
|
+
*,
|
|
108
|
+
poll_s: float = 0.25,
|
|
109
|
+
on_wait: Callable[[], None] | None = None,
|
|
110
|
+
) -> Iterator[bool]:
|
|
111
|
+
"""Like `exclusive`, but WAIT up to *timeout_s* for another holder to finish.
|
|
112
|
+
|
|
113
|
+
For work a person asked for. A background pass that loses the race skips, because nobody asked
|
|
114
|
+
for it and the holder is already doing it. A foreground `codeintel index` cannot skip: the user
|
|
115
|
+
asked for an index, and "somebody else is probably doing it" is not one. So it waits, and once
|
|
116
|
+
the holder finishes, its own pass is incremental and mostly finds nothing new.
|
|
117
|
+
|
|
118
|
+
Yields True once the lock is held, and False when *timeout_s* passed with it still held — in
|
|
119
|
+
which case the caller proceeds WITHOUT the lock, by the module's rule that a lock may dedupe work
|
|
120
|
+
but never stop it. A holder that is alive but hung must not block indexing forever.
|
|
121
|
+
|
|
122
|
+
*on_wait* runs once, the first time the lock is found held, so a caller can say why it has
|
|
123
|
+
stopped moving. Without `fcntl` there is nothing to wait on and this yields True at once.
|
|
124
|
+
"""
|
|
125
|
+
deadline = time.monotonic() + max(0.0, timeout_s)
|
|
126
|
+
announced = False
|
|
127
|
+
while True:
|
|
128
|
+
with exclusive(path) as got:
|
|
129
|
+
if got:
|
|
130
|
+
yield True
|
|
131
|
+
return
|
|
132
|
+
if not announced and on_wait is not None:
|
|
133
|
+
announced = True
|
|
134
|
+
with contextlib.suppress(Exception):
|
|
135
|
+
on_wait()
|
|
136
|
+
if time.monotonic() >= deadline:
|
|
137
|
+
yield False
|
|
138
|
+
return
|
|
139
|
+
time.sleep(poll_s)
|
|
140
|
+
|
|
141
|
+
|
|
102
142
|
def held_by_another_process(path: pathlib.Path) -> bool:
|
|
103
143
|
"""True when some other live process holds this lock.
|
|
104
144
|
|
|
@@ -144,3 +144,23 @@ def test_the_typed_arm_scores_only_the_class_qualified_targets():
|
|
|
144
144
|
f"{target} does not look class-qualified; this arm scores only that shape"
|
|
145
145
|
)
|
|
146
146
|
assert os.path.exists(os.path.join(_TYPED, path)), path
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_both_corpora_give_serena_the_same_project_config():
|
|
150
|
+
"""The serena config is part of what the language server sees, so it is part of the control.
|
|
151
|
+
|
|
152
|
+
It also stops the churn this repository kept seeing. A newer serena upgrades every REGISTERED
|
|
153
|
+
project's `project.yml` to its current template when it starts — and codeintel launching serena
|
|
154
|
+
with `--project <fixture>` registers the fixtures. `corpus_ts_typed` was generated by that
|
|
155
|
+
newer serena and never drifted; `corpus_ts` held an older template and was rewritten at the
|
|
156
|
+
start of every session. Keeping the two identical (apart from the name) keeps them both on the
|
|
157
|
+
template the other one already proved stable, and makes a future re-template show up here as
|
|
158
|
+
one file differing from its twin rather than as an unexplained diff."""
|
|
159
|
+
def config(root: str, name: str) -> str:
|
|
160
|
+
with open(os.path.join(root, ".serena", "project.yml"), encoding="utf-8") as fh:
|
|
161
|
+
text = fh.read()
|
|
162
|
+
assert f'project_name: "{name}"' in text, f"{root}: project_name is not {name!r}"
|
|
163
|
+
return text.replace(f'project_name: "{name}"', 'project_name: "<name>"')
|
|
164
|
+
|
|
165
|
+
assert config(_TYPED, "corpus_ts_typed") == config(_UNTYPED, "corpus_ts"), (
|
|
166
|
+
"the two fixtures' .serena/project.yml differ beyond project_name — copy one over the other")
|
|
@@ -12,6 +12,7 @@ commands, or a command dispatches to nothing.
|
|
|
12
12
|
from __future__ import annotations
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
|
+
import os
|
|
15
16
|
import string
|
|
16
17
|
import subprocess
|
|
17
18
|
import sys
|
|
@@ -930,3 +931,92 @@ def test_query_json_stays_parseable_when_the_query_itself_fails(monkeypatch, cap
|
|
|
930
931
|
payload = _json.loads(capsys.readouterr().out) # must not raise
|
|
931
932
|
assert payload["result"] is None
|
|
932
933
|
assert "backend exploded" in payload["reason"]
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
# --------------------------------------------------------------------------- index lock
|
|
937
|
+
|
|
938
|
+
def test_index_holds_the_reindex_lock_for_its_whole_pass(monkeypatch, tmp_path):
|
|
939
|
+
"""A server's background reindex skips while this is held, so the repository is embedded once.
|
|
940
|
+
Checked from inside the pass, because holding it "somewhere around" the pass is not enough."""
|
|
941
|
+
from codeintel.filelock import _HAVE_FLOCK, held_by_another_process, lock_path
|
|
942
|
+
if not _HAVE_FLOCK:
|
|
943
|
+
pytest.skip("advisory locking needs POSIX fcntl")
|
|
944
|
+
seen = _stub_index(monkeypatch, tmp_path, count=1, has_graph_backend=True)
|
|
945
|
+
path = lock_path(os.path.realpath(str(tmp_path)))
|
|
946
|
+
held: dict[str, bool] = {}
|
|
947
|
+
|
|
948
|
+
import codeintel.indexer as indexer_mod
|
|
949
|
+
base = indexer_mod.Indexer
|
|
950
|
+
|
|
951
|
+
class _Checking(base): # type: ignore[misc, valid-type]
|
|
952
|
+
def index(self, root):
|
|
953
|
+
held["semantic"] = held_by_another_process(path)
|
|
954
|
+
return super().index(root)
|
|
955
|
+
|
|
956
|
+
monkeypatch.setattr("codeintel.indexer.Indexer", _Checking)
|
|
957
|
+
import codeintel.reindexer as reindexer_mod
|
|
958
|
+
graph_base = reindexer_mod.Reindexer
|
|
959
|
+
|
|
960
|
+
class _CheckingGraph(graph_base): # type: ignore[misc, valid-type]
|
|
961
|
+
def _graph_reindex(self, root):
|
|
962
|
+
held["graph"] = held_by_another_process(path)
|
|
963
|
+
super()._graph_reindex(root)
|
|
964
|
+
|
|
965
|
+
monkeypatch.setattr("codeintel.reindexer.Reindexer", _CheckingGraph)
|
|
966
|
+
|
|
967
|
+
assert import_module("codeintel.commands.index").run(_args(project_root=str(tmp_path))) == 0
|
|
968
|
+
assert held == {"semantic": True, "graph": True}, held
|
|
969
|
+
assert seen["reindexed"]
|
|
970
|
+
assert not held_by_another_process(path), "the lock outlived the command"
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
def test_index_waits_for_another_pass_then_indexes_anyway_on_timeout(monkeypatch, tmp_path, capsys):
|
|
974
|
+
"""Held by someone else: say so, wait, and — past the limit — index anyway. Never skip: the
|
|
975
|
+
user asked for an index, and a hung holder must not be able to stop one."""
|
|
976
|
+
from codeintel.filelock import _HAVE_FLOCK, exclusive, lock_path
|
|
977
|
+
if not _HAVE_FLOCK:
|
|
978
|
+
pytest.skip("advisory locking needs POSIX fcntl")
|
|
979
|
+
_stub_index(monkeypatch, tmp_path, count=7)
|
|
980
|
+
mod = import_module("codeintel.commands.index")
|
|
981
|
+
monkeypatch.setattr(mod, "_LOCK_WAIT_S", 0.3)
|
|
982
|
+
|
|
983
|
+
with exclusive(lock_path(os.path.realpath(str(tmp_path)))) as other:
|
|
984
|
+
assert other
|
|
985
|
+
assert mod.run(_args(project_root=str(tmp_path))) == 0
|
|
986
|
+
|
|
987
|
+
out = capsys.readouterr().out
|
|
988
|
+
assert "waiting for another codeintel process" in out
|
|
989
|
+
assert "indexing anyway" in out
|
|
990
|
+
assert "Indexed 7 chunks" in out
|
|
991
|
+
|
|
992
|
+
|
|
993
|
+
def test_index_quiet_stays_quiet_while_waiting_for_the_lock(monkeypatch, tmp_path, capsys):
|
|
994
|
+
"""`--quiet` promises the result line only. Contention is not an exception to that."""
|
|
995
|
+
from codeintel.filelock import _HAVE_FLOCK, exclusive, lock_path
|
|
996
|
+
if not _HAVE_FLOCK:
|
|
997
|
+
pytest.skip("advisory locking needs POSIX fcntl")
|
|
998
|
+
_stub_index(monkeypatch, tmp_path, count=2)
|
|
999
|
+
mod = import_module("codeintel.commands.index")
|
|
1000
|
+
monkeypatch.setattr(mod, "_LOCK_WAIT_S", 0.2)
|
|
1001
|
+
|
|
1002
|
+
with exclusive(lock_path(os.path.realpath(str(tmp_path)))) as other:
|
|
1003
|
+
assert other
|
|
1004
|
+
assert mod.run(_args(project_root=str(tmp_path), quiet=True)) == 0
|
|
1005
|
+
|
|
1006
|
+
assert capsys.readouterr().out.strip() == "Indexed 2 chunks"
|
|
1007
|
+
|
|
1008
|
+
|
|
1009
|
+
def test_index_without_a_resolvable_home_fails_with_a_message_not_a_traceback(
|
|
1010
|
+
monkeypatch, tmp_path, capsys):
|
|
1011
|
+
"""A container UID with no home: the lock is skipped, and the failure the semantic pass hits
|
|
1012
|
+
is reported the way it always was — `index failed: …` and exit 1."""
|
|
1013
|
+
_stub_index(monkeypatch, tmp_path)
|
|
1014
|
+
|
|
1015
|
+
def _no_home(*a, **k):
|
|
1016
|
+
raise RuntimeError("could not determine home directory")
|
|
1017
|
+
|
|
1018
|
+
monkeypatch.setattr("codeintel.filelock.lock_path", _no_home)
|
|
1019
|
+
monkeypatch.setattr("codeintel.config.load_config", _no_home)
|
|
1020
|
+
|
|
1021
|
+
assert import_module("codeintel.commands.index").run(_args(project_root=str(tmp_path))) == 1
|
|
1022
|
+
assert "index failed: could not determine home directory" in capsys.readouterr().out
|
|
@@ -25,10 +25,18 @@ import pathlib
|
|
|
25
25
|
import subprocess
|
|
26
26
|
import sys
|
|
27
27
|
import textwrap
|
|
28
|
+
import threading
|
|
29
|
+
import time
|
|
28
30
|
|
|
29
31
|
import pytest
|
|
30
32
|
|
|
31
|
-
from codeintel.filelock import
|
|
33
|
+
from codeintel.filelock import (
|
|
34
|
+
_HAVE_FLOCK,
|
|
35
|
+
exclusive,
|
|
36
|
+
exclusive_waiting,
|
|
37
|
+
held_by_another_process,
|
|
38
|
+
lock_path,
|
|
39
|
+
)
|
|
32
40
|
from codeintel.reindexer import Reindexer
|
|
33
41
|
|
|
34
42
|
pytestmark = pytest.mark.skipif(not _HAVE_FLOCK, reason="advisory locking needs POSIX fcntl")
|
|
@@ -207,3 +215,53 @@ def test_a_probe_that_fails_does_not_claim_a_reindex_is_running(home, monkeypatc
|
|
|
207
215
|
monkeypatch.setattr(fl, "exclusive", lambda _p: (_ for _ in ()).throw(OSError("nope")))
|
|
208
216
|
|
|
209
217
|
assert fl.held_by_another_process(path) is False
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
# ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
221
|
+
# Waiting — for work a person asked for
|
|
222
|
+
# ══════════════════════════════════════════════════════════════════════════════════════════════
|
|
223
|
+
|
|
224
|
+
def test_a_free_lock_is_taken_at_once_without_announcing_a_wait(home):
|
|
225
|
+
waits: list[int] = []
|
|
226
|
+
with exclusive_waiting(lock_path("/repo/one"), 5, on_wait=lambda: waits.append(1)) as got:
|
|
227
|
+
assert got is True
|
|
228
|
+
assert waits == []
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def test_a_held_lock_is_waited_for_and_then_taken(home):
|
|
232
|
+
"""The foreground case: another pass is running, so wait for it rather than skip or duplicate."""
|
|
233
|
+
path = lock_path("/repo/one")
|
|
234
|
+
waits: list[int] = []
|
|
235
|
+
holding, release = threading.Event(), threading.Event()
|
|
236
|
+
|
|
237
|
+
def holder() -> None:
|
|
238
|
+
with exclusive(path) as got:
|
|
239
|
+
assert got
|
|
240
|
+
holding.set()
|
|
241
|
+
release.wait(5)
|
|
242
|
+
|
|
243
|
+
t = threading.Thread(target=holder)
|
|
244
|
+
t.start()
|
|
245
|
+
holding.wait(5)
|
|
246
|
+
threading.Timer(0.4, release.set).start()
|
|
247
|
+
|
|
248
|
+
started = time.monotonic()
|
|
249
|
+
with exclusive_waiting(path, 10, poll_s=0.05, on_wait=lambda: waits.append(1)) as got:
|
|
250
|
+
waited = time.monotonic() - started
|
|
251
|
+
assert got is True
|
|
252
|
+
assert held_by_another_process(path), "yielded True without actually holding the lock"
|
|
253
|
+
t.join(5)
|
|
254
|
+
|
|
255
|
+
assert waits == [1], "the wait must be announced exactly once"
|
|
256
|
+
assert waited >= 0.3, f"took the lock after {waited:.2f}s while it was still held"
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_a_lock_held_past_the_timeout_yields_false_so_the_work_still_happens(home):
|
|
260
|
+
"""A hung holder must not block indexing forever: time out, say so, proceed."""
|
|
261
|
+
path = lock_path("/repo/one")
|
|
262
|
+
waits: list[int] = []
|
|
263
|
+
with exclusive(path) as first:
|
|
264
|
+
assert first
|
|
265
|
+
with exclusive_waiting(path, 0.2, poll_s=0.05, on_wait=lambda: waits.append(1)) as got:
|
|
266
|
+
assert got is False
|
|
267
|
+
assert waits == [1]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.24.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|