mirror of
https://github.com/karma-riuk/crab.git
synced 2025-07-05 05:28:13 +02:00
formatted handlers.py
This commit is contained in:
57
handlers.py
57
handlers.py
@ -11,6 +11,7 @@ REPORT_SIZE_THRESHOLD = 400 # less than 400 bytes (charcaters), we don't care ab
|
|||||||
USER_ID = os.getuid() # for container user
|
USER_ID = os.getuid() # for container user
|
||||||
GROUP_ID = os.getgid()
|
GROUP_ID = os.getgid()
|
||||||
|
|
||||||
|
|
||||||
class BuildHandler(ABC):
|
class BuildHandler(ABC):
|
||||||
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__()
|
super().__init__()
|
||||||
@ -29,14 +30,13 @@ class BuildHandler(ABC):
|
|||||||
volumes={os.path.abspath(self.path): {"bind": "/repo", "mode": "rw"}},
|
volumes={os.path.abspath(self.path): {"bind": "/repo", "mode": "rw"}},
|
||||||
user=f"{USER_ID}:{GROUP_ID}",
|
user=f"{USER_ID}:{GROUP_ID}",
|
||||||
detach=True,
|
detach=True,
|
||||||
tty=True
|
tty=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
def __exit__(self, *args):
|
def __exit__(self, *args):
|
||||||
self.container.kill()
|
self.container.kill()
|
||||||
self.container.remove()
|
self.container.remove()
|
||||||
|
|
||||||
|
|
||||||
def check_for_tests(self) -> None:
|
def check_for_tests(self) -> None:
|
||||||
with open(os.path.join(self.path, self.build_file), "r") as f:
|
with open(os.path.join(self.path, self.build_file), "r") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
@ -123,13 +123,17 @@ class BuildHandler(ABC):
|
|||||||
candidates.append({"report_file": coverage_report_path, "fqc": fully_qualified_class})
|
candidates.append({"report_file": coverage_report_path, "fqc": fully_qualified_class})
|
||||||
# if coverage_report_path[:len(src_dir)] != src_dir:
|
# if coverage_report_path[:len(src_dir)] != src_dir:
|
||||||
# continue
|
# continue
|
||||||
coverage = get_coverage_for_file(coverage_report_path, fully_qualified_class, os.path.basename(filename))
|
coverage = get_coverage_for_file(
|
||||||
|
coverage_report_path, fully_qualified_class, os.path.basename(filename)
|
||||||
|
)
|
||||||
if coverage != -1:
|
if coverage != -1:
|
||||||
found_at_least_one = True
|
found_at_least_one = True
|
||||||
yield coverage_report_path, coverage
|
yield coverage_report_path, coverage
|
||||||
|
|
||||||
if not found_at_least_one:
|
if not found_at_least_one:
|
||||||
raise FileNotCovered(f"File '{filename}' didn't have any coverage in any of the jacoco reports: {candidates}")
|
raise FileNotCovered(
|
||||||
|
f"File '{filename}' didn't have any coverage in any of the jacoco reports: {candidates}"
|
||||||
|
)
|
||||||
|
|
||||||
def _extract_fully_qualified_class(self, filepath: str) -> str:
|
def _extract_fully_qualified_class(self, filepath: str) -> str:
|
||||||
if not filepath.endswith('.java'):
|
if not filepath.endswith('.java'):
|
||||||
@ -142,7 +146,9 @@ class BuildHandler(ABC):
|
|||||||
try:
|
try:
|
||||||
parsed_tree = javalang.parse.parse(f.read())
|
parsed_tree = javalang.parse.parse(f.read())
|
||||||
except javalang.parser.JavaSyntaxError as e:
|
except javalang.parser.JavaSyntaxError as e:
|
||||||
raise NotJavaFileError(f"File '{filepath}' has a syntax error and could not be parsed by javalang, raised error: '{e}'")
|
raise NotJavaFileError(
|
||||||
|
f"File '{filepath}' has a syntax error and could not be parsed by javalang, raised error: '{e}'"
|
||||||
|
)
|
||||||
|
|
||||||
package_name = None
|
package_name = None
|
||||||
for _, node in parsed_tree.filter(PackageDeclaration):
|
for _, node in parsed_tree.filter(PackageDeclaration):
|
||||||
@ -150,14 +156,15 @@ class BuildHandler(ABC):
|
|||||||
break # Stop after finding the first package declaration
|
break # Stop after finding the first package declaration
|
||||||
|
|
||||||
if package_name is None:
|
if package_name is None:
|
||||||
raise NoPackageFoundError(f"File '{filepath}' did not have a packaged name recognized by javalang")
|
raise NoPackageFoundError(
|
||||||
|
f"File '{filepath}' did not have a packaged name recognized by javalang"
|
||||||
|
)
|
||||||
|
|
||||||
fully_qualified_class = package_name.replace('.', '/')
|
fully_qualified_class = package_name.replace('.', '/')
|
||||||
# src_dir = filepath[:filepath.index(fully_qualified_class)]
|
# src_dir = filepath[:filepath.index(fully_qualified_class)]
|
||||||
fully_qualified_class += "/" + os.path.basename(filepath)[:-5] # -5 to remove '.java'
|
fully_qualified_class += "/" + os.path.basename(filepath)[:-5] # -5 to remove '.java'
|
||||||
return fully_qualified_class
|
return fully_qualified_class
|
||||||
|
|
||||||
|
|
||||||
def clean_repo(self) -> None:
|
def clean_repo(self) -> None:
|
||||||
self.container.exec_run(self.clean_cmd())
|
self.container.exec_run(self.clean_cmd())
|
||||||
|
|
||||||
@ -193,6 +200,7 @@ class BuildHandler(ABC):
|
|||||||
def container_name(self) -> str:
|
def container_name(self) -> str:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MavenHandler(BuildHandler):
|
class MavenHandler(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)
|
||||||
@ -239,7 +247,7 @@ class MavenHandler(BuildHandler):
|
|||||||
self.updates["n_tests_failed"] += failures
|
self.updates["n_tests_failed"] += failures
|
||||||
self.updates["n_tests_errors"] += errors
|
self.updates["n_tests_errors"] += errors
|
||||||
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_paths(self) -> Iterable[str]:
|
def get_jacoco_report_paths(self) -> Iterable[str]:
|
||||||
found_at_least_one = False
|
found_at_least_one = False
|
||||||
@ -253,6 +261,7 @@ class MavenHandler(BuildHandler):
|
|||||||
if not found_at_least_one:
|
if not found_at_least_one:
|
||||||
raise NoCoverageReportFound(f"Couldn't find any 'jacoco.xml' in {self.path}")
|
raise NoCoverageReportFound(f"Couldn't find any 'jacoco.xml' in {self.path}")
|
||||||
|
|
||||||
|
|
||||||
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)
|
||||||
@ -328,44 +337,59 @@ class GradleHandler(BuildHandler):
|
|||||||
found_at_least_one = True
|
found_at_least_one = True
|
||||||
yield os.path.join(root, file)
|
yield os.path.join(root, file)
|
||||||
if not found_at_least_one:
|
if not found_at_least_one:
|
||||||
raise NoCoverageReportFound(f"Couldn't find any 'index.html' inside any 'reports/jacoco' in {self.path}")
|
raise NoCoverageReportFound(
|
||||||
|
f"Couldn't find any 'index.html' inside any 'reports/jacoco' in {self.path}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HandlerException(Exception, ABC):
|
class HandlerException(Exception, ABC):
|
||||||
reason_for_failure = "Generic handler expection (this shouldn't appear)"
|
reason_for_failure = "Generic handler expection (this shouldn't appear)"
|
||||||
|
|
||||||
|
|
||||||
class NoTestsFoundError(HandlerException):
|
class NoTestsFoundError(HandlerException):
|
||||||
reason_for_failure = "No tests found"
|
reason_for_failure = "No tests found"
|
||||||
|
|
||||||
|
|
||||||
class FailedToCompileError(HandlerException):
|
class FailedToCompileError(HandlerException):
|
||||||
reason_for_failure = "Failed to compile"
|
reason_for_failure = "Failed to compile"
|
||||||
|
|
||||||
|
|
||||||
class FailedToTestError(HandlerException):
|
class FailedToTestError(HandlerException):
|
||||||
reason_for_failure = "Failed to test"
|
reason_for_failure = "Failed to test"
|
||||||
|
|
||||||
|
|
||||||
class NoTestResultsToExtractError(HandlerException):
|
class NoTestResultsToExtractError(HandlerException):
|
||||||
reason_for_failure = "Failed to extract test results"
|
reason_for_failure = "Failed to extract test results"
|
||||||
|
|
||||||
|
|
||||||
class CantExecJacoco(HandlerException):
|
class CantExecJacoco(HandlerException):
|
||||||
reason_for_failure = "Couldn't execute jacoco"
|
reason_for_failure = "Couldn't execute jacoco"
|
||||||
|
|
||||||
|
|
||||||
class NoCoverageReportFound(HandlerException):
|
class NoCoverageReportFound(HandlerException):
|
||||||
reason_for_failure = "No coverage report was found"
|
reason_for_failure = "No coverage report was found"
|
||||||
|
|
||||||
|
|
||||||
class FileNotCovered(HandlerException):
|
class FileNotCovered(HandlerException):
|
||||||
reason_for_failure = "Commented file from the PR wasn't not covered"
|
reason_for_failure = "Commented file from the PR wasn't not covered"
|
||||||
|
|
||||||
|
|
||||||
class GradleAggregateReportNotFound(HandlerException):
|
class GradleAggregateReportNotFound(HandlerException):
|
||||||
reason_for_failure = "Couldn't find the aggregate report (with gradle it's messy)"
|
reason_for_failure = "Couldn't find the aggregate report (with gradle it's messy)"
|
||||||
|
|
||||||
|
|
||||||
class NotJavaFileError(HandlerException):
|
class NotJavaFileError(HandlerException):
|
||||||
reason_for_failure = "File that was checked for coverage was not java file"
|
reason_for_failure = "File that was checked for coverage was not java file"
|
||||||
|
|
||||||
|
|
||||||
class NoPackageFoundError(HandlerException):
|
class NoPackageFoundError(HandlerException):
|
||||||
reason_for_failure = "Java file did not contain a valid package name"
|
reason_for_failure = "Java file did not contain a valid package name"
|
||||||
|
|
||||||
|
|
||||||
class FileNotFoundInRepoError(HandlerException):
|
class FileNotFoundInRepoError(HandlerException):
|
||||||
reason_for_failure = "Commented file not found in repo (likely renamed or deleted)"
|
reason_for_failure = "Commented file not found in repo (likely renamed or deleted)"
|
||||||
|
|
||||||
|
|
||||||
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.
|
||||||
@ -388,6 +412,7 @@ def merge_download_lines(lines: list) -> list:
|
|||||||
downloading_block = False
|
downloading_block = False
|
||||||
return cleaned_lines
|
return cleaned_lines
|
||||||
|
|
||||||
|
|
||||||
def merge_unapproved_licences(lines: list) -> list:
|
def merge_unapproved_licences(lines: list) -> list:
|
||||||
"""
|
"""
|
||||||
Merges lines that are part of the same unapproved licences block in Maven output.
|
Merges lines that are part of the same unapproved licences block in Maven output.
|
||||||
@ -412,6 +437,7 @@ def merge_unapproved_licences(lines: list) -> list:
|
|||||||
cleaned_lines.append(line)
|
cleaned_lines.append(line)
|
||||||
return cleaned_lines
|
return cleaned_lines
|
||||||
|
|
||||||
|
|
||||||
def clean_output(output: bytes) -> str:
|
def clean_output(output: bytes) -> str:
|
||||||
output_lines = output.decode().split("\n")
|
output_lines = output.decode().split("\n")
|
||||||
|
|
||||||
@ -420,6 +446,7 @@ def clean_output(output: bytes) -> str:
|
|||||||
|
|
||||||
return "\n".join(cleaned_lines)
|
return "\n".join(cleaned_lines)
|
||||||
|
|
||||||
|
|
||||||
def get_coverage_for_file(xml_file: str, target_fully_qualified_class: str, basename: str) -> float:
|
def get_coverage_for_file(xml_file: str, target_fully_qualified_class: str, basename: str) -> float:
|
||||||
# Parse the XML file
|
# Parse the XML file
|
||||||
tree = ET.parse(xml_file)
|
tree = ET.parse(xml_file)
|
||||||
@ -428,7 +455,10 @@ def get_coverage_for_file(xml_file: str, target_fully_qualified_class: str, base
|
|||||||
# Find coverage for the target file
|
# Find coverage for the target file
|
||||||
for package in root.findall(".//package"):
|
for package in root.findall(".//package"):
|
||||||
for class_ in package.findall("class"):
|
for class_ in package.findall("class"):
|
||||||
if class_.get("sourcefilename") == basename and class_.get("name") == target_fully_qualified_class:
|
if (
|
||||||
|
class_.get("sourcefilename") == basename
|
||||||
|
and class_.get("name") == target_fully_qualified_class
|
||||||
|
):
|
||||||
# Extract line coverage data
|
# Extract line coverage data
|
||||||
line_counter = class_.find("counter[@type='LINE']")
|
line_counter = class_.find("counter[@type='LINE']")
|
||||||
if line_counter is not None:
|
if line_counter is not None:
|
||||||
@ -443,6 +473,7 @@ def get_coverage_for_file(xml_file: str, target_fully_qualified_class: str, base
|
|||||||
return coverage
|
return coverage
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def get_build_handler(root: str, repo: str, updates: dict, verbose: bool = False) -> Optional[BuildHandler]:
|
def get_build_handler(root: str, repo: str, updates: dict, verbose: bool = False) -> Optional[BuildHandler]:
|
||||||
"""
|
"""
|
||||||
Get the path to the build file of a repository. The build file is either a
|
Get the path to the build file of a repository. The build file is either a
|
||||||
@ -466,7 +497,8 @@ def get_build_handler(root: str, repo: str, updates: dict, verbose: bool = False
|
|||||||
to_keep = ["pom.xml", "build.gradle"]
|
to_keep = ["pom.xml", "build.gradle"]
|
||||||
for entry in os.scandir(path):
|
for entry in os.scandir(path):
|
||||||
if entry.is_file() and entry.name in to_keep:
|
if entry.is_file() and entry.name in to_keep:
|
||||||
if verbose: print(f"Found {entry.name} in {repo} root, so keeping it and returning")
|
if verbose:
|
||||||
|
print(f"Found {entry.name} in {repo} root, so keeping it and returning")
|
||||||
updates["depth_of_build_file"] = 0
|
updates["depth_of_build_file"] = 0
|
||||||
if entry.name == "build.gradle":
|
if entry.name == "build.gradle":
|
||||||
updates["build_system"] = "gradle"
|
updates["build_system"] = "gradle"
|
||||||
@ -480,7 +512,8 @@ def get_build_handler(root: str, repo: str, updates: dict, verbose: bool = False
|
|||||||
if entry.is_dir():
|
if entry.is_dir():
|
||||||
for sub_entry in os.scandir(entry.path):
|
for sub_entry in os.scandir(entry.path):
|
||||||
if sub_entry.is_file() and sub_entry.name in to_keep:
|
if sub_entry.is_file() and sub_entry.name in to_keep:
|
||||||
if verbose: print(f"Found {sub_entry.name} in {repo} first level, so keeping it and returning")
|
if verbose:
|
||||||
|
print(f"Found {sub_entry.name} in {repo} first level, so keeping it and returning")
|
||||||
updates["depth_of_build_file"] = 1
|
updates["depth_of_build_file"] = 1
|
||||||
if entry.name == "build.gradle":
|
if entry.name == "build.gradle":
|
||||||
updates["build_system"] = "gradle"
|
updates["build_system"] = "gradle"
|
||||||
|
Reference in New Issue
Block a user