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
40 changes: 13 additions & 27 deletions src/codeql_wrapper/domain/use_cases/codeql_analysis_use_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -270,31 +270,12 @@ def _analyze_project(
for language in project.languages:
self._logger.debug(f"Running CodeQL analysis for {language.value}")

# Create database
db_path = output_dir / f"db-{language.value}"
# Create database and run analysis using the robust method
if self._codeql_runner is None:
raise Exception("CodeQL runner not initialized")

db_result = self._codeql_runner.create_database(
database_path=str(db_path),
source_root=str(project.path),
language=language.value,
overwrite=True,
)

if not db_result.success:
error_msg = (
f"Failed to create database for {language.value}: "
f"{db_result.stderr}"
)
self._logger.error(error_msg)
result.status = AnalysisStatus.FAILED
result.error_message = error_msg
result.end_time = datetime.now()
return result

# Run analysis
output_format = "sarif-latest" # Default output format
# Default output format
output_format = "sarif-latest"

# Map output formats to conventional file extensions
format_to_extension = {
Expand All @@ -309,15 +290,20 @@ def _analyze_project(

file_extension = format_to_extension.get(output_format, ".sarif")
output_file = output_dir / f"results-{language.value}{file_extension}"
analysis_result = self._codeql_runner.analyze_database(
database_path=str(db_path),
output_format=output_format,
output=str(output_file),

# Use the robust create_and_analyze method that handles corrupted databases
analysis_result = self._codeql_runner.create_and_analyze(
source_root=str(project.path),
language=language.value,
output_file=str(output_file),
database_name=str(output_dir / f"db-{language.value}"),
cleanup_database=False, # Keep database for debugging
)

if not analysis_result.success:
error_msg = (
f"Failed to analyze {language.value}: {analysis_result.stderr}"
f"Failed to create database and analyze {language.value}: "
f"{analysis_result.stderr}"
)
self._logger.error(error_msg)
result.status = AnalysisStatus.FAILED
Expand Down
43 changes: 38 additions & 5 deletions src/codeql_wrapper/infrastructure/codeql_runner.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""CodeQL runner infrastructure module."""

import subprocess
import shutil
from pathlib import Path
from typing import List, Optional
from dataclasses import dataclass
Expand Down Expand Up @@ -355,8 +356,43 @@ def create_and_analyze(
)

if not create_result.success:
self.logger.error(f"Database creation failed: {create_result.stderr}")
return create_result
# Check if this is the specific corrupted database error
if (
"Unrecognized file in database cluster" in create_result.stderr
or "does not appear to be a CodeQL database" in create_result.stderr
):
self.logger.warning(
"Detected corrupted database directory, removing and retrying..."
)

# Remove the corrupted directory
if database_path.exists():
try:
shutil.rmtree(database_path, ignore_errors=True)
self.logger.info(
f"Removed corrupted database directory: {database_path}"
)
except Exception as e:
self.logger.error(
f"Failed to remove corrupted directory: {e}"
)
return create_result

# Retry database creation
self.logger.info("Retrying database creation...")
create_result = self.create_database(
str(database_path),
source_root,
language,
build_command,
overwrite=True,
)

if not create_result.success:
self.logger.error(
f"Database creation failed: {create_result.stderr}"
)
return create_result

self.logger.info("Database created successfully")

Expand All @@ -381,6 +417,3 @@ def create_and_analyze(
# Cleanup database if requested
if cleanup_database and database_path.exists():
self.logger.info(f"Cleaning up database at {database_path}")
import shutil

shutil.rmtree(database_path, ignore_errors=True)
9 changes: 4 additions & 5 deletions tests/test_codeql_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,7 @@ def test_analyze_project_full_workflow(self) -> None:

# Mock CodeQL runner and all necessary methods
mock_runner = Mock()
mock_runner.create_database.return_value = Mock(success=True)
mock_runner.analyze_database.return_value = Mock(success=True)
mock_runner.create_and_analyze.return_value = Mock(success=True)

self.use_case._codeql_runner = mock_runner

Expand Down Expand Up @@ -444,8 +443,8 @@ def test_analyze_project_failure(self) -> None:

# Mock CodeQL runner to fail
mock_runner = Mock()
mock_runner.create_database.return_value = Mock(
success=False, stderr="Database creation failed"
mock_runner.create_and_analyze.return_value = Mock(
success=False, stderr="Database creation and analysis failed"
)

self.use_case._codeql_runner = mock_runner
Expand All @@ -459,7 +458,7 @@ def test_analyze_project_failure(self) -> None:
assert result.findings_count == 0
assert (
result.error_message is not None
and "Database creation failed" in result.error_message
and "Database creation and analysis failed" in result.error_message
)

self.use_case._codeql_runner = mock_runner
Expand Down
29 changes: 20 additions & 9 deletions tests/test_codeql_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,19 +511,30 @@ def test_create_and_analyze_analysis_fails(
)

assert result.success is False
assert "Analysis failed" in result.stderr
assert result.stderr == "Analysis failed"
assert mock_run.call_count == 2

@patch("subprocess.run")
def test_create_and_analyze_with_custom_database_path(self, mock_run) -> None:
@patch("pathlib.Path.exists")
def test_create_and_analyze_with_custom_database_path(
self, mock_exists, mock_run
) -> None:
"""Test create and analyze with custom database path."""
# Mock successful database creation and analysis
success_result = Mock()
success_result.returncode = 0
success_result.stdout = "Success"
success_result.stderr = ""
# Mock that database doesn't exist
mock_exists.return_value = False

mock_run.return_value = success_result
# Mock successful create and analyze
create_result = Mock()
create_result.returncode = 0
create_result.stdout = "Database created"
create_result.stderr = ""

analyze_result = Mock()
analyze_result.returncode = 0
analyze_result.stdout = "Analysis complete"
analyze_result.stderr = ""

mock_run.side_effect = [create_result, analyze_result]

result = self.runner.create_and_analyze(
"/source",
Expand All @@ -534,7 +545,7 @@ def test_create_and_analyze_with_custom_database_path(self, mock_run) -> None:
)

assert result.success is True
assert mock_run.call_count == 2
assert mock_run.call_count == 2 # create, analyze

# Check that the custom database path was used
create_call_args = mock_run.call_args_list[0][0][0]
Expand Down