Skip to content
Merged
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
14 changes: 9 additions & 5 deletions ci/test_python_sklearn_examples.sh
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/bin/bash
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# Support invoking test script outside the script directory
Expand Down Expand Up @@ -30,12 +30,16 @@ timeout -v --signal=SIGINT --kill-after=60s 60m ./python/cuml/cuml_accel_tests/u
--example-timeout=300 \
--junitxml="${SKLEARN_EXAMPLES_JUNITXML}"

# Per-example timeouts and network failures are reported as xfails. The
# examples tests still require a healthy majority of examples to pass so
# widespread regressions are not missed.
rapids-logger "scikit-learn examples: require >=90% pass rate"
# Per-example timeouts and network failures are reported as xfails. Network
# failures do not exercise cuml.accel and are excluded from the pass-rate
# denominator. Timeouts and all other xfails remain non-passing outcomes. The
# lower total pass-rate threshold includes network failures to catch widespread
# network outages.
rapids-logger "scikit-learn examples: require >=90% pass rate and >=80% total"
./python/cuml/cuml_accel_tests/upstream/summarize-results.py \
--fail-below 90 \
--total-fail-below 80 \
--exclude-xfail-reason "Network error:" \
"${SKLEARN_EXAMPLES_JUNITXML}"

rapids-logger "Test script exiting with value: $EXITCODE"
Expand Down
4 changes: 4 additions & 0 deletions python/cuml/cuml_accel_tests/upstream/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,14 @@ Useful `summarize-results.py` options:

- `-v, --verbose`: Display detailed failure information.
- `-f, --fail-below VALUE`: Set a minimum pass-rate threshold from 0 to 100.
- `--total-fail-below VALUE`: Set a minimum total pass-rate threshold,
including outcomes excluded by `--exclude-xfail-reason`.
- `--format FORMAT`: Output `summary`, `xfail_list`, or `traceback`.
- `--limit N`: Limit output to the first `N` entries.
- `--test-id-prefix PREFIX`: Prefix added to test IDs in generated output.
- `-k, --filter PATTERN`: Filter tests by ID substring, case-insensitively.
- `--exclude-xfail-reason TEXT`: Exclude xfails whose JUnit reason contains
`TEXT` from the pass-rate denominator. May be specified multiple times.
- `--config FILE`: Load summary defaults from a config file, such as
`scikit-learn/test_config.yaml` for scikit-learn tests.

Expand Down
55 changes: 53 additions & 2 deletions python/cuml/cuml_accel_tests/upstream/summarize-results.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION.
# SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Summarize test results from a JUnit XML report file."""
Expand Down Expand Up @@ -71,6 +71,14 @@ def parse_args():
type=float,
help="Minimum pass rate threshold [0-100] (default: 0)",
)
parser.add_argument(
"--total-fail-below",
type=float,
help=(
"Minimum total pass rate threshold [0-100], including outcomes "
"excluded by --exclude-xfail-reason (default: disabled)"
),
)
parser.add_argument(
"--format",
choices=["summary", "xfail_list", "traceback"],
Expand All @@ -94,6 +102,16 @@ def parse_args():
dest="filter_pattern",
help="Filter tests by ID pattern (substring match, case-insensitive)",
)
parser.add_argument(
"--exclude-xfail-reason",
action="append",
default=[],
metavar="TEXT",
help=(
"Exclude xfails whose JUnit reason contains TEXT from the pass-rate "
"denominator. May be specified multiple times."
),
)
args = parser.parse_args()

# Load config if provided
Expand Down Expand Up @@ -137,6 +155,12 @@ def matches_filter(test_id, pattern):
return pattern.lower() in test_id.lower()


def matches_xfail_reason(skipped_elem, patterns):
"""Return whether an xfail reason matches any exclusion pattern."""
message = skipped_elem.get("message", "")
return any(pattern in message for pattern in patterns)


def get_test_results(testsuite, prefix: str = ""):
"""Extract test results from testsuite.

Expand Down Expand Up @@ -289,6 +313,8 @@ def main():
"""Main entry point."""
args = parse_args()
validate_threshold(args.fail_below)
if args.total_fail_below is not None:
validate_threshold(args.total_fail_below)

if not args.report_file.exists():
print(f"Error: Report file not found: {args.report_file}")
Expand Down Expand Up @@ -317,6 +343,7 @@ def main():
regular_errors = 0
regular_skipped = 0
xfailed = 0
excluded_xfailed = 0
xpassed_strict = 0
xpassed_non_strict = 0
for testcase in testsuite.findall(".//testcase"):
Expand Down Expand Up @@ -345,6 +372,10 @@ def main():
elif skipped_elem is not None:
if skipped_elem.get("type") == "pytest.xfail":
xfailed += 1
if matches_xfail_reason(
skipped_elem, args.exclude_xfail_reason
):
excluded_xfailed += 1
else:
regular_skipped += 1

Expand All @@ -358,7 +389,13 @@ def main():
- xpassed_strict
- xpassed_non_strict
)
pass_rate = (passed / total_tests * 100) if total_tests > 0 else 0
pass_rate_denominator = total_tests - excluded_xfailed
pass_rate = (
passed / pass_rate_denominator * 100
if pass_rate_denominator > 0
else 0
)
total_pass_rate = passed / total_tests * 100 if total_tests > 0 else 0

if args.format == "traceback":
output = format_traceback_output(
Expand Down Expand Up @@ -407,11 +444,13 @@ def main():
["Passed:", str(passed)],
["Failed:", str(regular_failures)],
["XFailed:", str(xfailed)],
["Excluded XFailed:", str(excluded_xfailed)],
["XPassed (strict):", str(xpassed_strict)],
["XPassed (non-strict):", str(xpassed_non_strict)],
["Errors:", str(regular_errors)],
["Skipped:", str(regular_skipped)],
["Pass Rate:", f"{pass_rate:.2f}%"],
["Total Pass Rate:", f"{total_pass_rate:.2f}%"],
["Total Time:", f"{time:.2f}s"],
]
for row in format_table(rows, " "):
Expand Down Expand Up @@ -469,11 +508,23 @@ def main():
print(f' "{test_id}"')
count += 1

threshold_failed = False
if pass_rate < args.fail_below:
print(
f"\nError: Pass rate {pass_rate:.2f}% is below threshold "
f"{args.fail_below}%"
)
threshold_failed = True
if (
args.total_fail_below is not None
and total_pass_rate < args.total_fail_below
):
print(
f"\nError: Total pass rate {total_pass_rate:.2f}% is below "
f"threshold {args.total_fail_below}%"
)
threshold_failed = True
if threshold_failed:
sys.exit(1)

sys.exit(0)
Expand Down
Loading