RepoRepair / tools /docker_tool.py
Tanish2416's picture
Upload folder using huggingface_hub
3fe77a3 verified
Raw History Blame Contribute Delete
6.54 kB
"""Docker-based test execution tool."""
import logging
import time
from pathlib import Path
from typing import Optional
import docker
from docker.errors import DockerException
from models import TestResult
logger = logging.getLogger(__name__)
class DockerTool:
"""Tool for running tests in Docker containers."""
def __init__(self):
"""Initialize Docker tool."""
try:
self.client = docker.from_env()
self.client.ping()
logger.info("Docker tool initialized")
except DockerException as e:
logger.error(f"Docker not available: {e}")
raise
def run_tests(
self,
repo_path: Path,
test_command: Optional[str] = None,
timeout: int = 300,
) -> TestResult:
"""
Run tests in a Docker container.
Args:
repo_path: Path to repository
test_command: Test command to run (auto-detect if None)
timeout: Timeout in seconds
Returns:
TestResult object
"""
start_time = time.time()
try:
if test_command is None:
test_command = self._detect_test_command(repo_path)
if not test_command:
logger.warning("No test command detected, skipping tests")
return TestResult(
success=True,
output="No tests detected in repository",
exit_code=0,
duration=0.0,
)
logger.info(f"Running tests with command: {test_command}")
dockerfile_content = self._generate_dockerfile(repo_path)
dockerfile_path = repo_path / "Dockerfile.test"
dockerfile_path.write_text(dockerfile_content)
try:
image, build_logs = self.client.images.build(
path=str(repo_path),
dockerfile="Dockerfile.test",
tag="reporepair-test:latest",
rm=True,
)
logger.info("Docker image built successfully")
container = self.client.containers.run(
image=image.id,
command=test_command,
detach=True,
remove=True,
)
result = container.wait(timeout=timeout)
output = container.logs().decode("utf-8")
exit_code = result["StatusCode"]
duration = time.time() - start_time
success = exit_code == 0
logger.info(f"Tests {'passed' if success else 'failed'} in {duration:.2f}s")
return TestResult(
success=success,
output=output,
exit_code=exit_code,
duration=duration,
)
finally:
if dockerfile_path.exists():
dockerfile_path.unlink()
try:
self.client.images.remove("reporepair-test:latest", force=True)
except:
pass
except Exception as e:
duration = time.time() - start_time
logger.error(f"Test execution failed: {e}")
return TestResult(
success=False,
output=f"Error: {str(e)}",
exit_code=1,
duration=duration,
)
def _detect_test_command(self, repo_path: Path) -> Optional[str]:
"""
Auto-detect test command based on project files.
Args:
repo_path: Path to repository
Returns:
Test command or None
"""
if (repo_path / "pytest.ini").exists() or (repo_path / "setup.py").exists():
return "pytest"
if (repo_path / "package.json").exists():
import json
try:
package_json = json.loads((repo_path / "package.json").read_text())
if "test" in package_json.get("scripts", {}):
return "npm test"
except:
pass
if (repo_path / "Makefile").exists():
makefile_content = (repo_path / "Makefile").read_text()
if "test:" in makefile_content:
return "make test"
if (repo_path / "pom.xml").exists():
return "mvn test"
if (repo_path / "go.mod").exists():
return "go test ./..."
if (repo_path / "Cargo.toml").exists():
return "cargo test"
return None
def _generate_dockerfile(self, repo_path: Path) -> str:
"""
Generate appropriate Dockerfile based on project type.
Args:
repo_path: Path to repository
Returns:
Dockerfile content
"""
if (repo_path / "requirements.txt").exists():
return """FROM python:3.10-slim
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
CMD ["pytest"]
"""
if (repo_path / "package.json").exists():
return """FROM node:18-slim
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
CMD ["npm", "test"]
"""
if (repo_path / "go.mod").exists():
return """FROM golang:1.21-alpine
WORKDIR /app
COPY go.* ./
RUN go mod download
COPY . .
CMD ["go", "test", "./..."]
"""
if (repo_path / "Cargo.toml").exists():
return """FROM rust:1.75-slim
WORKDIR /app
COPY . /app
RUN cargo build --release
CMD ["cargo", "test"]
"""
return """FROM alpine:latest
WORKDIR /app
COPY . /app
CMD ["echo", "No tests found"]
"""
def is_docker_available(self) -> bool:
"""Check if Docker is available."""
try:
self.client.ping()
return True
except:
return False