From e8674512b58961162e767ad9473c1ebbbc0b9f6c Mon Sep 17 00:00:00 2001 From: Arpad Borsos Date: Fri, 13 Sep 2024 11:27:03 +0200 Subject: [PATCH] Fix some type errors in `LanguageProcessor`s (#701) --- services/report/languages/cobertura.py | 39 +++--- services/report/languages/helpers.py | 19 ++- services/report/languages/jacoco.py | 22 +-- services/report/languages/lcov.py | 34 ++--- services/report/languages/lua.py | 14 +- services/report/languages/mono.py | 7 +- services/report/languages/pycoverage.py | 132 +++++++++--------- services/report/languages/scoverage.py | 62 +++----- .../languages/tests/unit/test_cobertura.py | 6 +- .../report/languages/tests/unit/test_dlst.py | 2 +- .../unit/test_pycoverage_encoded_labels.py | 8 +- .../languages/tests/unit/test_scoverage.py | 4 +- .../report/languages/tests/unit/test_vb2.py | 4 +- services/report/languages/vb.py | 10 +- services/report/languages/vb2.py | 21 +-- services/report/languages/xcode.py | 126 ++++++++--------- services/report/languages/xcodeplist.py | 1 + services/report/report_builder.py | 45 +++--- 18 files changed, 275 insertions(+), 281 deletions(-) diff --git a/services/report/languages/cobertura.py b/services/report/languages/cobertura.py index 799ed10ed..501fa9107 100644 --- a/services/report/languages/cobertura.py +++ b/services/report/languages/cobertura.py @@ -15,10 +15,7 @@ class CoberturaProcessor(BaseLanguageProcessor): def matches_content(self, content: Element, first_line: str, name: str) -> bool: - return bool( - next(content.iter("coverage"), None) - or next(content.iter("scoverage"), None) - ) + return content.tag in ("coverage", "scoverage") @sentry_sdk.trace def process( @@ -45,14 +42,7 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None ("codecov", "max_report_age"), "12h ago" ): try: - timestamp = next(xml.iter("coverage")).get("timestamp") - except StopIteration: - try: - timestamp = next(xml.iter("scoverage")).get("timestamp") - except StopIteration: - timestamp = None - - try: + timestamp = xml.get("timestamp") parsed_datetime = Date(timestamp) is_valid_timestamp = True except TimestringInvalid: @@ -75,15 +65,18 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None for _class in xml.iter("class"): filename = _class.attrib["filename"] _file = report_builder_session.create_coverage_file(filename, do_fix_path=False) + assert ( + _file is not None + ), "`create_coverage_file` with pre-fixed path is infallible" for line in _class.iter("line"): _line = line.attrib - ln = _line["number"] + ln: str | int = _line["number"] if ln == "undefined": continue ln = int(ln) if ln > 0: - coverage = None + coverage: str | int _type = CoverageType.line missing_branches = None @@ -100,9 +93,9 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None coverage = Int(_line.get("hits")) # [python] [scoverage] [groovy] Conditions - conditions = _line.get("missing-branches", None) - if conditions: - conditions = conditions.split(",") + conditions_text = _line.get("missing-branches", None) + if conditions_text: + conditions = conditions_text.split(",") if len(conditions) > 1 and set(conditions) == set(("exit",)): # python: "return [...] missed" conditions = ["loop", "exit"] @@ -179,15 +172,15 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None # [scala] [scoverage] for stmt in _class.iter("statement"): # scoverage will have repeated data - stmt = stmt.attrib - if stmt.get("ignored") == "true": + attr = stmt.attrib + if attr.get("ignored") == "true": continue - coverage = Int(stmt["invocation-count"]) - line_no = int(stmt["line"]) + coverage = Int(attr["invocation-count"]) + line_no = int(attr["line"]) coverage_type = CoverageType.line - if stmt["branch"] == "true": + if attr["branch"] == "true": coverage_type = CoverageType.branch - elif stmt["method"]: + elif attr["method"]: coverage_type = CoverageType.method _file.append( diff --git a/services/report/languages/helpers.py b/services/report/languages/helpers.py index 90d878b46..958a37933 100644 --- a/services/report/languages/helpers.py +++ b/services/report/languages/helpers.py @@ -1,4 +1,19 @@ -def remove_non_ascii(string, replace_with=""): +from xml.etree.ElementTree import Element + + +def remove_non_ascii(string: str) -> str: # ASCII control characters <=31, 127 # Extended ASCII characters: >=128 - return "".join([i if 31 < ord(i) < 127 else replace_with for i in string]) + return "".join([c if 31 < ord(c) < 127 else "" for c in string]) + + +def child_text(parent: Element, element: str) -> str: + """ + Returns the text content of the first element of type `element` of `parent`. + + This defaults to the empty string if no child is found, or the child does not have any text. + """ + child = parent.find(element) + if child is None: + return "" + return child.text or "" diff --git a/services/report/languages/jacoco.py b/services/report/languages/jacoco.py index a625b9885..aa7f322bd 100644 --- a/services/report/languages/jacoco.py +++ b/services/report/languages/jacoco.py @@ -70,7 +70,9 @@ def try_to_fix_path(path: str) -> str | None: for package in xml.iter("package"): base_name = package.attrib["name"] - file_method_complixity = defaultdict(dict) + file_method_complixity: dict[str, dict[int, tuple[int, int]]] = defaultdict( + dict + ) # Classes complexity for _class in package.iter("class"): class_name = _class.attrib["name"] @@ -99,19 +101,23 @@ def try_to_fix_path(path: str) -> str | None: _file = report_builder_session.create_coverage_file( filename, do_fix_path=False ) + assert ( + _file is not None + ), "`create_coverage_file` with pre-fixed path is infallible" for line in source.iter("line"): - line = line.attrib - if line["mb"] != "0": - cov = "%s/%s" % (line["cb"], int(line["mb"]) + int(line["cb"])) + attr = line.attrib + cov: int | str + if attr["mb"] != "0": + cov = "%s/%s" % (attr["cb"], int(attr["mb"]) + int(attr["cb"])) coverage_type = CoverageType.branch - elif line["cb"] != "0": - cov = "%s/%s" % (line["cb"], line["cb"]) + elif attr["cb"] != "0": + cov = "%s/%s" % (attr["cb"], attr["cb"]) coverage_type = CoverageType.branch else: - cov = int(line["ci"]) + cov = int(attr["ci"]) coverage_type = CoverageType.line if ( @@ -121,7 +127,7 @@ def try_to_fix_path(path: str) -> str | None: ): cov = 1 - ln = int(line["nr"]) + ln = int(attr["nr"]) if ln > 0: complexity = method_complixity.get(ln) if complexity: diff --git a/services/report/languages/lcov.py b/services/report/languages/lcov.py index 8f7c2cae8..c9fdeace3 100644 --- a/services/report/languages/lcov.py +++ b/services/report/languages/lcov.py @@ -33,14 +33,13 @@ def from_txt(reports: bytes, report_builder_session: ReportBuilderSession) -> No def _process_file( doc: bytes, report_builder_session: ReportBuilderSession -) -> ReportFile: +) -> ReportFile | None: _already_informed_of_negative_execution_count = False - lines = {} - branches = defaultdict(dict) + branches: dict[str, dict[str, int]] = defaultdict(dict) fln, fh = {}, {} JS = False CPP = False - skip_lines = [] + skip_lines: list[str] = [] _file = None for encoded_line in BytesIO(doc): @@ -60,7 +59,7 @@ def _process_file( # BRH: branches hit continue - elif method == "SF": + if method == "SF": """ For each source file referenced in the .da file, there is a section containing filename and coverage data: @@ -69,13 +68,14 @@ def _process_file( """ # file name _file = report_builder_session.create_coverage_file(content) - if _file is None: - return None - JS = content[-3:] == ".js" CPP = content[-4:] == ".cpp" + continue + + if _file is None: + return None - elif method == "DA": + if method == "DA": """ Then there is a list of execution counts for each instrumented line (i.e. a line which resulted in executable code): @@ -86,9 +86,9 @@ def _process_file( if line.startswith("undefined,"): continue - splited_content = content.split(",") - line = splited_content[0] - hit = splited_content[1] + splitted_content = content.split(",") + line = splitted_content[0] + hit = splitted_content[1] if line[0] in ("0", "n") or hit[0] in ("=", "s"): continue @@ -163,10 +163,12 @@ def _process_file( 0 if taken in ("-", "0") else 1 ) + if _file is None: + return None + # remove skipped for sl in skip_lines: branches.pop(sl, None) - lines.pop(sl, None) methods = fln.values() @@ -174,13 +176,13 @@ def _process_file( for ln, br in branches.items(): s, li = sum(br.values()), len(br.values()) mb = [bid for bid, cov in br.items() if cov == 0] - cov = "%s/%s" % (s, li) - + coverage = f"{s}/{li}" coverage_type = CoverageType.method if ln in methods else CoverageType.branch + _file.append( int(ln), report_builder_session.create_coverage_line( - cov, + coverage, coverage_type, missing_branches=(mb if mb != [] else None), ), diff --git a/services/report/languages/lua.py b/services/report/languages/lua.py index fade3549f..6a835780e 100644 --- a/services/report/languages/lua.py +++ b/services/report/languages/lua.py @@ -20,18 +20,18 @@ def process( docs = re.compile(r"^=+\n", re.M).split -def from_txt(string: bytes, report_builder_session: ReportBuilderSession) -> None: +def from_txt(input: bytes, report_builder_session: ReportBuilderSession) -> None: _file = None - for string in docs(string.decode(errors="replace").replace("\t", " ")): - string = string.rstrip() - if string == "Summary": + for line in docs(input.decode(errors="replace").replace("\t", " ")): + line = line.rstrip() + if line == "Summary": _file = None - elif string.endswith((".lua", ".lisp")): - _file = report_builder_session.create_coverage_file(string) + elif line.endswith((".lua", ".lisp")): + _file = report_builder_session.create_coverage_file(line) elif _file is not None: - for ln, source in enumerate(string.splitlines(), start=1): + for ln, source in enumerate(line.splitlines(), start=1): try: cov = source.strip().split(" ")[0] cov = 0 if cov[-2:] in ("*0", "0") else int(cov) diff --git a/services/report/languages/mono.py b/services/report/languages/mono.py index 36b441b12..63c56a668 100644 --- a/services/report/languages/mono.py +++ b/services/report/languages/mono.py @@ -27,17 +27,18 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None if filename not in files: _file = report_builder_session.create_coverage_file(filename) files[filename] = _file + _file = files[filename] if _file is None: continue # loop through statements for line in method.iter("statement"): - line = line.attrib - coverage = int(line["counter"]) + attr = line.attrib + coverage = int(attr["counter"]) _file.append( - int(line["line"]), + int(attr["line"]), report_builder_session.create_coverage_line( coverage, ), diff --git a/services/report/languages/pycoverage.py b/services/report/languages/pycoverage.py index e634a18ac..16fd0770d 100644 --- a/services/report/languages/pycoverage.py +++ b/services/report/languages/pycoverage.py @@ -1,5 +1,3 @@ -from typing import Dict, List, Optional, Union - import sentry_sdk from services.report.languages.base import BaseLanguageProcessor @@ -11,14 +9,69 @@ class PyCoverageProcessor(BaseLanguageProcessor): def matches_content(self, content: dict, first_line: str, name: str) -> bool: - return ( - "meta" in content - and "files" in content - and isinstance(content.get("meta"), dict) - and "show_contexts" in content.get("meta") - ) + meta = "meta" in content and content["meta"] + return "files" in content and isinstance(meta, dict) and "show_contexts" in meta + + @sentry_sdk.trace + def process( + self, content: dict, report_builder_session: ReportBuilderSession + ) -> None: + labels_table = LabelsTable(report_builder_session, content) + + for filename, file_coverage in content["files"].items(): + _file = report_builder_session.create_coverage_file(filename) + if _file is None: + continue + + lines_and_coverage = [ + (COVERAGE_HIT, ln) for ln in file_coverage["executed_lines"] + ] + [(COVERAGE_MISS, ln) for ln in file_coverage["missing_lines"]] + for cov, ln in lines_and_coverage: + if ln > 0: + label_list_of_lists: list[list[str]] | list[list[int]] = [] + if report_builder_session.should_use_label_index: + label_list_of_lists = [ + [single_id] + for single_id in labels_table._get_list_of_label_ids( + report_builder_session.label_index, + file_coverage.get("contexts", {}).get(str(ln), []), + ) + ] + else: + label_list_of_lists = [ + [labels_table._normalize_label(testname)] + for testname in file_coverage.get("contexts", {}).get( + str(ln), [] + ) + ] + _file.append( + ln, + report_builder_session.create_coverage_line( + cov, + labels_list_of_lists=label_list_of_lists, + ), + ) + report_builder_session.append(_file) + + +class LabelsTable: + def __init__( + self, report_builder_session: ReportBuilderSession, content: dict + ) -> None: + self.labels_table: dict[str, str] = {} + self.reverse_table: dict[str, int] = {} + self.are_labels_already_encoded = False + + # Compressed pycoverage files will include a labels_table + if "labels_table" in content: + self.labels_table = content["labels_table"] + # We can pre-populate some of the indexes that will be used + for idx, testname in self.labels_table.items(): + clean_label = self._normalize_label(testname) + report_builder_session.label_index[int(idx)] = clean_label + self.are_labels_already_encoded = True - def _normalize_label(self, testname) -> str: + def _normalize_label(self, testname: int | float | str) -> str: if isinstance(testname, int) or isinstance(testname, float): # This is from a compressed report. # Pull label from the labels_table @@ -30,9 +83,9 @@ def _normalize_label(self, testname) -> str: def _get_list_of_label_ids( self, - current_label_idx: Optional[Dict[int, str]], - line_contexts: List[Union[str, int]] = None, - ) -> List[int]: + current_label_idx: dict[int, str], + line_contexts: list[str | int], + ) -> list[int]: if self.are_labels_already_encoded: # The line contexts already include indexes in the table. # We can re-use the table and don't have to do anything with contexts. @@ -51,58 +104,3 @@ def _get_list_of_label_ids( label_ids_for_line.add(label_id) return sorted(label_ids_for_line) - - @sentry_sdk.trace - def process( - self, content: dict, report_builder_session: ReportBuilderSession - ) -> None: - # Compressed pycoverage files will include a labels_table - # Mapping label_idx: int --> label: str - self.labels_table: dict[int, str] = None - self.reverse_table = {} - self.are_labels_already_encoded = False - if "labels_table" in content: - self.labels_table = content["labels_table"] - # We can pre-populate some of the indexes that will be used - for idx, testname in self.labels_table.items(): - clean_label = self._normalize_label(testname) - report_builder_session.label_index[int(idx)] = clean_label - self.are_labels_already_encoded = True - - for filename, file_coverage in content["files"].items(): - _file = report_builder_session.create_coverage_file(filename) - if _file is None: - continue - - lines_and_coverage = [ - (COVERAGE_HIT, ln) for ln in file_coverage["executed_lines"] - ] + [(COVERAGE_MISS, ln) for ln in file_coverage["missing_lines"]] - for cov, ln in lines_and_coverage: - if report_builder_session.should_use_label_index: - label_list_of_lists = [ - [single_id] - for single_id in self._get_list_of_label_ids( - report_builder_session.label_index, - file_coverage.get("contexts", {}).get(str(ln), []), - ) - ] - else: - label_list_of_lists = [ - [self._normalize_label(testname)] - for testname in file_coverage.get("contexts", {}).get( - str(ln), [] - ) - ] - if ln > 0: - _file.append( - ln, - report_builder_session.create_coverage_line( - cov, - labels_list_of_lists=label_list_of_lists, - ), - ) - report_builder_session.append(_file) - - # We don't need these anymore, so let them be removed by the garbage collector - self.reverse_table = None - self.labels_table = None diff --git a/services/report/languages/scoverage.py b/services/report/languages/scoverage.py index 134cdbc6b..2a4dfea06 100644 --- a/services/report/languages/scoverage.py +++ b/services/report/languages/scoverage.py @@ -4,9 +4,11 @@ from shared.helpers.numeric import maxint from shared.reports.resources import ReportFile -from services.report.languages.base import BaseLanguageProcessor from services.report.report_builder import CoverageType, ReportBuilderSession +from .base import BaseLanguageProcessor +from .helpers import child_text + class SCoverageProcessor(BaseLanguageProcessor): def matches_content(self, content: Element, first_line: str, name: str) -> bool: @@ -20,53 +22,24 @@ def process( def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None: - path_fixer = report_builder_session.path_fixer - - ignore = [] - cache_fixes = {} - _cur_file_name = None - files: dict[str, ReportFile] = {} + files: dict[str, ReportFile | None] = {} for statement in xml.iter("statement"): - # Determine the path - unfixed_path = next(statement.iter("source")).text - if unfixed_path in ignore: - continue - - elif unfixed_path in cache_fixes: - # cached results - filename = cache_fixes[unfixed_path] - - else: - # fix path - filename = path_fixer(unfixed_path) - if filename is None: - # add unfixed to list of ignored - ignore.append(unfixed_path) - continue - - # cache result (unfixed => filenmae) - cache_fixes[unfixed_path] = filename + filename = child_text(statement, "source") + if filename not in files: + files[filename] = report_builder_session.create_coverage_file(filename) - # Get the file - if filename != _cur_file_name: - _cur_file_name = filename - if filename not in files: - _file = report_builder_session.create_coverage_file( - filename, do_fix_path=False - ) - files[filename] = _file - _file = files[filename] + _file = files.get(filename) + if _file is None: + continue # Add the line - ln = int(next(statement.iter("line")).text) - hits = next(statement.iter("count")).text - try: - if next(statement.iter("ignored")).text == "true": - continue - except StopIteration: - pass + ln = int(child_text(statement, "line")) + hits = child_text(statement, "count") + + if child_text(statement, "ignored") == "true": + continue - if next(statement.iter("branch")).text == "true": + if child_text(statement, "branch") == "true": cov = "%s/2" % hits _file.append( ln, @@ -85,4 +58,5 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None ) for _file in files.values(): - report_builder_session.append(_file) + if _file is not None: + report_builder_session.append(_file) diff --git a/services/report/languages/tests/unit/test_cobertura.py b/services/report/languages/tests/unit/test_cobertura.py index a148b3a31..99e9eb54d 100644 --- a/services/report/languages/tests/unit/test_cobertura.py +++ b/services/report/languages/tests/unit/test_cobertura.py @@ -394,9 +394,13 @@ def test_expired(self, date): def test_matches_content(self): processor = cobertura.CoberturaProcessor() - content = etree.fromstring(xml % ("", int(time()), "", "")) first_line = xml.split("\n", 1)[0] name = "coverage.xml" + + content = etree.fromstring(xml % ("", int(time()), "", "")) + assert processor.matches_content(content, first_line, name) + + content = etree.fromstring(xml % ("s", int(time()), "", "s")) assert processor.matches_content(content, first_line, name) def test_not_matches_content(self): diff --git a/services/report/languages/tests/unit/test_dlst.py b/services/report/languages/tests/unit/test_dlst.py index 3fc17e91a..382489448 100644 --- a/services/report/languages/tests/unit/test_dlst.py +++ b/services/report/languages/tests/unit/test_dlst.py @@ -48,4 +48,4 @@ def test_matches_content(self): " 1|test", "name", ) - return dlst.DLSTProcessor().matches_content(content, first_line, name) + assert dlst.DLSTProcessor().matches_content(content, first_line, name) diff --git a/services/report/languages/tests/unit/test_pycoverage_encoded_labels.py b/services/report/languages/tests/unit/test_pycoverage_encoded_labels.py index cc06d8de3..134fe59b6 100644 --- a/services/report/languages/tests/unit/test_pycoverage_encoded_labels.py +++ b/services/report/languages/tests/unit/test_pycoverage_encoded_labels.py @@ -1,4 +1,4 @@ -from services.report.languages.pycoverage import PyCoverageProcessor +from services.report.languages.pycoverage import LabelsTable, PyCoverageProcessor from services.report.report_builder import SpecialLabelsEnum from test_utils.base import BaseTestCase @@ -189,9 +189,7 @@ def test_matches_content_pycoverage(self): assert not p.matches_content({"meta": {}}, "", "coverage.json") def test__get_list_of_label_ids(self): - p = PyCoverageProcessor() - p.are_labels_already_encoded = False - p.reverse_table = {} + p = LabelsTable(None, {}) current_label_idx = {} assert p._get_list_of_label_ids(current_label_idx, [""]) == [1] assert current_label_idx == { @@ -213,7 +211,7 @@ def test__get_list_of_label_ids(self): } def test__get_list_of_label_ids_already_encoded(self): - p = PyCoverageProcessor() + p = LabelsTable(None, {}) p.are_labels_already_encoded = True assert p._get_list_of_label_ids({}, ["2"]) == [2] assert p._get_list_of_label_ids({}, ["2", "3", "1"]) == [1, 2, 3] diff --git a/services/report/languages/tests/unit/test_scoverage.py b/services/report/languages/tests/unit/test_scoverage.py index c19f20339..44c4decd8 100644 --- a/services/report/languages/tests/unit/test_scoverage.py +++ b/services/report/languages/tests/unit/test_scoverage.py @@ -61,12 +61,10 @@ def fixes(path): report = report_builder_session.output_report() processed_report = self.convert_report_to_better_readable(report) - expected_result_archive = { + assert processed_report["archive"] == { "source.scala": [ (1, 1, None, [[0, 1, None, None, None]], None, None), (2, "0/2", "b", [[0, "0/2", None, None, None]], None, None), (3, 0, None, [[0, 0, None, None, None]], None, None), ] } - - assert expected_result_archive == processed_report["archive"] diff --git a/services/report/languages/tests/unit/test_vb2.py b/services/report/languages/tests/unit/test_vb2.py index a3e2723e6..001de5a9f 100644 --- a/services/report/languages/tests/unit/test_vb2.py +++ b/services/report/languages/tests/unit/test_vb2.py @@ -53,7 +53,7 @@ def test_report(self): report = report_builder_session.output_report() processed_report = self.convert_report_to_better_readable(report) - expected_result_archive = { + assert processed_report["archive"] == { "Source/Mobius/csharp/Tests.Common/RowHelper.cs": [ (260, 1, None, [[0, 1, None, None, None]], None, None), (261, 0, None, [[0, 0, None, None, None]], None, None), @@ -63,5 +63,3 @@ def test_report(self): (258, True, None, [[0, True, None, None, None]], None, None) ], } - - assert expected_result_archive == processed_report["archive"] diff --git a/services/report/languages/vb.py b/services/report/languages/vb.py index 1d123a1c2..0a585a444 100644 --- a/services/report/languages/vb.py +++ b/services/report/languages/vb.py @@ -31,14 +31,14 @@ def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None # loop through each line for line in module.iter("range"): - line = line.attrib - _file = files.get(line["source_id"]) + attr = line.attrib + _file = files.get(attr["source_id"]) if _file is None: continue - coverage = line["covered"] - coverage = 1 if coverage == "yes" else 0 if coverage == "no" else True - for ln in range(int(line["start_line"]), int(line["end_line"]) + 1): + cov_txt = attr["covered"] + coverage = 1 if cov_txt == "yes" else 0 if cov_txt == "no" else True + for ln in range(int(attr["start_line"]), int(attr["end_line"]) + 1): _file.append( ln, report_builder_session.create_coverage_line( diff --git a/services/report/languages/vb2.py b/services/report/languages/vb2.py index 41744e97f..e68b896a8 100644 --- a/services/report/languages/vb2.py +++ b/services/report/languages/vb2.py @@ -3,9 +3,11 @@ import sentry_sdk from shared.reports.resources import ReportFile -from services.report.languages.base import BaseLanguageProcessor from services.report.report_builder import ReportBuilderSession +from .base import BaseLanguageProcessor +from .helpers import child_text + class VbTwoProcessor(BaseLanguageProcessor): def matches_content(self, content: Element, first_line: str, name: str) -> bool: @@ -20,23 +22,24 @@ def process( def from_xml(xml: Element, report_builder_session: ReportBuilderSession) -> None: files: dict[str, ReportFile] = {} - for source in xml.iter("SourceFileNames"): + for source in xml.iterfind("SourceFileNames"): _file = report_builder_session.create_coverage_file( - source.find("SourceFileName").text.replace("\\", "/") + child_text(source, "SourceFileName").replace("\\", "/") ) if _file is not None: - files[source.find("SourceFileID").text] = _file + files[child_text(source, "SourceFileID")] = _file - for line in xml.iter("Lines"): - _file = files.get(line.find("SourceFileID").text) + for line in xml.iterfind("Lines"): + _file = files.get(child_text(line, "SourceFileID")) if _file is None: continue # 0 == hit, 1 == partial, 2 == miss - cov = line.find("Coverage").text - cov = 1 if cov == "0" else 0 if cov == "2" else True + cov_txt = child_text(line, "Coverage") + cov = 1 if cov_txt == "0" else 0 if cov_txt == "2" else True for ln in range( - int(line.find("LnStart").text), int(line.find("LnEnd").text) + 1 + int(child_text(line, "LnStart")), + int(child_text(line, "LnEnd")) + 1, ): _file.append( ln, diff --git a/services/report/languages/xcode.py b/services/report/languages/xcode.py index 9946e3e25..23312b8bd 100644 --- a/services/report/languages/xcode.py +++ b/services/report/languages/xcode.py @@ -73,72 +73,72 @@ def from_txt(content: bytes, report_builder_session: ReportBuilderSession) -> No cov_i = 0 for encoded_line in BytesIO(content): line = encoded_line.decode(errors="replace").rstrip("\n") - if line: - line = remove_non_ascii(line).strip(" ") - if line[0] not in ("-", "|", "w"): - line = line.replace(NAME_COLOR, "") - if line.endswith(":") and "|" not in line: - if _file is not None: - report_builder_session.append(_file) - # file names could be "relative/path.abc:" or "/absolute/path.abc:" - # new file - _file = report_builder_session.create_coverage_file( - line.replace(END_PARTIAL, "")[1:-1] - ) + line = remove_non_ascii(line).strip(" ") + if not line or line[0] in ("-", "|", "w"): + continue + + line = line.replace(NAME_COLOR, "") + if line.endswith(":") and "|" not in line: + if _file is not None: + report_builder_session.append(_file) + # file names could be "relative/path.abc:" or "/absolute/path.abc:" + # new file + _file = report_builder_session.create_coverage_file( + line.replace(END_PARTIAL, "")[1:-1] + ) + continue + + if _file is None: + continue + + parts = line.split("|") + lnl = len(parts) + if lnl > 1: + if lnl > 2 and parts[2].strip() == "}": + # skip ending bracket lines + continue + + try: + ln = int(parts[ln_i].strip()) + except Exception: + # bad xcode line + if parts[0] == "1": + ln_i, cov_i = 0, 1 + ln = 1 + else: + continue - elif _file is None: + if parts[2]: + partials = get_partials_in_line(parts[2]) + if partials: + _file.append( + ln, + report_builder_session.create_coverage_line( + 0, + partials=partials, + ), + ) continue - line = line.split("|") - lnl = len(line) - if lnl > 1: - if lnl > 2 and line[2].strip() == "}": - # skip ending bracket lines - continue - - try: - ln = int(line[ln_i].strip()) - except Exception: - # bad xcode line - if line[0] == "1": - ln_i, cov_i = 0, 1 - ln = 1 - else: - continue - - if line[2]: - partials = get_partials_in_line(line[2]) - if partials: - _file.append( - ln, - report_builder_session.create_coverage_line( - 0, - partials=partials, - ), - ) - continue - - cov = line[cov_i].replace("E", "").strip() - if cov != "": - try: - if "k" in cov or "K" in cov: - cov = maxint( - str(int(float(cov.replace("k", "")) * 1000.0)) - ) - elif "m" in cov or "M" in cov: - cov = 99999 - else: - cov = maxint(str(int(float(cov)))) - except Exception: - cov = 1 - - try: - _file.append( - ln, - report_builder_session.create_coverage_line(cov), - ) - except Exception: - pass + cov_s = parts[cov_i].replace("E", "").strip() + if cov_s != "": + try: + if "k" in cov_s or "K" in cov_s: + cov = maxint(str(int(float(cov_s.replace("k", "")) * 1000.0))) + elif "m" in cov_s or "M" in cov_s: + cov = 99999 + else: + cov = maxint(str(int(float(cov_s)))) + except Exception: + cov = 1 + + try: + _file.append( + ln, + report_builder_session.create_coverage_line(cov), + ) + except Exception: + pass if _file is not None: report_builder_session.append(_file) diff --git a/services/report/languages/xcodeplist.py b/services/report/languages/xcodeplist.py index 7f4f2d3cc..abcf1b9ab 100644 --- a/services/report/languages/xcodeplist.py +++ b/services/report/languages/xcodeplist.py @@ -12,6 +12,7 @@ def matches_content(self, content: bytes, first_line: str, name: str) -> bool: return name.endswith("xccoverage.plist") if content.find(b'') > -1 and content.startswith(b"