Skip to content
Merged
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
50 changes: 19 additions & 31 deletions .github/workflows/pr-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,15 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Run linting
run: uv run ruff check .

- name: Run formatting check
run: uv run black --check .
run: uv run ruff format --check .

- name: Run type checking
run: uv run mypy src
Expand All @@ -65,11 +63,9 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Run unit tests with coverage
run: uv run pytest tests/unit/ --cov=src --cov-report=xml:coverage-unit.xml --cov-report=term -v
Expand Down Expand Up @@ -102,11 +98,9 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Install sbctl
run: |
Expand Down Expand Up @@ -143,11 +137,9 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Install sbctl
run: |
Expand Down Expand Up @@ -192,11 +184,9 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Download unit test coverage
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -269,11 +259,9 @@ jobs:

- name: Setup environment
run: |
# Create virtual environment
uv venv -p python3.13 .venv

# Install development dependencies
uv pip install -e ".[dev]"
# UV will automatically create and manage the virtual environment
# Just sync the project dependencies
uv sync --extra dev

- name: Install sbctl
run: |
Expand Down
1 change: 1 addition & 0 deletions .melange.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package:
dependencies:
runtime:
- python3
- py3-psutil # Required for process management (replaces ps/pkill)

environment:
contents:
Expand Down
Binary file modified packages/x86_64/APKINDEX.tar.gz
Binary file not shown.
Binary file modified packages/x86_64/troubleshoot-mcp-server-0.1.0-r0.apk
Binary file not shown.
4 changes: 0 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ dev = [
"pytest-asyncio",
"pytest-timeout",
"pytest-cov",
"black",
"ruff",
"mypy",
"types-PyYAML", # Type stubs for PyYAML
Expand Down Expand Up @@ -82,9 +81,6 @@ exclude = [
"tests/fixtures/mock_kubectl.py"
]

[tool.black]
line-length = 100
target-version = ["py313"]

[tool.mypy]
python_version = "3.13"
Expand Down
6 changes: 1 addition & 5 deletions src/mcp_server_troubleshoot/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,6 @@ async def _wait_for_initialization(

# Try to copy to expected location
try:

safe_copy_file(alt_path, kubeconfig_path)
logger.info(
f"Copied kubeconfig from {alt_path} to {kubeconfig_path}"
Expand Down Expand Up @@ -1102,7 +1101,6 @@ async def _wait_for_initialization(
# Make sure we have a kubeconfig at expected location
if found_kubeconfig_path != kubeconfig_path:
try:

logger.info(
f"Copied kubeconfig from {found_kubeconfig_path} to {kubeconfig_path}"
)
Expand All @@ -1120,7 +1118,7 @@ async def _wait_for_initialization(
if time_since_kubeconfig > (timeout * api_server_wait_percentage):
logger.warning(
f"API server not responding after {time_since_kubeconfig:.1f}s "
f"({api_server_wait_percentage*100:.0f}% of timeout). Proceeding anyway."
f"({api_server_wait_percentage * 100:.0f}% of timeout). Proceeding anyway."
)

# Make sure we have a kubeconfig at expected location
Expand Down Expand Up @@ -1329,7 +1327,6 @@ async def _terminate_sbctl_process(self) -> None:
and proc.info["cmdline"]
and any(bundle_path in arg for arg in proc.info["cmdline"])
):

pid = proc.info["pid"]
logger.debug(
f"Found orphaned sbctl process with PID {pid}, attempting to terminate"
Expand Down Expand Up @@ -1376,7 +1373,6 @@ async def _terminate_sbctl_process(self) -> None:
and proc.info["cmdline"]
and any("serve" in arg for arg in proc.info["cmdline"])
):

try:
proc.terminate()
terminated_count += 1
Expand Down
3 changes: 2 additions & 1 deletion test_direct_mcp_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ async def test_direct_mcp_tool():

# Call the MCP tool directly
result = await asyncio.wait_for(
initialize_bundle(args), timeout=15.0 # 15 second timeout
initialize_bundle(args),
timeout=15.0, # 15 second timeout
)

elapsed = time.time() - start_time
Expand Down
3 changes: 2 additions & 1 deletion test_initialize_bundle_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ async def test_initialize_bundle_tool():
if process.stdout:
print("Waiting for tool response (this should complete in ~6 seconds)...")
response_bytes = await asyncio.wait_for(
process.stdout.readline(), timeout=30.0 # Generous timeout
process.stdout.readline(),
timeout=30.0, # Generous timeout
)
response_line = response_bytes.decode().strip()
print(f"✅ Tool response: {response_line}")
Expand Down
30 changes: 15 additions & 15 deletions tests/e2e/test_build_reliability.py
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,19 @@ def test_build_config_changes_reflected_in_tests():
conftest_content = conftest_path.read_text()

# Verify the problematic caching code was removed
assert (
"Using existing container image for tests" not in conftest_content
), "The problematic caching logic should have been removed from conftest.py"
assert "Using existing container image for tests" not in conftest_content, (
"The problematic caching logic should have been removed from conftest.py"
)

# Verify the fix is in place
assert (
"Building container image (Podman will use layer cache" in conftest_content
), "The fix to always build container images should be present in conftest.py"
assert "Building container image (Podman will use layer cache" in conftest_content, (
"The fix to always build container images should be present in conftest.py"
)

# Verify we always build (no dangerous skip logic)
assert (
"Always run the build process" in conftest_content
), "Tests should always run build process and rely on Podman layer caching"
assert "Always run the build process" in conftest_content, (
"Tests should always run build process and rely on Podman layer caching"
)


def test_sbctl_installation_path_is_correct():
Expand All @@ -147,14 +147,14 @@ def test_sbctl_installation_path_is_correct():
config_content = melange_config.read_text()

# Verify the fix is in place
assert (
"${{targets.destdir}}/usr/bin" in config_content
), "sbctl should be installed to ${{targets.destdir}}/usr/bin for proper packaging"
assert "${{targets.destdir}}/usr/bin" in config_content, (
"sbctl should be installed to ${{targets.destdir}}/usr/bin for proper packaging"
)

# Verify the broken pattern is not present
assert (
"/usr/local/bin/sbctl" not in config_content
), "sbctl should not be installed to /usr/local/bin (the broken path)"
assert "/usr/local/bin/sbctl" not in config_content, (
"sbctl should not be installed to /usr/local/bin (the broken path)"
)

# Verify the installation command sequence is correct
lines = config_content.split("\n")
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/test_container_bundle_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ async def send_request(self, method: str, params: Optional[dict] = None) -> dict
pass

raise RuntimeError(
f"Timeout waiting for response from container. " f"Stderr: {stderr_data.decode()}"
f"Timeout waiting for response from container. Stderr: {stderr_data.decode()}"
)

async def call_tool(self, tool_name: str, arguments: dict) -> dict:
Expand Down
37 changes: 23 additions & 14 deletions tests/e2e/test_container_production_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
in production scenarios, independent of host system setup.
"""

import os
import pytest
import subprocess
import uuid
Expand Down Expand Up @@ -64,9 +63,9 @@ def test_container_has_required_tools_isolated(container_image: str):
f"This indicates the tool is not properly packaged. "
f"returncode: {result.returncode}, stdout: {result.stdout}, stderr: {result.stderr}"
)
assert (
"Usage:" in result.stdout or "usage:" in result.stdout
), f"sbctl --help output doesn't contain expected usage text: {result.stdout}"
assert "Usage:" in result.stdout or "usage:" in result.stdout, (
f"sbctl --help output doesn't contain expected usage text: {result.stdout}"
)

elif tool_name == "kubectl":
# Test kubectl exists and works
Expand Down Expand Up @@ -94,9 +93,9 @@ def test_container_has_required_tools_isolated(container_image: str):
f"This indicates the tool is not properly packaged. "
f"returncode: {result.returncode}, stdout: {result.stdout}, stderr: {result.stderr}"
)
assert (
"Client Version:" in result.stdout
), f"kubectl version output doesn't contain expected version text: {result.stdout}"
assert "Client Version:" in result.stdout, (
f"kubectl version output doesn't contain expected version text: {result.stdout}"
)

elif tool_name == "python3":
# Test python3 exists and works
Expand All @@ -123,9 +122,9 @@ def test_container_has_required_tools_isolated(container_image: str):
f"This indicates the tool is not properly packaged. "
f"returncode: {result.returncode}, stdout: {result.stdout}, stderr: {result.stderr}"
)
assert (
"Python" in result.stdout
), f"python3 --version output doesn't contain expected version text: {result.stdout}"
assert "Python" in result.stdout, (
f"python3 --version output doesn't contain expected version text: {result.stdout}"
)


def test_container_bundle_initialization_isolated(
Expand Down Expand Up @@ -260,10 +259,20 @@ def test_production_container_mcp_protocol():
if not available:
pytest.skip(f"Container runtime {runtime} not available")

# Skip in CI due to container build requirements
# The publish workflow validates container functionality
if os.environ.get("CI") == "true":
pytest.skip("Container runtime tests are skipped in CI - run locally with 'pytest -m slow'")
# Container tests now run in CI to catch deployment issues

# Check if the required container image exists
try:
result = subprocess.run(
[runtime, "image", "exists", "troubleshoot-mcp-server:latest"],
capture_output=True,
)
if result.returncode != 0:
pytest.skip(
"Container image troubleshoot-mcp-server:latest not available - build first with: MELANGE_TEST_BUILD=true ./scripts/build.sh"
)
except FileNotFoundError:
pytest.skip(f"Container runtime {runtime} not found")

container_name = f"mcp-protocol-test-{uuid.uuid4().hex[:8]}"

Expand Down
5 changes: 4 additions & 1 deletion tests/e2e/test_container_shutdown_reliability.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,10 @@ def test_rapid_shutdown_requests(self):

# Test with very quick signal after startup
return_code, stdout, stderr = run_server_as_subprocess(
args=[], env=env, signal_to_send=signal.SIGTERM, signal_delay=0.1 # Very quick signal
args=[],
env=env,
signal_to_send=signal.SIGTERM,
signal_delay=0.1, # Very quick signal
)

# Should handle even rapid shutdown without crashes
Expand Down
18 changes: 9 additions & 9 deletions tests/e2e/test_direct_tool_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ async def test_list_available_bundles_tool_direct(self, test_bundle_copy):
if bundle_name not in bundles_text and "No support bundles found" in bundles_text:
# This is acceptable - bundle might need to be in a specific format
print("Bundle not automatically detected, this is expected for test bundles")
assert (
"support bundles" in bundles_text.lower()
), f"Should mention bundles: {bundles_text}"
assert "support bundles" in bundles_text.lower(), (
f"Should mention bundles: {bundles_text}"
)
else:
assert (
bundle_name in bundles_text
), f"Bundle {bundle_name} should appear in list: {bundles_text}"
assert bundle_name in bundles_text, (
f"Bundle {bundle_name} should appear in list: {bundles_text}"
)

@pytest.mark.asyncio
async def test_file_operations_direct(self, test_bundle_copy):
Expand Down Expand Up @@ -170,9 +170,9 @@ async def test_file_operations_direct(self, test_bundle_copy):
assert len(read_content) > 0, f"Should be able to read file {file_path}"
except json.JSONDecodeError:
# If not JSON, just verify we got some text output
assert (
"file" in files_text.lower() or "directory" in files_text.lower()
), f"File listing should mention files or directories: {files_text}"
assert "file" in files_text.lower() or "directory" in files_text.lower(), (
f"File listing should mention files or directories: {files_text}"
)

@pytest.mark.asyncio
async def test_grep_functionality_direct(self, test_bundle_copy):
Expand Down
Loading