Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ stages:

variables:
PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
CODELLENS_STRICT_COMMANDS: "1"

cache:
paths:
Expand Down
8 changes: 8 additions & 0 deletions scripts/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ def get_all_commands():
import importlib
import logging

_STRICT_COMMAND_IMPORTS = os.environ.get("CODELLENS_STRICT_COMMANDS", "").lower() in {
"1",
"true",
"yes",
}

_commands_dir = os.path.dirname(__file__)
for fname in sorted(os.listdir(_commands_dir)):
if fname.endswith('.py') and fname != '__init__.py':
try:
importlib.import_module(f'.{fname[:-3]}', package='commands')
except Exception as e:
if _STRICT_COMMAND_IMPORTS:
raise
logging.getLogger('codelens').error(
f"Failed to import command module '{fname}': {e}"
)
15 changes: 8 additions & 7 deletions scripts/commands/self_analyze.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,11 @@ def _compute_overall_health(analyses: Dict[str, Any]) -> Dict[str, Any]:
}


# ─── Command Registration ─────────────────────────────────────

COMMAND_INFO = {
"help": "Run CodeLens on its own codebase (dogfooding / meta-analysis)",
"add_args": add_args,
"execute": execute,
}
from commands import register_command

register_command(
"self-analyze",
"Run CodeLens on its own codebase (dogfooding / meta-analysis)",
add_args,
execute,
)
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""Shared pytest configuration for CodeLens."""

import os

# Fail fast when command modules fail to import during test runs.
os.environ.setdefault("CODELLENS_STRICT_COMMANDS", "1")
65 changes: 65 additions & 0 deletions tests/test_command_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""Tests for command module registration and strict import behavior."""

import os
import subprocess
import sys
from pathlib import Path

import pytest

SCRIPT_DIR = Path(__file__).resolve().parents[1] / "scripts"
COMMANDS_DIR = SCRIPT_DIR / "commands"

if str(SCRIPT_DIR) not in sys.path:
sys.path.insert(0, str(SCRIPT_DIR))

from commands import COMMAND_REGISTRY


def test_every_command_module_registers():
"""Each commands/*.py module must register at least one CLI command."""
missing = []
for module_path in sorted(COMMANDS_DIR.glob("*.py")):
if module_path.name == "__init__.py":
continue

module_name = f"commands.{module_path.stem}"
registered = [
name
for name, info in COMMAND_REGISTRY.items()
if getattr(info["execute"], "__module__", None) == module_name
]
if not registered:
missing.append(module_path.name)

assert not missing, (
"Command modules without register_command(): "
+ ", ".join(missing)
)


def test_strict_command_imports_fail_fast_on_broken_module():
"""CODELLENS_STRICT_COMMANDS=1 should surface broken command imports."""
broken_module = COMMANDS_DIR / "_test_broken_import.py"
broken_module.write_text("def broken(\n", encoding="utf-8")
env = os.environ.copy()
env["CODELLENS_STRICT_COMMANDS"] = "1"
env["PYTHONPATH"] = str(SCRIPT_DIR)

try:
result = subprocess.run(
[
sys.executable,
"-c",
"import importlib; importlib.import_module('commands')",
],
cwd=SCRIPT_DIR,
env=env,
capture_output=True,
text=True,
)
finally:
broken_module.unlink(missing_ok=True)

assert result.returncode != 0
assert "SyntaxError" in result.stderr or "_test_broken_import" in result.stderr