diff --git a/multiplatform/junit_summary/action.yaml b/multiplatform/junit_summary/action.yaml new file mode 100644 index 00000000..36910ea9 --- /dev/null +++ b/multiplatform/junit_summary/action.yaml @@ -0,0 +1,58 @@ +name: 'junit_summary' +description: 'Generate a summary from a jUnit report. This summary shows in the workflow summary page. Exit code is number of failed tests.' + +inputs: + junit_reports_dir: + description: 'Path to directory containing XML files with the jUnit reports' + required: true + + output_file: + description: 'Path to the file where the summary will be written' + required: false + default: ${{ vars.GITHUB_STEP_SUMMARY }} + + print_summary: + description: 'Whether to print the summary (Default: True)' + required: false + default: 'True' + + show_failed: + description: 'Whether to show the list of failed tests (Default: True)' + required: false + default: 'True' + + show_disabled: + description: 'Whether to show the list of disabled tests (Default: False)' + required: false + default: 'False' + + show_skipped: + description: 'Whether to show the list of skipped tests (Default: False)' + required: false + default: 'False' + +runs: + using: composite + steps: + + - name: Run in ubuntu + if: runner.os == 'Linux' + uses: eProsima/eProsima-CI/ubuntu/junit_summary@feature/ctest2junit_translation + with: + junit_reports_dir: ${{ inputs.junit_reports_dir }} + output_file: ${{ inputs.output_file }} + print_summary: ${{ inputs.print_summary }} + show_failed: ${{ inputs.show_failed }} + show_disabled: ${{ inputs.show_disabled }} + show_skipped: ${{ inputs.show_skipped }} + + - name: Run in windows + if: runner.os == 'Windows' + uses: eProsima/eProsima-CI/windows/junit_summary@feature/ctest2junit_translation + with: + junit_reports_dir: ${{ inputs.junit_reports_dir }} + output_file: ${{ inputs.output_file }} + print_summary: ${{ inputs.print_summary }} + show_failed: ${{ inputs.show_failed }} + show_disabled: ${{ inputs.show_disabled }} + show_skipped: ${{ inputs.show_skipped }} diff --git a/resources/junit_summary.py b/resources/junit_summary.py new file mode 100644 index 00000000..eef095b3 --- /dev/null +++ b/resources/junit_summary.py @@ -0,0 +1,180 @@ +# Copyright 2024 Proyectos y Sistemas de Mantenimiento SL (eProsima). +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Script to parse the jUnit test results and create a summary.""" + +import argparse +import xml.etree.ElementTree as ET + +DESCRIPTION = """Script to parse the jUnit test results and create a summary""" +USAGE = ('python3 junit_summary.py') + + +def parse_options(): + """ + Parse arguments. + :return: The arguments parsed. + """ + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + add_help=True, + description=(DESCRIPTION), + usage=(USAGE) + ) + + required_args = parser.add_argument_group('required arguments') + required_args.add_argument( + '-j', + '--junit-report', + type=str, + required=True, + help='Path to junit report file.' + ) + required_args.add_argument( + '-o', + '--output-file', + type=str, + required=True, + help='Path to output file.' + ) + parser.add_argument( + '-p', + '--print-summary', + action='store_true', + help='Print the summary to STDOUT' + ) + parser.add_argument( + '-f', + '--show-failed', + action='store_true', + help='Show a list of failed tests' + ) + parser.add_argument( + '-d', + '--show-disabled', + action='store_true', + help='Show a list of disabled tests' + ) + parser.add_argument( + '-s', + '--show-skipped', + action='store_true', + help='Show a list of skipped tests' + ) + + return parser.parse_args() + +def junit_report_to_dict(junit_report): + """Convert a jUnit report to a dictionary.""" + result = { + 'passed_tests': [], + 'failed_tests': [], + 'disabled_tests': [], + 'skipped_tests': [], + } + tree = ET.parse(junit_report) + root = tree.getroot() + result['tests'] = root.attrib['tests'] + result['failures'] = root.attrib['failures'] + result['disabled'] = root.attrib['disabled'] + result['skipped'] = root.attrib['skipped'] + result['time'] = root.attrib['time'] + result['timestamp'] = root.attrib['timestamp'] + + for child in root: + if child.tag == 'testcase': + if child.attrib['status'] == "run": + result['passed_tests'].append(child.attrib['name']) + + elif child.attrib['status'] == "fail": + result['failed_tests'].append(child.attrib['name']) + + elif child.attrib['status'] == "ignored": + result['disabled_tests'].append(child.attrib['name']) + + elif child.attrib['status'] == "notrun": + result['skipped_tests'].append(child.attrib['name']) + + return result + +def create_md_summary(results_dict, show_failed, show_disabled, show_skipped): + """Create Markdown summary from results.""" + # Test summary + summary = '## Test summary\n' + + # Table header + summary += '|Total number of tests|Test failures|Disabled test|Skipped test|Spent time|Timestamp|\n' + summary += '|-|-|-|-|-|-|\n' + + # Entry + summary += f'|{results_dict["tests"]}' + summary += f'|{results_dict["failures"]}' + summary += f'|{results_dict["disabled"]}' + summary += f'|{results_dict["skipped"]}' + summary += f'|{results_dict["time"]}' + summary += f'|{results_dict["timestamp"]}' + summary += '|\n' + + # Failed tests list + if show_failed is True and len(results_dict['failed_tests']) != 0: + summary += '\n## Failed tests\n' + for failed_test in results_dict['failed_tests']: + summary += f'* {failed_test}\n' + + # Disabled tests list + if show_disabled is True and len(results_dict['disabled_tests']) != 0: + summary += '\n## Disabled tests\n' + for failed_test in results_dict['disabled_tests']: + summary += f'* {failed_test}\n' + + # Skipped tests list + if show_skipped is True and len(results_dict['skipped_tests']) != 0: + summary += '\n## Skipped tests\n' + for failed_test in results_dict['skipped_tests']: + summary += f'* {failed_test}\n' + + return summary + + +if __name__ == '__main__': + # Parse arguments + args = parse_options() + results = junit_report_to_dict(args.junit_report) + + # Create summary + summary = create_md_summary( + results, + args.show_failed, + args.show_disabled, + args.show_skipped + ) + + # Print summary if required + if args.print_summary is True: + print(summary) + + # Write output if required + if args.output_file != '': + with open(args.output_file, 'a') as file: + file.write( + create_md_summary( + results, + args.show_failed, + args.show_disabled, + args.show_skipped + ) + ) + + # Exit code is the number of failed tests + exit(int(results['failures'])) diff --git a/ubuntu/junit_summary/action.yaml b/ubuntu/junit_summary/action.yaml new file mode 100644 index 00000000..26001287 --- /dev/null +++ b/ubuntu/junit_summary/action.yaml @@ -0,0 +1,85 @@ +name: 'junit_summary' +description: 'Generate a summary from a jUnit report. This summary shows in the workflow summary page. Exit code is number of failed tests.' + +inputs: + junit_reports_dir: + description: 'Path to directory containing XML files with the jUnit reports' + required: true + + output_file: + description: 'Path to the file where the summary will be written' + required: false + default: ${{ vars.GITHUB_STEP_SUMMARY }} + + print_summary: + description: 'Whether to print the summary (Default: True)' + required: false + default: 'True' + + show_failed: + description: 'Whether to show the list of failed tests (Default: True)' + required: false + default: 'True' + + show_disabled: + description: 'Whether to show the list of disabled tests (Default: False)' + required: false + default: 'False' + + show_skipped: + description: 'Whether to show the list of skipped tests (Default: False)' + required: false + default: 'False' + +runs: + using: composite + steps: + - name: jUnit Summary + id: junit_summary + shell: bash + run: | + OUTPUT_FILE_OPTION="" + if [[ ! -z "${{ inputs.output_file }}" ]] + then + OUTPUT_FILE_OPTION="--output-file ${{ inputs.output_file }}" + fi + + PRINT_SUMMARY_OPTION="" + if [[ "${{ inputs.print_summary }}" == "True" ]] + then + PRINT_SUMMARY_OPTION="--print-summary" + fi + + SHOW_FAILED_OPTION="" + if [[ "${{ inputs.show_failed }}" == "True" ]] + then + SHOW_FAILED_OPTION="--show-failed" + fi + + SHOW_DISABLED_OPTION="" + if [[ "${{ inputs.show_disabled }}" == "True" ]] + then + SHOW_DISABLED_OPTION="--show-disabled" + fi + + SHOW_SKIPPED_OPTION="" + if [[ "${{ inputs.show_skipped }}" == "True" ]] + then + SHOW_SKIPPED_OPTION="--show-skipped" + fi + + EXIT_CODE=0 + for JUNIT_REPORT in ${{ inputs.junit_reports_dir }}/*.xml + do + python3 ${{ github.action_path }}/../../junit_summary.py \ + --junit-report ${JUNIT_REPORT} \ + ${OUTPUT_FILE_OPTION} \ + ${PRINT_SUMMARY_OPTION} \ + ${SHOW_FAILED_OPTION} \ + ${SHOW_DISABLED_OPTION} \ + ${SHOW_SKIPPED_OPTION} + + EXIT_CODE=$((${EXIT_CODE} + $?)) + done + + exit ${EXIT_CODE} diff --git a/windows/junit_summary/action.yaml b/windows/junit_summary/action.yaml new file mode 100644 index 00000000..9593bd97 --- /dev/null +++ b/windows/junit_summary/action.yaml @@ -0,0 +1,79 @@ +name: 'junit_summary' +description: 'Generate a summary from a jUnit report. This summary shows in the workflow summary page. Exit code is number of failed tests.' + +inputs: + junit_reports_dir: + description: 'Path to directory containing XML files with the jUnit reports' + required: true + + output_file: + description: 'Path to the file where the summary will be written' + required: false + default: ${{ vars.GITHUB_STEP_SUMMARY }} + + print_summary: + description: 'Whether to print the summary (Default: True)' + required: false + default: 'True' + + show_failed: + description: 'Whether to show the list of failed tests (Default: True)' + required: false + default: 'True' + + show_disabled: + description: 'Whether to show the list of disabled tests (Default: False)' + required: false + default: 'False' + + show_skipped: + description: 'Whether to show the list of skipped tests (Default: False)' + required: false + default: 'False' + +runs: + using: composite + steps: + - name: jUnit Summary + id: junit_summary + shell: pwsh + run: | + $OUTPUT_FILE_OPTION = "" + if (-not [string]::IsNullOrEmpty(${{ inputs.output_file }})) { + $OUTPUT_FILE_OPTION = "--output-file ${{ inputs.output_file }}" + } + + $PRINT_SUMMARY_OPTION = "" + if ($env:INPUT_PRINT_SUMMARY -eq "True") { + $PRINT_SUMMARY_OPTION = "--print-summary" + } + + $SHOW_FAILED_OPTION = "" + if ($env:INPUT_SHOW_FAILED -eq "True") { + $SHOW_FAILED_OPTION = "--show-failed" + } + + $SHOW_DISABLED_OPTION = "" + if ($env:INPUT_SHOW_DISABLED -eq "True") { + $SHOW_DISABLED_OPTION = "--show-disabled" + } + + $SHOW_SKIPPED_OPTION = "" + if ($env:INPUT_SHOW_SKIPPED -eq "True") { + $SHOW_SKIPPED_OPTION = "--show-skipped" + } + + $EXIT_CODE=0 + foreach ($JUNIT_REPORT in Get-ChildItem -Path ${{ inputs.junit_report }} -Filter "*.xml") { + python3 ${{ github.action_path }}\..\..\junit_summary.py ` + --junit-report $JUNIT_REPORT.FullName ` + $OUTPUT_FILE_OPTION ` + $PRINT_SUMMARY_OPTION ` + $SHOW_FAILED_OPTION ` + $SHOW_DISABLED_OPTION ` + $SHOW_SKIPPED_OPTION + + $EXIT_CODE += $LASTEXITCODE + } + + exit $EXIT_CODE