Skip to content

Commit 7983049

Browse files
author
Joris Conijn
authored
feat: return non 0 exit code when there are failures (#28)
* feat: return non 0 exit code when there are failures When failures are reported in the destination report, you have two options: - We should raise an exit code, this allows you to act on the error. - We ignore it and return a 0 exit code, this allows us to ignore the results and continue as usual.
1 parent 5c3b170 commit 7983049

File tree

5 files changed

+67
-18
lines changed

5 files changed

+67
-18
lines changed

Makefile

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ clean:
2222
[[ -f .coverage ]] && rm .coverage || true
2323

2424
run:
25-
report2junit --source-type cfn-guard ./sample-reports/cfn-guard.json
26-
report2junit --source-type cfn-nag ./sample-reports/cfn-nag.json
25+
report2junit --ignore-failures ./sample-reports/cfn-guard.json ./sample-reports/cfn-nag.json
2726

2827
build:
2928
python setup.py sdist

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,17 @@ report2junit ./sample-reports/cfn-nag.json ./sample-reports/cfn-guard.json
5757
# Or if you want to specify the destination:
5858
report2junit ./sample-reports/cfn-nag.json ./sample-reports/cfn-guard.json --destination-file ./sample-reports/junit-other.xml
5959
```
60+
61+
In some cases it is useful to explicitly stop when there are failures. For example when you want to enforce that there
62+
are no failures. Or on the other hand we could continue when there are failures. This behaviour can be influenced using
63+
the `--ignore-failures` and `--fail-on-failures` options. Where `--fail-on-failures` is the default.
64+
65+
```bash
66+
# Convert the given report and when there are failures exit code 1 is returned.
67+
report2junit ./sample-reports/cfn-guard.json --fail-on-failures
68+
echo $?
69+
70+
# Convert the given report and when there are failures exit code 0 is returned.
71+
report2junit ./sample-reports/cfn-guard.json --ignore-failures
72+
echo $?
73+
```

report2junit/__init__.py

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional
1+
from typing import Optional, List
22
import os.path
33
import click
44

@@ -10,27 +10,40 @@
1010
@click.command()
1111
@click.argument("source-files", nargs=-1)
1212
@click.option("--destination-file", required=False)
13+
@click.option("--fail-on-failures/--ignore-failures", default=True)
1314
def main(
14-
source_files: str,
15-
destination_file: Optional[str],
16-
):
15+
source_files: List[str], destination_file: Optional[str], fail_on_failures: bool
16+
) -> None:
1717
"""
1818
Convert2JUnit
1919
2020
This tool allows you to convert various reports into the JUnit format.
2121
"""
22-
if not destination_file:
23-
destination_file = os.path.join(os.path.dirname(source_files[0]), "junit.xml")
22+
report = initialize_report(destination_file, source_files)
23+
apply_source_files(report, source_files)
24+
report.write()
2425

25-
report = JUnitOutput(destination_file)
26+
if fail_on_failures and report.has_failures():
27+
raise click.ClickException(
28+
"report2junit detected that the report contains failures"
29+
)
2630

31+
32+
def apply_source_files(report: JUnitOutput, source_files: List[str]) -> None:
2733
for source_file in source_files:
2834
source_file = os.path.abspath(source_file)
2935

3036
if not report.apply(source_file):
3137
raise click.ClickException("Could not convert the report")
3238

33-
report.write()
39+
40+
def initialize_report(
41+
destination_file: Optional[str], source_files: List[str]
42+
) -> JUnitOutput:
43+
if not destination_file:
44+
destination_file = os.path.join(os.path.dirname(source_files[0]), "junit.xml")
45+
46+
return JUnitOutput(destination_file)
3447

3548

3649
if __name__ == "__main__":

report2junit/junit.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33

44
from typing import List
5-
from junit_xml import to_xml_report_string, TestSuite
5+
from junit_xml import to_xml_report_string, TestSuite, TestCase
66

77
from report2junit.reports import Report
88

99

1010
class JUnitOutput:
11+
__destination: str
12+
__reports: List[TestSuite]
13+
1114
def __init__(self, destination: str):
1215
self.__destination = destination
1316
self.__reports = self.__load_existing()
@@ -24,6 +27,15 @@ def apply(self, source: str) -> bool:
2427
def add_test_suite(self, report: TestSuite) -> None:
2528
self.__reports.append(report)
2629

30+
def has_failures(self) -> bool:
31+
def report_has_failures(report: TestSuite) -> bool:
32+
return any(map(case_has_failures, report.test_cases))
33+
34+
def case_has_failures(case: TestCase) -> bool:
35+
return len(case.failures) > 0
36+
37+
return any(map(report_has_failures, self.__reports))
38+
2739
def write(self) -> None:
2840
if len(self.__reports) == 0:
2941
raise Exception("No reports found to write")

tests/test_cli.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,32 @@
77

88

99
@pytest.mark.parametrize(
10-
"input_file",
10+
"fail_on_failures, input_file, expected_exit_code",
1111
[
12-
"cfn-guard.json",
13-
"cfn-guard-fully-compliant.json",
14-
"cfn-nag.json",
15-
"cfn-guard-skipped-only.json",
12+
(False, "cfn-guard.json", 0),
13+
(True, "cfn-guard.json", 1),
14+
(False, "cfn-guard-fully-compliant.json", 0),
15+
(True, "cfn-guard-fully-compliant.json", 0),
16+
(False, "cfn-nag.json", 0),
17+
(True, "cfn-nag.json", 1),
18+
(False, "cfn-guard-skipped-only.json", 0),
19+
(True, "cfn-guard-skipped-only.json", 0),
1620
],
1721
)
18-
def test_single_report_conversion(input_file: str, sample_reports_path: str) -> None:
22+
def test_single_report_conversion(
23+
fail_on_failures: bool,
24+
input_file: str,
25+
expected_exit_code: int,
26+
sample_reports_path: str,
27+
) -> None:
1928
runner = CliRunner()
2029
m = mock_open()
2130

2231
with patch("report2junit.junit.open", m):
2332
result = runner.invoke(
2433
main,
2534
[
35+
"--fail-on-failures" if fail_on_failures else "--ignore-failures",
2636
f"{sample_reports_path}/{input_file}",
2737
],
2838
)
@@ -32,7 +42,7 @@ def test_single_report_conversion(input_file: str, sample_reports_path: str) ->
3242
handle.write.assert_called_once_with(
3343
expected_payload(input_file.replace(".json", ".xml"))
3444
)
35-
assert result.exit_code == 0
45+
assert result.exit_code == expected_exit_code
3646

3747

3848
def test_cfn_guard_conversion_explicit_destination(sample_reports_path: str) -> None:
@@ -43,6 +53,7 @@ def test_cfn_guard_conversion_explicit_destination(sample_reports_path: str) ->
4353
result = runner.invoke(
4454
main,
4555
[
56+
"--ignore-failures",
4657
f"{sample_reports_path}/cfn-guard.json",
4758
"--destination-file",
4859
f"{sample_reports_path}/cfn-guard-specific.xml",

0 commit comments

Comments
 (0)