Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions mergify_cli/ci/junit_processing/junit.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,17 +194,19 @@ async def junit_to_spans(

for testcase in testsuite.findall("testcase"):
classname = testcase.get("classname")
test_case_name = testcase.get("name", "unnamed test")
if classname is not None:
test_name = classname + "." + testcase.get("name", "unnamed test")
test_name = classname + "." + test_case_name
else:
test_name = testcase.get("name", "unnamed test")
test_name = test_case_name
start_time = now - int(float(testcase.get("time", 0)) * 10e9)
min_start_time = min(min_start_time, start_time)

attributes: dict[str, str | bool] = {
"test.scope": "case",
"test.case.name": test_name,
"code.function.name": test_name,
"test.suite.name": classname if classname is not None else "",
"test.case.name": test_case_name,
"code.function.name": test_case_name,
"cicd.test.quarantined": False,
}

Expand Down
32 changes: 30 additions & 2 deletions mergify_cli/ci/junit_processing/quarantine.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ class QuarantineFailedError(Exception):
)


def generate_test_report(test_class: str, test_name: str) -> None:
click.echo(" · Test:")
click.echo(
f" Class: {test_class}",
)
click.echo(
f" Name: {test_name}",
)


async def check_and_update_failing_spans(
api_url: str,
token: str,
Expand Down Expand Up @@ -98,12 +108,30 @@ async def check_and_update_failing_spans(
if quarantined_tests_spans:
click.echo(" - 🔒 Quarantined:")
for qt_span in quarantined_tests_spans:
click.echo(f" · {qt_span.name}")
if qt_span.attributes is not None:
generate_test_report(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should pass directly qt_span.attributes and handle the if None and cast there to avoid repeating the code.

test_class=typing.cast(
"str",
qt_span.attributes["test.suite.name"],
),
test_name=typing.cast("str", qt_span.attributes["test.case.name"]),
)
else:
click.echo(f" · {qt_span.name}")

if non_quarantined_tests_spans:
click.echo(" - ❌ Unquarantined:")
for nqt_span in non_quarantined_tests_spans:
click.echo(f" · {nqt_span.name}")
if nqt_span.attributes is not None:
generate_test_report(
test_class=typing.cast(
"str",
nqt_span.attributes["test.suite.name"],
),
test_name=typing.cast("str", nqt_span.attributes["test.case.name"]),
)
else:
click.echo(f" · {nqt_span.name}")

return failing_tests_not_quarantined_count

Expand Down
42 changes: 35 additions & 7 deletions mergify_cli/tests/ci/junit_processing/test_check_failing_spans.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,11 @@ async def test_no_failing_tests_quarantined(
ReadableSpan(
name="test_me.py::test_mee",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe",
"test.case.name": "test_me",
},
),
]

Expand Down Expand Up @@ -90,17 +94,29 @@ async def test_some_failing_tests_quarantined(
ReadableSpan(
name="test_me.py::test_me1",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe1",
"test.case.name": "test_me1",
},
),
ReadableSpan(
name="test_me.py::test_me2",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe2",
"test.case.name": "test_me2",
},
),
ReadableSpan(
name="test_me.py::test_me3",
status=Status(status_code=StatusCode.OK, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe3",
"test.case.name": "test_me3",
},
),
ReadableSpan(
name="test_me.py::test_me4",
Expand Down Expand Up @@ -152,17 +168,29 @@ async def test_all_failing_tests_quarantined(
ReadableSpan(
name="test_me.py::test_me1",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe1",
"test.case.name": "test_me1",
},
),
ReadableSpan(
name="test_me.py::test_me2",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe2",
"test.case.name": "test_me2",
},
),
ReadableSpan(
name="test_me.py::test_me3",
status=Status(status_code=StatusCode.ERROR, description=""),
attributes={"test.scope": "case"},
attributes={
"test.scope": "case",
"test.suite.name": "TestMe3",
"test.case.name": "test_me3",
},
),
]

Expand Down
50 changes: 30 additions & 20 deletions mergify_cli/tests/ci/test_junit.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Registration.testCase1",
"code.function.name": "Tests.Registration.testCase1",
"test.suite.name": "Tests.Registration",
"test.case.name": "testCase1",
"code.function.name": "testCase1",
"test.case.result.status": "passed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -161,8 +162,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Registration.testCase2",
"code.function.name": "Tests.Registration.testCase2",
"test.suite.name": "Tests.Registration",
"test.case.name": "testCase2",
"code.function.name": "testCase2",
"test.case.result.status": "skipped",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -194,8 +196,9 @@ async def test_parse(
"exception.message": "invalid literal for int() with base 10: 'foobar'",
"exception.stacktrace": "bip, bip, bip, error!",
"exception.type": "ValueError",
"test.case.name": "Tests.Registration.testCase3",
"code.function.name": "Tests.Registration.testCase3",
"test.suite.name": "Tests.Registration",
"test.case.name": "testCase3",
"code.function.name": "testCase3",
"test.case.result.status": "failed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -251,8 +254,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Authentication.testCase7",
"code.function.name": "Tests.Authentication.testCase7",
"test.suite.name": "Tests.Authentication",
"test.case.name": "testCase7",
"code.function.name": "testCase7",
"test.case.result.status": "passed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -281,8 +285,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Authentication.testCase8",
"code.function.name": "Tests.Authentication.testCase8",
"test.suite.name": "Tests.Authentication",
"test.case.name": "testCase8",
"code.function.name": "testCase8",
"test.case.result.status": "passed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -314,8 +319,9 @@ async def test_parse(
"exception.message": "Assertion error message",
"exception.stacktrace": "Such a mess, the failure is unrecoverable",
"exception.type": "AssertionError",
"test.case.name": "Tests.Authentication.testCase9",
"code.function.name": "Tests.Authentication.testCase9",
"test.suite.name": "Tests.Authentication",
"test.case.name": "testCase9",
"code.function.name": "testCase9",
"test.case.result.status": "failed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -348,8 +354,9 @@ async def test_parse(
"exception.stacktrace": "Everything is broken, meh!\n"
"With a second line!",
"exception.type": "ZeroDivisionError",
"test.case.name": "Tests.Permission.testCase10",
"code.function.name": "Tests.Permission.testCase10",
"test.suite.name": "Tests.Permission",
"test.case.name": "testCase10",
"code.function.name": "testCase10",
"test.case.result.status": "failed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -405,8 +412,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Authentication.Login.testCase4",
"code.function.name": "Tests.Authentication.Login.testCase4",
"test.suite.name": "Tests.Authentication.Login",
"test.case.name": "testCase4",
"code.function.name": "testCase4",
"test.case.result.status": "passed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -438,8 +446,9 @@ async def test_parse(
"exception.message": "invalid syntax",
"exception.stacktrace": "bad syntax, bad!",
"exception.type": "SyntaxError",
"test.case.name": "Tests.Authentication.Login.testCase5",
"code.function.name": "Tests.Authentication.Login.testCase5",
"test.suite.name": "Tests.Authentication.Login",
"test.case.name": "testCase5",
"code.function.name": "testCase5",
"test.case.result.status": "failed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down Expand Up @@ -468,8 +477,9 @@ async def test_parse(
},
{
"attributes": {
"test.case.name": "Tests.Authentication.Login.testCase6",
"code.function.name": "Tests.Authentication.Login.testCase6",
"test.suite.name": "Tests.Authentication.Login",
"test.case.name": "testCase6",
"code.function.name": "testCase6",
"test.case.result.status": "passed",
"test.scope": "case",
"test.framework": "unittest",
Expand Down