Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[20411] Generate jUnit reports when CMake < 3.21 #56

Merged
merged 12 commits into from
Feb 13, 2024
Merged
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,16 +107,19 @@ For more information about versioning handle of this project, check following [f
- Install Fast DDS and general eProsima projects dependencies.

- [install_gtest](multiplatform/install_gtest/action.yml)
- Instal `gtest` and `gmock` C++ library.
- Install `gtest` and `gmock` C++ library.

- [install_openssl](multiplatform/install_openssl/action.yml)
- Instal `Open SSL` C++ library.
- Install `Open SSL` C++ library.

- [install_python_packages](ubuntu/install_python_packages/action.yml)
- Install python packages.

- [install_yamlcpp](multiplatform/install_yamlcpp/action.yml)
- Instal `yaml-cpp` C++ library.
- Install `yaml-cpp` C++ library.

- [junit_summary](multiplatform/junit_summary/action.yaml)
- Create a workflow summary with the results of the tests from jUnit reports.

- [tsan_build_test](multiplatform/tsan_build_test/action.yml)
- Build a project using colcon and TSAN flags and execute tests.
Expand All @@ -139,6 +142,9 @@ For more information about versioning handle of this project, check following [f
- [install_apt_packages](ubuntu/install_apt_packages/action.yml)
- Install apt packages.

- [junit_summary](ubuntu/junit_summary/action.yaml)
- Create a workflow summary with the results of the tests from jUnit reports.

- [set_platform](ubuntu/set_platform/action.yml)
- Set the platform OS version in a environment variable.

Expand Down
2 changes: 1 addition & 1 deletion external/upload-artifact/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ runs:
steps:

- name: Upload artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ inputs.name }}
path: ${{ inputs.path }}
Expand Down
4 changes: 2 additions & 2 deletions multiplatform/colcon_build/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,8 @@ runs:
colcon_build_args: ${{ inputs.colcon_build_args }}
colcon_build_args_default: ${{ inputs.colcon_build_args_default }}
cmake_args: ${{ inputs.cmake_args }}
# TODO (jparisu) find a way to use it for windows, as there is not a common Werror flag for both
cmake_args_default: ${{ inputs.cmake_args_default }}
workspace: ${{ inputs.workspace }}
workspace_dependencies: ${{ inputs.workspace_dependencies }}
cmake_build_type: ${{ inputs.cmake_build_type }}
# cmake_args_default: ${{ inputs.cmake_args_default }}
# TODO (jparisu) find a way to use it for windows, as there is not a common Werror flag for both
51 changes: 51 additions & 0 deletions multiplatform/junit_summary/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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

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@main
with:
junit_reports_dir: ${{ inputs.junit_reports_dir }}
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@main
with:
junit_reports_dir: ${{ inputs.junit_reports_dir }}
print_summary: ${{ inputs.print_summary }}
show_failed: ${{ inputs.show_failed }}
show_disabled: ${{ inputs.show_disabled }}
show_skipped: ${{ inputs.show_skipped }}
41 changes: 41 additions & 0 deletions resources/ctest-to-junit.xsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Taken from https://github.com/rpavlik/jenkins-ctest-plugin/blob/master/ctest-to-junit.xsl -->
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:template match="/">
<testsuites>
<xsl:variable name="buildName" select="//Site/@BuildName"/>
<xsl:variable name="numberOfTests" select="count(//Site/Testing/Test)"/>
<xsl:variable name="numberOfFailures" select="count(//Site/Testing/Test[@Status!='passed'])" />
<xsl:variable name="numberOfErrors" select="count(//Site/Testing/Test[@Status='error'])" />
<xsl:variable name="numberOfSkipped" select="count(//Site/Testing/Test[@Status='notrun'])"/>
<xsl:variable name="buildTime" select="(//Site/Testing/EndTestTime - //Site/Testing/StartTestTime)"/>
<testsuite name="CTest"
tests="{$numberOfTests}"
failures="{$numberOfFailures}"
errors="{$numberOfErrors}"
skipped="{$numberOfSkipped}"
time="{$buildTime}">
<xsl:for-each select="//Site/Testing/Test">
<xsl:variable name="testName" select="translate(Name, '-', '_')"/>
<xsl:variable name="duration" select="Results/NamedMeasurement[@name='Execution Time']/Value"/>
<xsl:variable name="status" select="@Status"/>
<xsl:variable name="output" select="Results/Measurement/Value"/>
<xsl:variable name="className" select="translate(Path, '/.', '.')"/>
<testcase classname="projectroot{$className}"
name="{$testName}"
time="{$duration}">
<xsl:if test="@Status!='passed'">
<failure>
<xsl:value-of select="$output" />
</failure>
</xsl:if>
<system-out>
<xsl:value-of select="$output" />
</system-out>
</testcase>
</xsl:for-each>
</testsuite>
</testsuites>
</xsl:template>
</xsl:stylesheet>
146 changes: 146 additions & 0 deletions resources/ctest2junit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# 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 transform CTest native XML reports to jUnit if they do not exist"""

import argparse

from lxml import etree
import os

DESCRIPTION = """Script to transform CTest native XML reports to jUnit"""
USAGE = ('python3 ctest2junit.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(
'-b',
'--build-dir',
type=str,
required=True,
help='Path to build directory'
)
required_args.add_argument(
'-x',
'--xslt',
type=str,
required=True,
help='Path to XSLT translation file.'
)
required_args.add_argument(
'-o',
'--output-junit',
type=str,
required=True,
help='Path to output jUnit file. If the file exists, the script takes no action'
)

return parser.parse_args()


def find_ctest_report(build_dir):
"""
Find the last ctest report in a given CMake build directory.

:param build_dir: The CMake build directory

:return: The path to latests CTest report on build_dir if any; else None
"""
ret = None
testing_dir = os.path.join(build_dir, 'Testing')

# Check if build_dir/Testing exists
if os.path.exists(testing_dir):
tag_file = os.path.join(testing_dir, 'TAG')

# CTest generates the reports if build_dir/Testing/{label}/Test.xml,
# where {label} is specified in the first line of a file build_dir/Testing/TAG
# Look for that build_dir/Testing/{label}/Test.xml.
if os.path.isfile(tag_file):
# Extract the {label} from the TAG file
with open(tag_file, 'r') as f:
tag_content = f.readline().strip()
test_dir = os.path.join(testing_dir, tag_content)

# Check that Test.xml exists
test_xml = os.path.join(test_dir, 'Test.xml')

if os.path.isfile(test_xml):
ret = test_xml

return ret


def translate(original_xml, xsl_file):
"""
Translate an XML from one spec to another using an XSLT file.

:param original_xml: The XML to translate
:param xsl_file: The XSLT transformation file

:return: A stream containing the translated XML
"""
xml = etree.parse(original_xml)
xslt = etree.parse(xsl_file)
transform = etree.XSLT(xslt)
try:
return str(transform(xml))
except Exception as e:
for error in transform.error_log:
print(error.message, error.line)
raise e


def write_to_file(stream, filename):
"""
Write a stream to a file.

:param stream: The stream to write
:param filename: The destination file
"""
with open(filename, 'w') as f:
f.write(stream)


if __name__ == '__main__':

args = parse_options()

exit_code = 0

if os.path.isfile(args.output_junit):
print(f'File {args.output_junit} already exists. No action taken.')

else:
exit_code = 1
ctest_report = find_ctest_report(args.build_dir)

if ctest_report:
junit = translate(ctest_report, args.xslt)
write_to_file(junit, args.output_junit)
exit_code = 0

exit(exit_code)
Loading