Skip to content

Commit ff06878

Browse files
committed
Rewrite cts_exe.py to handle match logic
cts_exe.py has been rewritten to include the logic for handling .match files. Specifically, it will divide the test space into two halves; tests that are expected to pass, and those that are expected to fail. For the tests expected to pass, it will run them all in the same gtest invocation with the assumption that it succeeds. For the tests expected to fail, they will each be ran with individual gtest invocations. This allows them to freely segfault or abort without hurting other tests. In this commit, the match files are (mostly) unchanged, and the passing and failing tests should be the same. The match file is treated as a list of failing tests with a few tokens that are replaced: * `{{NONDETERMINISTIC}}` ignored, required for compatibility with the match checker. * `{{OPT}}` this test may or may not fail. It's still ran seperately, but doesn't report an error on failure. * `{{.*}}` replaced with `*`; converts "match" wildcard matches to "gtest" test name matches. * `#` and empty lines are ignored and treated as a comment. * `{{Segmentation` for compatibility, this will cause a failure in the "excepted success" execution to not count as an error. This matches the behaviour of the prior match test logic. Some .match files have been fixed and empty ones have been removed. If GTEST_OUTPUT is specified, we assume that we are being run in ctest_parser.py and don't do anything fancy.
1 parent 568a96a commit ff06878

File tree

6 files changed

+169
-67
lines changed

6 files changed

+169
-67
lines changed

test/conformance/CMakeLists.txt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,13 @@ function(add_test_adapter name adapter backend)
2121
function(do_add_test tname env)
2222
if(${UR_CONFORMANCE_ENABLE_MATCH_FILES} AND EXISTS ${MATCH_FILE})
2323
add_test(NAME ${tname}
24-
COMMAND ${CMAKE_COMMAND}
25-
-D TEST_FILE=${Python3_EXECUTABLE}
26-
-D TEST_ARGS="${UR_CONFORMANCE_TEST_DIR}/cts_exe.py --test_command ${TEST_COMMAND}"
27-
-D MODE=stdout
28-
-D MATCH_FILE=${MATCH_FILE}
29-
-P ${PROJECT_SOURCE_DIR}/cmake/match.cmake
30-
DEPENDS ${TEST_TARGET_NAME}
24+
COMMAND ${Python3_EXECUTABLE} ${UR_CONFORMANCE_TEST_DIR}/cts_exe.py
25+
--failslist ${MATCH_FILE}
26+
--test_command ${PROJECT_BINARY_DIR}/bin/${TEST_TARGET_NAME}
27+
--
28+
--backend=${backend}
29+
--devices_count=${UR_TEST_DEVICES_COUNT}
30+
--platforms_count=${UR_TEST_PLATFORMS_COUNT}
3131
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
3232
)
3333
else()
@@ -40,7 +40,7 @@ function(add_test_adapter name adapter backend)
4040
endif()
4141

4242
if(UR_CONFORMANCE_ENABLE_MATCH_FILES)
43-
list(APPEND env GTEST_COLOR=no)
43+
list(APPEND env GTEST_COLOR=yes)
4444
endif()
4545
set_tests_properties(${tname} PROPERTIES
4646
ENVIRONMENT "${env}"

test/conformance/cts_exe.py

100644100755
Lines changed: 159 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
#! /usr/bin/env python3
1+
#!/usr/bin/env python3
22
"""
3-
Copyright (C) 2023 Intel Corporation
3+
Copyright (C) 2024 Intel Corporation
44
55
Part of the Unified-Runtime Project, under the Apache License v2.0 with LLVM Exceptions.
66
See LICENSE.TXT
@@ -11,68 +11,171 @@
1111
# The match files contain tests that are expected to fail.
1212

1313
import os
14-
import shlex
1514
import sys
16-
from argparse import ArgumentParser
15+
import argparse
1716
import subprocess # nosec B404
18-
import signal
19-
import re
20-
from collections import OrderedDict
2117

2218

23-
def _print_cmdline(cmd_args, env, cwd, file=sys.stderr):
24-
cwd = shlex.quote(cwd)
25-
env_args = " ".join(
26-
"%s=%s" % (shlex.quote(k), shlex.quote(v)) for k, v in env.items()
19+
def _ci():
20+
return os.environ.get("CI") is not None
21+
22+
23+
def _color():
24+
return sys.stdout.isatty() or os.environ.get("GTEST_COLOR").lower() == "yes"
25+
26+
27+
def _print_header(header, *args):
28+
if _ci():
29+
# GitHub CI interprets this as a "group header" and will provide buttons to fold/unfold it
30+
print("##[group]{}".format(header.format(*args)))
31+
elif _color():
32+
# Inverse color
33+
print("\033[7m{}\033[27m".format(header.format(*args)))
34+
else:
35+
print("### {}".format(header.format(*args)))
36+
37+
38+
def _print_end_header():
39+
if _ci():
40+
print("##[endgroup]")
41+
42+
43+
def _print_error(header, *args):
44+
if _color():
45+
# "!!!" on a red background
46+
print("\033[41m!!!\033[0m {}".format(header.format(*args)))
47+
else:
48+
print("!!! {}".format(header.format(*args)))
49+
50+
51+
def _print_format(msg, *args):
52+
print(msg.format(*args))
53+
54+
55+
def _print_environ(env):
56+
_print_header("Environment")
57+
for k, v in env.items():
58+
_print_format("> {} = {}", k, v)
59+
_print_end_header()
60+
61+
62+
def _check_filter(cmd, filter):
63+
"""
64+
Checks that the filter matches at least one test for the given cmd
65+
"""
66+
sys.stdout.flush()
67+
check = subprocess.Popen( # nosec B603
68+
cmd + ["--gtest_list_tests"],
69+
stdout=subprocess.PIPE,
70+
stderr=subprocess.DEVNULL,
71+
env=(os.environ | {"GTEST_FILTER": filter}),
2772
)
28-
cmd_str = " ".join(map(shlex.quote, cmd_args))
29-
print(f"### env -C {cwd} -i {env_args} {cmd_str}", file=file)
73+
if not check.stdout.read(1):
74+
return False
75+
return True
3076

3177

32-
if __name__ == "__main__":
78+
def _run_cmd(cmd, comment, filter):
79+
_print_header("Running suite for: {}", comment)
80+
_print_format("### {}", " ".join(cmd))
81+
82+
# Check tests are found
83+
if not _check_filter(cmd, filter):
84+
_print_end_header()
85+
_print_error("Could not find any tests with this filter")
86+
return 2
3387

34-
parser = ArgumentParser()
88+
sys.stdout.flush()
89+
result = subprocess.call( # nosec B603
90+
cmd,
91+
stdout=sys.stdout,
92+
stderr=sys.stdout,
93+
env=(os.environ | {"GTEST_FILTER": filter}),
94+
)
95+
_print_end_header()
96+
return result
97+
98+
99+
if __name__ == "__main__":
100+
parser = argparse.ArgumentParser()
35101
parser.add_argument("--test_command", help="Ctest test case")
36-
parser.add_argument("--devices_count", type=str, help="Number of devices on which tests will be run")
37-
parser.add_argument("--platforms_count", type=str, help="Number of platforms on which tests will be run")
38-
parser.add_argument("--backend", type=str, help="Number of platforms on which tests will be run")
102+
parser.add_argument("--failslist", type=str, help="Failure list")
103+
parser.add_argument("--", dest="ignored", action="store_true")
104+
parser.add_argument("rest", nargs=argparse.REMAINDER)
39105
args = parser.parse_args()
40-
invocation = [
41-
args.test_command,
42-
"--gtest_brief=1",
43-
f"--devices_count={args.devices_count}",
44-
f"--platforms_count={args.platforms_count}",
45-
f"--backend={args.backend}",
46-
]
47-
_print_cmdline(invocation, os.environ, os.getcwd())
48-
49-
result = subprocess.Popen( # nosec B603
50-
invocation, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
51-
)
52106

53-
pat = re.compile(r'\[( )*FAILED( )*\]')
54-
output_list = []
55-
test_cases = []
56-
for line in result.stdout:
57-
output_list.append(line)
58-
if pat.search(line):
59-
test_case = line.split(" ")[5]
60-
test_case = test_case.rstrip(',')
61-
test_cases.append(test_case)
62-
63-
# Every fail has a single corresponding match line but if there are multiple
64-
# devices being tested there will be multiple lines with the same failure
65-
# message. To avoid matching mismatch, remove lines that differ only by device ID.
66-
test_cases = [re.sub(r'ID[0-9]ID', 'X', tc) for tc in test_cases]
67-
test_cases = list(OrderedDict.fromkeys(test_cases))
68-
69-
for tc in test_cases:
70-
print(tc)
71-
72-
rc = result.wait()
73-
if rc < 0:
74-
print(signal.strsignal(abs(rc)))
75-
76-
print("#### GTEST_OUTPUT ####", file=sys.stderr)
77-
print(''.join(output_list), file=sys.stderr)
78-
print("#### GTEST_OUTPUT_END ####", file=sys.stderr)
107+
base_invocation = [args.test_command] + args.rest
108+
109+
if os.environ.get("GTEST_OUTPUT") is not None:
110+
# We are being ran purely to generate an output file (likely for ctest_parser.py); falling back to just using
111+
# one test execution
112+
sys.exit(
113+
subprocess.call( # nosec B603
114+
base_invocation, stdout=sys.stdout, stderr=sys.stderr
115+
)
116+
)
117+
118+
_print_environ(os.environ)
119+
120+
# Parse fails list
121+
_print_format("Loading fails from {}", args.failslist)
122+
fail_patterns = []
123+
expected_fail = False
124+
with open(args.failslist) as f:
125+
for l in f:
126+
optional = "{{OPT}}" in l
127+
l = l.replace("{{OPT}}", "")
128+
l = l.replace("{{.*}}", "*")
129+
130+
if l.startswith("{{Segmentation fault"):
131+
expected_fail = True
132+
continue
133+
if l.startswith("#"):
134+
continue
135+
if l.startswith("{{NONDETERMINISTIC}}"):
136+
continue
137+
if l.strip() == "":
138+
continue
139+
140+
fail_patterns.append(
141+
{
142+
"pattern": l.strip(),
143+
"optional": optional,
144+
}
145+
)
146+
147+
_print_header("Known failing tests")
148+
for fail in fail_patterns:
149+
_print_format("> {}", fail)
150+
_print_end_header()
151+
if len(fail_patterns) == 0:
152+
_print_error(
153+
"Fail list is empty, if there are no more failures, please remove the file"
154+
)
155+
sys.exit(2)
156+
157+
final_result = 0
158+
159+
# First, run all the known good tests
160+
gtest_filter = "-" + (":".join(map(lambda x: x["pattern"], fail_patterns)))
161+
if _check_filter(base_invocation, gtest_filter):
162+
result = _run_cmd(base_invocation, "known good tests", gtest_filter)
163+
if result != 0 and not expected_fail:
164+
_print_error("Tests we expected to pass have failed")
165+
final_result = result
166+
else:
167+
_print_format("Note: No tests in this suite are expected to pass")
168+
169+
# Then run each known failing tests
170+
for fail in fail_patterns:
171+
result = _run_cmd(
172+
base_invocation, "failing test {}".format(fail["pattern"]), fail["pattern"]
173+
)
174+
175+
if result == 0 and not fail["optional"]:
176+
_print_error(
177+
"Test {} is passing when we expect it to fail!", fail["pattern"]
178+
)
179+
final_result = 1
180+
181+
sys.exit(final_result)

test/conformance/exp_command_buffer/exp_command_buffer_adapter_cuda.match

Whitespace-only changes.

test/conformance/exp_command_buffer/exp_command_buffer_adapter_hip.match

Whitespace-only changes.

test/conformance/exp_command_buffer/exp_command_buffer_adapter_native_cpu.match

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
{{OPT}}BufferFillCommandTest.OverrideArgList/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
1414
{{OPT}}InvalidUpdateTest.NotFinalizedCommandBuffer/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
1515
{{OPT}}InvalidUpdateTest.NotUpdatableCommandBuffer/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
16-
{{OPT}}InvalidUpdateTest.InvalidDimensions/SYCL_NATIVE_CPU___SYCL_Native_CPU__X_
16+
{{OPT}}InvalidUpdateTest.InvalidDimensions/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
1717
{{OPT}}USMFillCommandTest.UpdateParameters/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
1818
{{OPT}}USMFillCommandTest.UpdateBeforeEnqueue/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
1919
{{OPT}}USMMultipleFillCommandTest.UpdateAllKernels/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
@@ -33,6 +33,6 @@
3333
{{OPT}}KernelCommandEventSyncTest.InterCommandBuffer/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
3434
{{OPT}}KernelCommandEventSyncTest.SignalWaitBeforeEnqueue/SYCL_NATIVE_CPU__{{.*}}
3535
{{OPT}}KernelCommandEventSyncUpdateTest.Basic/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
36-
{{OPT}}KernelCommandEventSyncUpdateTest.TwoWaitEvents/SYCL_NATIVE_CPU___SYCL_Native_CPU__X_{{.*}}
36+
{{OPT}}KernelCommandEventSyncUpdateTest.TwoWaitEvents/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
3737
{{OPT}}KernelCommandEventSyncUpdateTest.InvalidWaitUpdate/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}
3838
{{OPT}}KernelCommandEventSyncUpdateTest.InvalidSignalUpdate/SYCL_NATIVE_CPU___SYCL_Native_CPU__{{.*}}

test/conformance/queue/queue_adapter_level_zero.match

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)