Skip to content

Commit

Permalink
feat: Support RobotFramework xUnit files
Browse files Browse the repository at this point in the history
Support for junit files that does not
have tessuites element as root element.
Example RobotFramework xunit files
  • Loading branch information
jannek76 authored and Tatu Aalto committed Dec 15, 2022
1 parent 42a5222 commit cf3d58a
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 26 deletions.
68 changes: 42 additions & 26 deletions flaky_tests_detection/check_flakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from pathlib import Path
from typing import Dict, Set

from junitparser import JUnitXml
from junitparser import JUnitXml, TestSuite
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
Expand Down Expand Up @@ -136,37 +136,53 @@ def generate_image(image: pd.DataFrame, title: str, filename: str) -> None:
plt.close()


def parse_junit_suite_to_df(suite: TestSuite) -> list:
"""Parses Junit TestSuite results to a test history dataframe"""
dataframe_entries = []
time = suite.timestamp

for testcase in suite:
test_identifier = testcase.classname + "::" + testcase.name

# junitparser has "failure", "skipped" or "error" in result list if any
if not testcase.result:
test_status = "pass"
else:
test_status = testcase.result[0]._tag
if test_status == "skipped":
continue

dataframe_entries.append(
{
"timestamp": time,
"test_identifier": test_identifier,
"test_status": test_status,
}
)
return dataframe_entries


def parse_junit_to_df(folderpath: Path) -> pd.DataFrame:
"""Read JUnit test result files to a test history dataframe"""
dataframe_entries = []

for filepath in folderpath.glob("*.xml"):
xml = JUnitXml.fromfile(filepath)
for suite in xml:
time = suite.timestamp
for testcase in suite:
test_identifier = testcase.classname + "::" + testcase.name

# junitparser has "failure", "skipped" or "error" in result list if any
if not testcase.result:
test_status = "pass"
else:
test_status = testcase.result[0]._tag
if test_status == "skipped":
continue

dataframe_entries.append(
{
"timestamp": time,
"test_identifier": test_identifier,
"test_status": test_status,
}
)

df = pd.DataFrame(dataframe_entries)
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
return df
if isinstance(xml, JUnitXml):
for suite in xml:
dataframe_entries += parse_junit_suite_to_df(suite)
elif isinstance(xml, TestSuite):
dataframe_entries += parse_junit_suite_to_df(xml)
else:
raise TypeError(f"not known suite type in {filepath}")

if dataframe_entries:
df = pd.DataFrame(dataframe_entries)
df["timestamp"] = pd.to_datetime(df["timestamp"])
df = df.set_index("timestamp")
return df
else:
raise RuntimeError(f"No Junit files found from path {folderpath}")


def create_heat_map(
Expand Down
16 changes: 16 additions & 0 deletions tests/junit_rf4/xunit_rf4.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuite name="Test" tests="5" errors="0" failures="3" skipped="0" time="0.086">
<testcase classname="Test" name="Testi Allways Passed" time="0.001">
</testcase>
<testcase classname="Test" name="test failing always" time="0.003">
<failure message="AssertionError" type="AssertionError"/>
</testcase>
<testcase classname="Test" name="test random 10percent fail" time="0.002">
<failure message="AssertionError" type="AssertionError"/>
</testcase>
<testcase classname="Test" name="test random 50percent fail" time="0.001">
<failure message="AssertionError" type="AssertionError"/>
</testcase>
<testcase classname="Test" name="test random 25percent fail" time="0.001">
</testcase>
</testsuite>
34 changes: 34 additions & 0 deletions tests/test_check_flakes.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,40 @@ def test_skipped():
assert result_value != skipped


def test_parse_junit_to_df_without_testsuites(tmpdir: LocalPath):
"""Test junit file parsing to test history dataframe.
Junit report doesn't have a testsuites element.
"""
original_path = os.getcwd()
test_junit_path = os.path.join(original_path, "tests/junit_rf4")
result_df = parse_junit_to_df(Path(test_junit_path))

assert list(result_df.columns) == ["test_identifier", "test_status"]
assert result_df.index.name == "timestamp"

expected_values = [
("Test::Testi Allways Passed", "pass"),
("Test::test failing always", "failure"),
("Test::test random 10percent fail", "failure"),
("Test::test random 50percent fail", "failure"),
("Test::test random 25percent fail", "pass"),
]

for result_value in result_df.itertuples(index=False, name=None):
assert result_value in expected_values


def test_parse_junit_to_df_empty_dir(testdir: Testdir):
"""Test junit file parsing to test history dataframe
No Unit files in given directory
"""
test_junit_path = testdir.mkdir("empty_junit_dir")
with pytest.raises(RuntimeError) as excinfo:
result_df = parse_junit_to_df(Path(str(test_junit_path)))

assert "No Junit files found from path" in str(excinfo.value)


def test_full_usage_day_grouping(tmpdir: LocalPath):
original_path = os.getcwd()
test_history_path = os.path.join(tmpdir, "test_history.csv")
Expand Down

0 comments on commit cf3d58a

Please sign in to comment.