Skip to content

Commit

Permalink
Add missing Junit fields
Browse files Browse the repository at this point in the history
- 'software' section
- 'hardware' section
- 'timestamp' for testcases
- 'hostname' for testsuite
- 'id' for testsuite
- 'package' for testsuite
  • Loading branch information
denisbrykovpartner authored and aigarius committed Sep 26, 2023
1 parent b4851c5 commit eb290b6
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 22 deletions.
4 changes: 2 additions & 2 deletions src/dltlyse/core/analyser.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (C) 2022. BMW Car IT GmbH. All rights reserved.
# Copyright (C) 2022-23. BMW Car IT GmbH. All rights reserved.
"""DLT file analyser"""

from contextlib import contextmanager
Expand Down Expand Up @@ -428,7 +428,7 @@ def run_analyse(self, traces, xunit, no_sort, is_live, testsuite_name="dltlyse")
# for the method (e.g. avoid to access attribute with dots, function
# inlining, loop unrolling, ...). The inner most loop is called over
# 10 million times when the input file is large. Any small/tiny change
# could causes performance pentlty.
# could causes performance penalty.

filters = self.get_filters()
# add filter for lifecycle start message in case it is missing
Expand Down
46 changes: 35 additions & 11 deletions src/dltlyse/core/report.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
# Copyright (C) 2022. BMW Car IT GmbH. All rights reserved.
# Copyright (C) 2022-23. BMW Car IT GmbH. All rights reserved.
"""Reporting for dltlyse"""
from collections import Counter
import datetime as dt
import logging
import socket
import xml.etree.ElementTree as etree


ATTACHMENT_TEMPLATE = "[[ATTACHMENT|{filename}]]"

TEST_CASE_RESULT_TYPE = {
Expand Down Expand Up @@ -67,6 +70,7 @@ def __init__(
message="",
metadata=None,
attach=None,
timestamp=None,
):
self.classname = classname
self.testname = testname
Expand All @@ -79,6 +83,7 @@ def __init__(
self.attach = attach

self.metadata = Metadata(metadata)
self.timestamp = str(dt.datetime.now(dt.timezone.utc)) if timestamp is None else str(timestamp)

def __repr__(self):
return repr(self.__dict__)
Expand All @@ -99,7 +104,9 @@ def render_xml(self):
self.state = "error"

# Prepare test case root
root = etree.Element("testcase", classname="dltlyse." + self.classname, name=self.testname, time="0")
root = etree.Element(
"testcase", classname="dltlyse." + self.classname, name=self.testname, time="0", timestamp=self.timestamp
)

# Set attachment
root.text = "".join(ATTACHMENT_TEMPLATE.format(filename=filename) for filename in self.attach)
Expand All @@ -123,10 +130,17 @@ def render_xml(self):
class XUnitReport(object):
"""Template class producing report in xUnit format"""

def __init__(self, outfile="", testsuite_name="dltlyse"):
def __init__(
self, outfile="", testsuite_name="dltlyse", hardware=None, software=None, hostname=None, id_=None, package=None
):
self.results = []
self.outfile = outfile
self.testsuite_name = testsuite_name
self.hardware = hardware
self.software = software
self.hostname = socket.gethostname() if hostname is None else hostname
self.id = id_
self.package = package

def add_results(self, results):
"""Adds a result to the report"""
Expand All @@ -145,15 +159,25 @@ def _generate_summary(self):
def render_xml(self):
"""Return a xml element to present report"""
summary = self._generate_summary()
root_attributes = {
"name": self.testsuite_name,
"tests": summary["number_of_tests"],
"errors": summary["number_of_errors"],
"failures": summary["number_of_failures"],
"skip": summary["number_of_skipped"],
"hostname": self.hostname,
}
if self.id:
root_attributes["id"] = str(self.id)
if self.package:
root_attributes["package"] = str(self.package)

root = etree.Element(
"testsuite",
name=self.testsuite_name,
tests=summary["number_of_tests"],
errors=summary["number_of_errors"],
failures=summary["number_of_failures"],
skip=summary["number_of_skipped"],
)
root = etree.Element("testsuite", **root_attributes)

if self.hardware and isinstance(self.hardware, dict):
etree.SubElement(root, "hardware", self.hardware)
if self.software and isinstance(self.software, dict):
etree.SubElement(root, "software", self.software)

result_elements = []
for result in self.results:
Expand Down
64 changes: 55 additions & 9 deletions tests/unittests/test_plugin_report.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Copyright (C) 2022. BMW Car IT GmbH. All rights reserved.
# Copyright (C) 2022-23. BMW Car IT GmbH. All rights reserved.
"""Test plugin_metadata decorator and xunit report functions"""
import xml.etree.ElementTree as etree
import datetime as dt
import inspect
import socket
from unittest.mock import patch, mock_open
import xml.etree.ElementTree as etree

from dltlyse.core.plugin_base import Plugin, plugin_metadata
from dltlyse.core.report import logger, Metadata, Result, XUnitReport
Expand Down Expand Up @@ -79,22 +81,25 @@ def generate_test_result(attach=None, extra=""):
"""Prepare test result data and xml string"""
attach = attach or []

timestamp = dt.datetime.now()

result = Result(
classname="TestPlugin",
testname="TestPlugin-shot-description",
state="success",
stdout="TestPlugin-stdoutput",
message="TestPlugin-success-message",
attach=attach,
timestamp=timestamp,
)

xml_str = (
'<testcase classname="dltlyse.TestPlugin" name="TestPlugin-shot-description" time="0">'
'<testcase classname="dltlyse.TestPlugin" name="TestPlugin-shot-description" time="0" timestamp="{}">'
"{}"
"<system-out>TestPlugin-stdoutput</system-out>"
"{}"
"</testcase>"
).format("".join("[[ATTACHMENT|{}]]".format(filename) for filename in attach), extra)
).format(timestamp, "".join("[[ATTACHMENT|{}]]".format(filename) for filename in attach), extra)

return result, xml_str

Expand Down Expand Up @@ -199,14 +204,16 @@ def test_metadata_render_recursive():


def test_result_equal():
result = Result()
other = Result(metadata={"key": "should-not-have-effect"})
timestamp = str(dt.datetime.now())
result = Result(timestamp=timestamp)
other = Result(metadata={"key": "should-not-have-effect"}, timestamp=timestamp)

assert result == other


def test_result_render_xml_error_state(): # pylint: disable=invalid-name
"""Test the warning message when the test state is undefined."""

result = Result(classname="noclass", state="nostate")

with patch.object(logger, "warning") as logger_mock:
Expand All @@ -220,23 +227,26 @@ def test_result_render_xml_fail():
"""Tests that result is rendered when the state is error."""
state = "error"
state_type = "error"
timestamp = str(dt.datetime.now())

result = Result(
classname="TestPlugin",
testname="TestPlugin-shot-description",
state=state,
stdout="TestPlugin-stdoutput",
message="TestPlugin-{}-message".format(state),
timestamp=timestamp,
)

assert equal_xml_tree(
result.render_xml(),
(
'<testcase classname="dltlyse.TestPlugin" name="TestPlugin-shot-description" time="0">'
'<testcase classname="dltlyse.TestPlugin" name="TestPlugin-shot-description" \
time="0" timestamp="{timestamp}">'
'<{state} message="TestPlugin-{state}-message" type="{state_type}"/>'
"<system-out>TestPlugin-stdoutput</system-out>"
"</testcase>"
).format(state=state, state_type=state_type),
).format(timestamp=timestamp, state=state, state_type=state_type),
)


Expand Down Expand Up @@ -291,7 +301,8 @@ def test_xunit_report_render_xml():
with patch("dltlyse.core.report.Result.render_xml", return_value=etree.Element("testcase")):
assert equal_xml_tree(
xunit_report.render_xml(),
'<testsuite errors="0" failures="0" name="dltlyse" skip="0" tests="1"><testcase/></testsuite>',
f'<testsuite errors="0" failures="0" name="dltlyse" skip="0" tests="1" \
hostname="{socket.gethostname()}"><testcase/></testsuite>',
)


Expand All @@ -318,3 +329,38 @@ def test_xunit_report_render():

write_xml = "".join(args[0].decode() for args, _ in mocked_file().write.call_args_list)
assert write_xml == "<?xml version='1.0' encoding='UTF-8'?>\n<testsuite />"


def test_xunit_report_check_software_hardware():
"""Tests that xunit report contains software and hardware sections."""
xunit_report = XUnitReport(
software={"OS": "Windows NT 3.5 Daytona"}, hardware={"CPU": "Intel 486DX2-66", "RAM": "8Mb"}
)
xunit_report.outfile = "mocked-file"

with patch("dltlyse.core.report.open", mock_open()) as mocked_file:
xunit_report.render()

write_xml = "".join(args[0].decode() for args, _ in mocked_file().write.call_args_list)
print(write_xml)
check_params = (
'<hardware CPU="Intel 486DX2-66" RAM="8Mb" />',
'<software OS="Windows NT 3.5 Daytona" />',
)
for param in check_params:
assert param in write_xml


def test_xunit_report_check_testsuite_params():
"""Tests that xunit report contains additional testsuite params."""
xunit_report = XUnitReport(package="package.gz", id_="some_strange_id", hostname="test1")
xunit_report.outfile = "mocked-file"

with patch("dltlyse.core.report.open", mock_open()) as mocked_file:
xunit_report.render()

write_xml = "".join(args[0].decode() for args, _ in mocked_file().write.call_args_list)

check_params = ('package="package.gz"', 'id="some_strange_id"', 'hostname="test1"')
for param in check_params:
assert param in write_xml

0 comments on commit eb290b6

Please sign in to comment.