Skip to content

Commit f1b6328

Browse files
authored
Fix running smoketests --list (#3700)
# Description of Changes We had a few issues with `smoketests --list`. I wanted to improve this behavior in preparation for using it (or similar logic) to parallelize the unit tests. First of all, the `--list` logic ran _after_ our calls to `cargo build` and `spacetime login`, so it could take a while to run tests _and_ you would need a server running just to list tests. Also, if any tests failed to import, it would give a cryptic error and exit in the middle of the list: ``` Traceback (most recent call last): File "<frozen runpy>", line 198, in _run_module_as_main File "<frozen runpy>", line 88, in _run_code File "/home/lead/work/clockwork-localhd/SpacetimeDBPrivate/public/smoketests/__main__.py", line 169, in <module> main() File "/home/lead/work/clockwork-localhd/SpacetimeDBPrivate/public/smoketests/__main__.py", line 151, in main for test in itertools.chain(*itertools.chain(*tests)): TypeError: '_FailedTest' object is not iterable ``` Now, it completes the entire list regardless, and prints a much better clearer message: ``` Failed to construct unittest.loader._FailedTest.pg_wire: ImportError: Failed to import test module: pg_wire Traceback (most recent call last): File "/usr/lib/python3.12/unittest/loader.py", line 137, in loadTestsFromName module = __import__(module_name) ^^^^^^^^^^^^^^^^^^^^^^^ File "/home/lead/work/clockwork-localhd/SpacetimeDBPrivate/public/smoketests/tests/pg_wire.py", line 5, in <module> import psycopg2 ModuleNotFoundError: No module named 'psycopg2' ``` # API and ABI breaking changes None. CI only. # Expected complexity level and risk 2 # Testing <!-- Describe any testing you've done, and any testing you'd like your reviewers to do, so that you're confident that all the changes work as expected! --> - [x] Existing CI still passes - [x] Running `python -m smoketests --list` no longer tries to build SpacetimeDB or log in to a server - [x] Running `python -m smoketests --list` has a (much) more descriptive error if there are broken imports Co-authored-by: Zeke Foppa <bfops@users.noreply.github.com>
1 parent 87de672 commit f1b6328

File tree

1 file changed

+55
-34
lines changed

1 file changed

+55
-34
lines changed

smoketests/__main__.py

Lines changed: 55 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import tempfile
1616
from pathlib import Path
1717
import shutil
18+
import traceback
1819

1920
def check_docker():
2021
docker_ps = smoketests.run_cmd("docker", "ps", "--format=json")
@@ -57,6 +58,15 @@ def _convert_select_pattern(pattern):
5758

5859

5960
TESTPREFIX = "smoketests.tests."
61+
62+
def _iter_all_tests(suite_or_case):
63+
"""Yield all individual tests from possibly nested TestSuite structures."""
64+
if isinstance(suite_or_case, unittest.TestSuite):
65+
for t in suite_or_case:
66+
yield from _iter_all_tests(t)
67+
else:
68+
yield suite_or_case
69+
6070
def main():
6171
tests = [fname.removesuffix(".py") for fname in os.listdir(TEST_DIR / "tests") if fname.endswith(".py") and fname != "__init__.py"]
6272

@@ -79,6 +89,51 @@ def main():
7989
parser.add_argument("--spacetime-login", action="store_true", help="Use `spacetime login` for these tests (and disable tests that don't work with that)")
8090
args = parser.parse_args()
8191

92+
if args.docker:
93+
# have docker logs print concurrently with the test output
94+
if args.compose_file:
95+
smoketests.COMPOSE_FILE = args.compose_file
96+
if not args.no_docker_logs:
97+
if args.compose_file:
98+
subprocess.Popen(["docker", "compose", "-f", args.compose_file, "logs", "-f"])
99+
else:
100+
docker_container = check_docker()
101+
subprocess.Popen(["docker", "logs", "-f", docker_container])
102+
smoketests.HAVE_DOCKER = True
103+
104+
if not args.skip_dotnet:
105+
smoketests.HAVE_DOTNET = check_dotnet()
106+
if not smoketests.HAVE_DOTNET:
107+
print("no suitable dotnet installation found")
108+
exit(1)
109+
110+
add_prefix = lambda testlist: [TESTPREFIX + test for test in testlist]
111+
import fnmatch
112+
excludelist = add_prefix(args.exclude)
113+
testlist = add_prefix(args.test)
114+
115+
loader = ExclusionaryTestLoader(excludelist)
116+
loader.testNamePatterns = args.testNamePatterns
117+
118+
tests = loader.loadTestsFromNames(testlist)
119+
if args.list:
120+
failed_cls = getattr(unittest.loader, "_FailedTest", None)
121+
any_failed = False
122+
for test in _iter_all_tests(tests):
123+
name = test.id()
124+
if isinstance(test, failed_cls):
125+
any_failed = True
126+
print('')
127+
print("Failed to construct %s:" % test.id())
128+
exc = getattr(test, "_exception", None)
129+
if exc is not None:
130+
tb = ''.join(traceback.format_exception(exc))
131+
print(tb.rstrip())
132+
print('')
133+
else:
134+
print(f"{name}")
135+
exit(1 if any_failed else 0)
136+
82137
if not args.no_build_cli:
83138
logging.info("Compiling spacetime cli...")
84139
smoketests.run_cmd("cargo", "build", cwd=TEST_DIR.parent, capture_stderr=False)
@@ -100,18 +155,6 @@ def main():
100155

101156
os.environ["SPACETIME_SKIP_CLIPPY"] = "1"
102157

103-
if args.docker:
104-
# have docker logs print concurrently with the test output
105-
if args.compose_file:
106-
smoketests.COMPOSE_FILE = args.compose_file
107-
if not args.no_docker_logs:
108-
if args.compose_file:
109-
subprocess.Popen(["docker", "compose", "-f", args.compose_file, "logs", "-f"])
110-
else:
111-
docker_container = check_docker()
112-
subprocess.Popen(["docker", "logs", "-f", docker_container])
113-
smoketests.HAVE_DOCKER = True
114-
115158
with tempfile.NamedTemporaryFile(mode="w+b", suffix=".toml", buffering=0, delete_on_close=False) as config_file:
116159
with BASE_STDB_CONFIG_PATH.open("rb") as src, config_file.file as dst:
117160
shutil.copyfileobj(src, dst)
@@ -130,28 +173,6 @@ def main():
130173
smoketests.STDB_CONFIG = Path(config_file.name).read_text()
131174

132175
build_template_target()
133-
134-
if not args.skip_dotnet:
135-
smoketests.HAVE_DOTNET = check_dotnet()
136-
if not smoketests.HAVE_DOTNET:
137-
print("no suitable dotnet installation found")
138-
exit(1)
139-
140-
add_prefix = lambda testlist: [TESTPREFIX + test for test in testlist]
141-
import fnmatch
142-
excludelist = add_prefix(args.exclude)
143-
testlist = add_prefix(args.test)
144-
145-
loader = ExclusionaryTestLoader(excludelist)
146-
loader.testNamePatterns = args.testNamePatterns
147-
148-
tests = loader.loadTestsFromNames(testlist)
149-
if args.list:
150-
print("Selected tests:\n")
151-
for test in itertools.chain(*itertools.chain(*tests)):
152-
print(f"{test}")
153-
exit(0)
154-
155176
buffer = not args.show_all_output
156177
verbosity = 2
157178

0 commit comments

Comments
 (0)