Source code for run_aster.ctest2junit

# coding=utf-8
# --------------------------------------------------------------------
# Copyright (C) 1991 - 2026 - EDF - www.code-aster.org
# This file is part of code_aster.
#
# code_aster is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# code_aster is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with code_aster.  If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------

"""
:py:mod:`ctest2junit` --- Convert a CTest execution report to JUnit
-------------------------------------------------------------------

This module converts a report from CTest to JUnit format.

This creates a XML file that can be presented by *Continous Integration*
tools.
"""

import os
import os.path as osp
import re

from . import junit_xml_output as JUNIT
from .config import CFG
from .error_messages import search_msg

MESS_EXT = os.environ.get("MESS_EXT", "mess")


[docs]class XUnitReport: """Represents a report to be exported at the xUnit format. Arguments: resudir (str): Directory containing the result files. """
[docs] def __init__(self, resudir, legend=""): self.base = resudir self.legend = legend self.junit_test = []
[docs] def read_ctest(self, resudir=None): """Read the CTest report. It reads the list of testcases that passed and that failed from the ``CTestCostData.txt`` file and extracts detailed informations (error messages, OK/NOOK results) from ``.mess`` files. ``LastTest.log`` is also parsed to get the elapsed time of testcases in failure. """ resudir = resudir or self.base cost = osp.join(resudir, "Testing", "Temporary", "CTestCostData.txt") lastlog = osp.join(resudir, "Testing", "Temporary", "LastTest.log") if not osp.isfile(cost): return re_test_time = re.compile( r"^(?P<ctname>ASTER_[0-9\.]+_(?P<name>\S+)) +" r"(?P<ok>[0-9]+) +" r"(?P<time>[0-9\.,]+)", re.M, ) re_test = re.compile(r"^(\S+)", re.M) with open(cost, "r") as fcost: text = fcost.read() passed, failed = text.split("---") iternames = re_test_time.finditer(passed) failures = re_test.findall(failed) with open(lastlog, "rb") as flog: log = flog.read().decode(errors="replace") re_elaps = re.compile( r".(?P<ctname>ASTER_[0-9\.]+_(?P<name>\S+)). +" r"time elapsed: +(?P<time>[0-9]+:[0-9]+:[0-9]+)" ) timedict = {} for mat in re_elaps.finditer(log): hours, mins, secs = mat.group("time").split(":") secs = int(hours) * 3600 + int(mins) * 60 + int(secs) timedict[mat.group("name")] = secs for mat in iternames: ctname = mat.group("ctname") testname = mat.group("name") jstate = "failure" if ctname in failures else "" mess = osp.join(resudir, testname + "." + MESS_EXT) if osp.isfile(mess): with open(mess, "rb") as fmess: output = fmess.read().decode(errors="replace") state = get_state(output) if "NOT_RUN" in state: jstate = "skipped" details = [] if jstate: details.append(get_nook(output)) details.append(get_err_msg(output)) content = "\n".join(details) else: output = "" state = f"not found: {mess}" content = "" time = float(mat.group("time").replace(",", ".")) self.junit_test.append( JUNIT.TestCase(ctname, content, state, jstate, time or timedict.get(testname, 0.0)) )
[docs] def write_xml(self, filename): """Export the report in XML. Arguments: filename (str): Output XML file (relative to the base directory). """ junit = JUNIT.JunitXml( "code_aster " + CFG.get("version_tag", "?") + "-" + CFG.get("version_sha1", "?")[:12] + self.legend, self.junit_test, ) with open(osp.join(self.base, filename), "wb") as fobj: dump = junit.dump() if isinstance(dump, bytes): dump = dump.decode("utf-8", errors="replace") fobj.write(dump.encode())
RE_STATE = re.compile("DIAGNOSTIC JOB : (.*)", re.M)
[docs]def get_state(txt): """Extract the test state for a 'message' file content. Arguments: txt (str): '.mess' file content. Returns: str: Error messages. """ state = RE_STATE.findall(txt) if not state: return "?" return state[-1]
[docs]def get_err_msg(txt): """Extract the error message for a 'message' file content. Arguments: txt (str): '.mess' file content. Returns: str: Error messages. """ warn = re.compile("^ *<(?:INFO|I|A)>") dmsg = search_msg(txt, maxlines=1000) errors = [] for lmsg in dmsg.values(): errors.extend([msg[1] for msg in lmsg if not warn.search(msg[1])]) return os.linesep.join(errors)
[docs]def get_nook(txt): """Extract NOOK values from a 'message' file content. Arguments: txt (str): '.mess' file content. Returns: str: Text of OK/NOOK values. """ reg_resu = re.compile("^( *(?:REFERENCE|OK|NOOK) .*$)", re.M) lines = reg_resu.findall(txt) return os.linesep.join(lines)
def _dbg(): report = XUnitReport(".") report.read_ctest() junit = JUNIT.JunitXml("code_aster", report.junit_test) print(junit.dump())