Unverified Commit be05ed6b authored by Dom Sekotill's avatar Dom Sekotill
Browse files

Update and move JUnit merge script

parent 134d711a
Loading
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -63,7 +63,7 @@ Publish Unit Tests:
  script:
  - pip install --upgrade junit2html
  - mkdir -p unittest
  - python util/junit_merge.py results.*/junit.xml > junit.xml
  - python .gitlab/ci/junit_merge.py results.*/junit.xml > junit.xml
  - junit2html junit.xml unittest/index.html
  artifacts:
    when: always
+107 −0
Original line number Diff line number Diff line
#
#   Copyright 2018 Dominik Sekotill <dom.sekotill@kodo.org.uk>
#   Copyright 2018, 2026 Dominik Sekotill <dom.sekotill@kodo.org.uk>
#
#   Licensed under the Apache License, Version 2.0 (the "License");
#   you may not use this file except in compliance with the License.
@@ -18,16 +18,20 @@ Simple jUnit XML merger
"""

import sys
from collections.abc import Iterable
from xml.etree import ElementTree as etree  # ElementTree is a stupid module name

SUITE_METRICS = 'tests', 'failures', 'errors', 'skipped'
type Element = etree.Element[str]
type ElementTree = etree.ElementTree[Element]

SUITE_METRICS = "tests", "failures", "errors", "skipped"

def merge(testsuites, tree, name_format=None):

def merge(testsuites: Element, tree: ElementTree, name_format: str | None = None) -> None:
	"""
	Merge a jUnit tree into a 'testsuites' element
	"""
	assert testsuites.tag == 'testsuites'
	assert testsuites.tag == "testsuites"
	total_metrics = get_suite_metrics(testsuites)

	for test_suite in get_suites(tree):
@@ -36,32 +40,32 @@ def merge(testsuites, tree, name_format=None):
		for name in metrics:
			total_metrics[name] += metrics[name]
		if name_format:
			test_suite.set('name', name_format.format(test_suite.get('name')))
			test_suite.set("name", name_format.format(test_suite.get("name")))

	for name, value in total_metrics.items():
		testsuites.set(name, str(value))


def get_suite_metrics(suite):
def get_suite_metrics(suite: Element) -> dict[str, int | float]:
	"""
	Return a dict of the metric attributes of a test suite
	"""
	metrics = {name: int(suite.get(name, 0)) for name in SUITE_METRICS}
	metrics['time'] = float(suite.get('time', 0.0))
	metrics: dict[str, int | float] = {name: int(suite.get(name, 0)) for name in SUITE_METRICS}
	metrics["time"] = float(suite.get("time", 0.0))

	if metrics['tests']:
	if metrics["tests"]:
		return metrics

	# No metric attributes: make some
	for testcase in suite.iter('testcase'):
		metrics['time'] += float(testcase.get('time', 0.0))
		metrics['tests'] += 1
		if testcase.find('failure'):
			metrics['failures'] += 1
		elif testcase.find('error'):
			metrics['errors'] += 1
		elif testcase.find('skipped'):
			metrics['skipped'] += 1
	for testcase in suite.iter("testcase"):
		metrics["time"] += float(testcase.get("time", 0.0))
		metrics["tests"] += 1
		if testcase.find("failure"):
			metrics["failures"] += 1
		elif testcase.find("error"):
			metrics["errors"] += 1
		elif testcase.find("skipped"):
			metrics["skipped"] += 1

	for name, value in metrics.items():
		suite.set(name, str(value))
@@ -69,33 +73,35 @@ def get_suite_metrics(suite):
	return metrics


def get_suites(tree):
def get_suites(tree: ElementTree) -> Iterable[Element]:
	"""
	Get all the testsuites as a list
	"""
	root = tree.find('.')
	if root.tag == 'testsuite':
	root = tree.find(".")
	assert root is not None
	if root.tag == "testsuite":
		return [root]
	return root.find('.//testsuite')
	suites = root.find(".//testsuite")
	assert suites is not None
	return suites


def main():
def main() -> None:
	"""
	Main CLI entrypoint function
	Merge each JUnit results file on the command line and output the result to stdout
	"""
	testsuites = etree.Element('testsuites')
	testsuites.text = '\n'  # .text & .tail are a deeply brain-dead design
	testsuites = etree.Element("testsuites")
	testsuites.text = "\n"  # .text & .tail are a deeply brain-dead design
	for arg in sys.argv[1:]:
		filename, *name_prefix = arg.split(':', 1)
		name_fmt = '{0[0]}: ({{}})'.format(name_prefix) if name_prefix else None
		filename, *name_prefix = arg.split(":", 1)
		name_fmt = f"{name_prefix[0]}: ({{}})" if name_prefix else None
		tree = etree.parse(filename)
		merge(testsuites, tree, name_format=name_fmt)
		tree.getroot().tail = '\n'
		tree.getroot().tail = "\n"

	with open(1, 'wb') as stdout:
		etree.ElementTree(testsuites) \
		.write(stdout, encoding='UTF-8', xml_declaration=True)
	with open(1, "wb") as stdout:
		etree.ElementTree(testsuites).write(stdout, encoding="UTF-8", xml_declaration=True)


if __name__ == '__main__':
if __name__ == "__main__":
	main()