Coverage for src/debputy/linting/lint_report_junit.py: 27%

41 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-10-12 15:06 +0000

1import textwrap 

2from typing import TYPE_CHECKING 

3from collections.abc import Iterable 

4 

5from debputy.util import _info 

6 

7if TYPE_CHECKING: 

8 from junit_xml import TestSuite, TestCase, to_xml_report_file 

9else: 

10 try: 

11 from junit_xml import TestSuite, TestCase, to_xml_report_file 

12 except ImportError: 

13 pass 

14 

15 

16from debputy.linting.lint_util import ( 

17 LintReport, 

18 LintDiagnosticResult, 

19 LintDiagnosticResultState, 

20 debputy_severity, 

21) 

22 

23 

24class JunitLintReport(LintReport): 

25 

26 def __init__(self, output_filename: str) -> None: 

27 super().__init__() 

28 self._output_filename = output_filename 

29 

30 def finish_report(self) -> None: 

31 # Nothing to do for now 

32 all_test_cases = list(self._as_test_cases()) 

33 test_suites = [ 

34 TestSuite( 

35 "debputy lint", 

36 test_cases=all_test_cases, 

37 timestamp=str(self.start_timestamp), 

38 ) 

39 ] 

40 with open(self._output_filename, "w", encoding="utf-8") as wfd: 

41 to_xml_report_file(wfd, test_suites, encoding="utf-8") 

42 _info(f"Wrote {self._output_filename}") 

43 

44 def _as_test_cases(self) -> Iterable["TestCase"]: 

45 for filename, duration in self.durations.items(): 

46 results = self.diagnostics_by_file.get(filename, []) 

47 yield self._as_test_case(filename, results, duration) 

48 

49 def _as_test_case( 

50 self, 

51 filename: str, 

52 diagnostic_results: Iterable[LintDiagnosticResult], 

53 duration: float, 

54 ) -> "TestCase": 

55 if not duration: 

56 duration = 0.000001 

57 case = TestCase( 

58 filename, 

59 # The JUnit schema has `classname` as mandatory 

60 classname=filename, 

61 allow_multiple_subelements=True, 

62 elapsed_sec=duration, 

63 ) 

64 for diagnostic_result in diagnostic_results: 

65 if diagnostic_result.result_state == LintDiagnosticResultState.FIXED: 

66 continue 

67 diagnostic = diagnostic_result.diagnostic 

68 severity = debputy_severity(diagnostic) 

69 if diagnostic_result.is_file_level_diagnostic: 

70 range_desc = "entire file" 

71 else: 

72 range_desc = str(diagnostic.range) 

73 code = f" [{diagnostic.code}]" if diagnostic.code else "" 

74 authority = diagnostic.source 

75 assert authority is not None 

76 output = textwrap.dedent( 

77 f"""\ 

78 {filename}{code} ({severity}) {range_desc} [{authority}]: {diagnostic.message} 

79 """ 

80 ) 

81 case.add_failure_info( 

82 message=f"[{severity}]: {diagnostic.message}", 

83 output=output, 

84 ) 

85 return case