generating and checking the coverage with jacoco

This commit is contained in:
Karma Riuk
2025-03-17 11:29:07 +01:00
parent 47763f5803
commit d7b3d62c0c
2 changed files with 69 additions and 6 deletions

View File

@ -99,10 +99,31 @@ class BuildHandler(ABC):
finally: finally:
signal.alarm(0) # Cancel the alarm signal.alarm(0) # Cancel the alarm
def generate_coverage_report(self):
result = self.container.exec_run(self.generate_coverage_report_cmd())
if result.exit_code != 0:
raise CantExecJacoco(clean_output(result.output))
def check_coverage(self, filename: str) -> None:
"""
Check if the given filename is covered by JaCoCo.
"""
coverage_report_path = self.get_jacoco_report_path()
if not os.path.exists(coverage_report_path):
raise NoCoverageReportFound(f"Coverage report file '{coverage_report_path}' does not exist")
with open(coverage_report_path, "r") as file:
soup = BeautifulSoup(file, "html.parser")
# Extract all files listed in the JaCoCo coverage report
covered_files = [a.text.strip() for a in soup.select("table tbody tr td a")]
if filename not in covered_files:
raise FileNotCovered(f"The file '{filename}' was not present in the report '{coverage_report_path}'")
def clean_repo(self) -> None: def clean_repo(self) -> None:
self.container.exec_run(self.clean_cmd()) self.container.exec_run(self.clean_cmd())
@abstractmethod @abstractmethod
def compile_cmd(self) -> str: def compile_cmd(self) -> str:
pass pass
@ -119,6 +140,14 @@ class BuildHandler(ABC):
def clean_cmd(self) -> str: def clean_cmd(self) -> str:
pass pass
@abstractmethod
def generate_coverage_report_cmd(self) -> str:
pass
@abstractmethod
def get_jacoco_report_path(self) -> str:
pass
@abstractmethod @abstractmethod
def container_name(self) -> str: def container_name(self) -> str:
pass pass
@ -140,6 +169,9 @@ class MavenHandler(BuildHandler):
def clean_cmd(self) -> str: def clean_cmd(self) -> str:
return f"{self.base_cmd} clean" return f"{self.base_cmd} clean"
def generate_coverage_report_cmd(self):
return f"{self.base_cmd} jacoco:report"
def container_name(self) -> str: def container_name(self) -> str:
return "crab-maven" return "crab-maven"
@ -165,6 +197,9 @@ class MavenHandler(BuildHandler):
self.updates["n_tests_skipped"] += skipped self.updates["n_tests_skipped"] += skipped
self.updates["n_tests_passed"] += (tests_run - (failures + errors)) # Calculate passed tests self.updates["n_tests_passed"] += (tests_run - (failures + errors)) # Calculate passed tests
def get_jacoco_report_path(self) -> str:
return os.path.join(self.path, "target/site/jacoco/index.html")
class GradleHandler(BuildHandler): class GradleHandler(BuildHandler):
def __init__(self, repo_path: str, build_file: str, updates: dict) -> None: def __init__(self, repo_path: str, build_file: str, updates: dict) -> None:
super().__init__(repo_path, build_file, updates) super().__init__(repo_path, build_file, updates)
@ -179,6 +214,9 @@ class GradleHandler(BuildHandler):
def clean_cmd(self) -> str: def clean_cmd(self) -> str:
return f"{self.base_cmd} clean" return f"{self.base_cmd} clean"
def generate_coverage_report_cmd(self) -> str:
return f"{self.base_cmd}jacocoTestReport"
def container_name(self) -> str: def container_name(self) -> str:
return "crab-gradle" return "crab-gradle"
@ -197,21 +235,25 @@ class GradleHandler(BuildHandler):
with open(test_results_path, "r") as file: with open(test_results_path, "r") as file:
soup = BeautifulSoup(file, "html.parser") soup = BeautifulSoup(file, "html.parser")
test_div = soup.find("div", class_="infoBox", id="tests") # test_div = soup.select_one("div", class_="infoBox", id="tests")
test_div = soup.select_one("div.infoBox#tests")
if test_div is None: if test_div is None:
raise NoTestResultsToExtractError("No test results found (no div.infoBox#tests)") raise NoTestResultsToExtractError("No test results found (no div.infoBox#tests)")
counter_div = test_div.find("div", class_="counter") # counter_div = test_div.find("div", class_="counter")
counter_div = test_div.select_one("div.counter")
if counter_div is None: if counter_div is None:
raise NoTestResultsToExtractError("No test results found (not div.counter for tests)") raise NoTestResultsToExtractError("No test results found (not div.counter for tests)")
self.updates["n_tests"] = int(counter_div.text.strip()) self.updates["n_tests"] = int(counter_div.text.strip())
failures_div = soup.find("div", class_="infoBox", id="failures") # failures_div = soup.find("div", class_="infoBox", id="failures")
failures_div = soup.select_one("div.infoBox#failures")
if failures_div is None: if failures_div is None:
raise NoTestResultsToExtractError("No test results found (no div.infoBox#failures)") raise NoTestResultsToExtractError("No test results found (no div.infoBox#failures)")
counter_div = failures_div.find("div", class_="counter") # counter_div = failures_div.find("div", class_="counter")
counter_div = failures_div.select_one("div.counter")
if counter_div is None: if counter_div is None:
raise NoTestResultsToExtractError("No test results found (not div.counter for failures)") raise NoTestResultsToExtractError("No test results found (not div.counter for failures)")
@ -220,6 +262,9 @@ class GradleHandler(BuildHandler):
# Calculate passed tests # Calculate passed tests
self.updates["n_tests_passed"] = self.updates["n_tests"] - self.updates["n_tests_failed"] self.updates["n_tests_passed"] = self.updates["n_tests"] - self.updates["n_tests_failed"]
def get_jacoco_report_path(self) -> str:
return os.path.join(self.path, "build/reports/jacoco/test/html/index.html")
class NoTestsFoundError(Exception): class NoTestsFoundError(Exception):
pass pass
@ -232,6 +277,15 @@ class FailedToTestError(Exception):
class NoTestResultsToExtractError(Exception): class NoTestResultsToExtractError(Exception):
pass pass
class CantExecJacoco(Exception):
pass
class NoCoverageReportFound(Exception):
pass
class FileNotCovered(Exception):
pass
def merge_download_lines(lines: list) -> list: def merge_download_lines(lines: list) -> list:
""" """
Merges lines that are part of the same download block in Maven output. Merges lines that are part of the same download block in Maven output.

View File

@ -8,7 +8,7 @@ from tqdm import tqdm
from datetime import datetime from datetime import datetime
from dataset import Dataset, DatasetEntry, FileData, Metadata, Diff from dataset import Dataset, DatasetEntry, FileData, Metadata, Diff
from handlers import FailedToCompileError, FailedToTestError, NoTestsFoundError, NoTestResultsToExtractError, get_build_handler from handlers import CantExecJacoco, FailedToCompileError, FailedToTestError, FileNotCovered, NoCoverageReportFound, NoTestsFoundError, NoTestResultsToExtractError, get_build_handler
from utils import has_only_1_comment, move_github_logging_to_file, clone from utils import has_only_1_comment, move_github_logging_to_file, clone
@ -123,10 +123,16 @@ def process_pull(repo: Repository, pr: PullRequest, dataset: Dataset, repos_dir:
return return
build_handler.set_client(docker_client) build_handler.set_client(docker_client)
def _check_coverage(files: list[str]):
for file in files:
build_handler.check_coverage(file)
steps = [ steps = [
("Checking for tests...", build_handler.check_for_tests), ("Checking for tests...", build_handler.check_for_tests),
("Compiling...", build_handler.compile_repo), ("Compiling...", build_handler.compile_repo),
("Running tests...", build_handler.test_repo), ("Running tests...", build_handler.test_repo),
("Generating coverage...", build_handler.generate_coverage_report),
("Checking coverage...", lambda: _check_coverage([file.filename for file in pr.get_files()])),
] ]
error_map = { error_map = {
@ -134,6 +140,9 @@ def process_pull(repo: Repository, pr: PullRequest, dataset: Dataset, repos_dir:
FailedToCompileError: "Failed to compile", FailedToCompileError: "Failed to compile",
FailedToTestError: "Failed to test", FailedToTestError: "Failed to test",
NoTestResultsToExtractError: "Failed to extract test results", NoTestResultsToExtractError: "Failed to extract test results",
CantExecJacoco: "Coudln't execute jacoco",
NoCoverageReportFound: "No coverage report was found",
FileNotCovered: "A file from the PR was not coverege",
} }
with build_handler, tqdm(total=len(steps), desc="Processing PR", leave=False) as pbar: with build_handler, tqdm(total=len(steps), desc="Processing PR", leave=False) as pbar: