Skip to content

Commit 2299600

Browse files
DinoVmeta-codesync[bot]
authored andcommitted
Run tests as their own Sandcastle jobs
Summary: This may or may not be a good idea... please look at the `cinderx_ci-...` runs on this diff and see how you feel about this. With this diff I saw my sandcastle jobs as being throttled - I'm not sure if Sandcastle will adapt to the fact that these run quickly over time. The "Duration" for these on the diff page also seems to include queueing time, so you can see they actually run much faster. This splits our test cases out to run one by one. Now if a test is failing we'll have just a single failure instead of having an entire opaque test run failing. We can't read the list of tests at build time so we now have a cached list of known tests. There's a new test which tests that the tests are up to date, and if they're not `TestScripts/test_test_defs.sh` can be run and the resulting `/tmp/tests.txt` can be copied over `cinderx/tests.bzl`. The test runners needed to be updated to have a new `--list` option to print out all of the tests they discovered. There also were some small updates so that the individual tests would successfully run and pass: * We need to set sys.path on 3.10 in the worker like we do on 3.12+ * When running an individual module on 3.10 we need to fallback to `_runtest_inner2` so that it can run 'test_main` to setup the module. This also means we need to get rid of it's propensity to pre-pend `test.` if a test name doesn't start with that so that we can run `test_cinderx` tests. * If no tests are run we report success (because our filters can now filter down to 0 tests). Probably the most annoying thing about this is when we're onboarding a new version we don't get a nice concise list of test failures to update a failures script. I'm curious what people think about that... we could always have some easy-to-enable thing which runs all of the tests. This also enables base revision retries now that they should be a little faster as they target individual tests. Reviewed By: alexmalyshev Differential Revision: D85780300 fbshipit-source-id: 94168f134ba98d62088eaa8c80f16f3f2cc7231a
1 parent 895b7a9 commit 2299600

3 files changed

Lines changed: 155 additions & 76 deletions

File tree

cinderx/TestScripts/cinder_test_runner310.py

Lines changed: 71 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@
3131
import types
3232
import unittest
3333

34+
from importlib.util import find_spec
3435
from pathlib import Path
35-
3636
from typing import Dict, IO, Iterable, List, Optional, Set, Tuple
3737

3838
from cinderx.test_support import get_cinderjit_xargs, is_asan_build
@@ -62,7 +62,9 @@
6262
from test import support
6363
from test.libregrtest.cmdline import Namespace
6464
from test.libregrtest.main import Regrtest
65+
6566
from test.libregrtest.runtest import (
67+
_runtest_inner2,
6668
ChildError,
6769
findtestdir,
6870
findtests,
@@ -156,6 +158,10 @@ def run(self, ns: types.SimpleNamespace) -> None:
156158
t.set_temp_dir()
157159
test_cwd = t.create_temp_dir()
158160
setup_tests(t.ns)
161+
import test.libregrtest.runtest as rt
162+
163+
rt._runtest_inner2 = _patched_runtest_inner2
164+
rt.get_abs_module = lambda ns, name: name
159165
# Run the tests in a context manager that temporarily changes the CWD to a
160166
# temporary and writable directory. If it's not possible to create or
161167
# change the CWD, the original CWD will be used. The original CWD is
@@ -321,6 +327,45 @@ def _setupCinderIgnoredTests(ns: Namespace, use_rr: bool) -> Tuple[List[str], Se
321327
return list(stdtest_set), nottests
322328

323329

330+
@staticmethod
331+
def list_tests(ns: Namespace, use_rr: bool) -> None:
332+
cinderx_dir = get_cinderx_dir()
333+
test_cinderx_dir = get_test_cinderx_dir(cinderx_dir)
334+
335+
# Added to sys.path for test modules. `test_cinderx_dir.parent` is just
336+
# `cinderx_dir` in the Git repository, but it can be different for
337+
# internal builds.
338+
ns.testdir = str(test_cinderx_dir.parent)
339+
340+
setup_tests(ns)
341+
test_filters = _setupCinderIgnoredTests(ns, use_rr)
342+
343+
cinderx_dir = get_cinderx_dir()
344+
test_cinderx_dir = get_test_cinderx_dir(cinderx_dir)
345+
346+
stdtest, nottests = test_filters
347+
348+
# Initial set of tests are the core Python/Cinder ones.
349+
tests = ["test." + t for t in findtests(None, stdtest, nottests)]
350+
351+
# Add CinderX tests
352+
cinderx_tests = findtests(str(test_cinderx_dir), [], nottests)
353+
tests.extend("test_cinderx." + t for t in cinderx_tests if not t == "test_compiler")
354+
355+
# Split the compiler tests into their subtests so they run faster
356+
compiler_tests = findtests(str(test_cinderx_dir) + "/test_compiler", [], nottests)
357+
tests.extend(
358+
"test_cinderx.test_compiler." + t for t in compiler_tests if t != "test_static"
359+
)
360+
361+
# Also split the static Python tests, they don't start with "test_" so we
362+
# need to manually discover them, and exclude
363+
testdir = findtestdir(str(test_cinderx_dir) + "/test_compiler/test_static")
364+
tests.extend(get_cinderx_static_tests(testdir))
365+
366+
return tests
367+
368+
324369
class MultiWorkerCinderRegrtest(Regrtest):
325370
def __init__(
326371
self,
@@ -476,21 +521,10 @@ def _save_recording_metadata(self, replay_infos: List[ReplayInfo]) -> None:
476521
def _main(self, tests, kwargs):
477522
self.ns.fail_env_changed = True
478523

479-
cinderx_dir = get_cinderx_dir()
480-
test_cinderx_dir = get_test_cinderx_dir(cinderx_dir)
481-
482-
# Added to sys.path for test modules. `test_cinderx_dir.parent` is just
483-
# `cinderx_dir` in the Git repository, but it can be different for
484-
# internal builds.
485-
self.ns.testdir = str(test_cinderx_dir.parent)
486-
487-
setup_tests(self.ns)
488-
489-
test_filters = _setupCinderIgnoredTests(self.ns, self._use_rr)
490-
491524
if tests is None:
492-
self._selectDefaultCinderTests(test_filters, test_cinderx_dir)
525+
self.tests = list_tests(self.ns, self._use_rr)
493526
else:
527+
_setupCinderIgnoredTests(self.ns, self._use_rr)
494528
self.find_tests(tests)
495529

496530
replay_infos = self.run_tests()
@@ -519,37 +553,6 @@ def _main(self, tests, kwargs):
519553
sys.exit(3)
520554
sys.exit(0)
521555

522-
def _selectDefaultCinderTests(
523-
self, test_filters: Tuple[List[str], Set[str]], test_cinderx_dir: Path
524-
) -> None:
525-
stdtest, nottests = test_filters
526-
527-
# Initial set of tests are the core Python/Cinder ones.
528-
tests = ["test." + t for t in findtests(None, stdtest, nottests)]
529-
530-
# Add CinderX tests
531-
cinderx_tests = findtests(str(test_cinderx_dir), [], nottests)
532-
tests.extend(
533-
"test_cinderx." + t for t in cinderx_tests if not t == "test_compiler"
534-
)
535-
536-
# Spilt the compiler tests into their subtests so they run faster
537-
compiler_tests = findtests(
538-
str(test_cinderx_dir) + "/test_compiler", [], nottests
539-
)
540-
tests.extend(
541-
"test_cinderx.test_compiler." + t
542-
for t in compiler_tests
543-
if t != "test_static"
544-
)
545-
546-
# Also split the static Python tests, they don't start with "test_" so we
547-
# need to manually discover them, and exclude
548-
testdir = findtestdir(str(test_cinderx_dir) + "/test_compiler/test_static")
549-
tests.extend(get_cinderx_static_tests(testdir))
550-
551-
self.selected = tests
552-
553556
def _writeResultsToScuba(self) -> None:
554557
template = {
555558
"int": {
@@ -593,8 +596,15 @@ def _writeResultsToScuba(self) -> None:
593596
def _patched_runtest_inner2(ns: Namespace, tests_name: str) -> bool:
594597
import test.libregrtest.runtest as runtest
595598

599+
if find_spec(tests_name) is not None:
600+
# if we have a module fallback to the existing _runtest_inner2, it
601+
# does some additioanl setup like calling test_main that we can't
602+
# handle for non-module tests.
603+
return _runtest_inner2(ns, tests_name)
604+
596605
loader = unittest.TestLoader()
597606
tests = loader.loadTestsFromName(tests_name, None)
607+
598608
for error in loader.errors:
599609
print(error, file=sys.stderr)
600610
if loader.errors:
@@ -687,6 +697,7 @@ def force_dots_output(self, *args, **kwargs):
687697

688698

689699
def worker_main(args):
700+
sys.path.insert(0, str(get_cinderx_dir() / "PythonLib"))
690701
ns_dict = json.loads(args.ns)
691702
ns = types.SimpleNamespace(**ns_dict)
692703
with MessagePipe(args.cmd_fd, args.result_fd) as pipe:
@@ -752,7 +763,6 @@ def dispatcher_main(args):
752763
with tempfile.NamedTemporaryFile(
753764
delete=False, mode="w+t", dir=CINDER_RUNNER_LOG_DIR
754765
) as logfile:
755-
print(f"Using scheduling log file {logfile.name}")
756766
test_runner = MultiWorkerCinderRegrtest(
757767
logfile,
758768
args.log_to_scuba,
@@ -763,6 +773,15 @@ def dispatcher_main(args):
763773
args.recording_metadata_path,
764774
args.no_retry_on_test_errors,
765775
)
776+
if args.list:
777+
sys.argv[1:] = args.rest[1:]
778+
test_runner.parse_args({})
779+
tests = list_tests(test_runner.ns, args.use_rr)
780+
tests.sort()
781+
print(json.dumps(tests, indent=0))
782+
sys.exit(0)
783+
784+
print(f"Using scheduling log file {logfile.name}")
766785
test_runner.num_workers = args.num_workers
767786
print(f"Spawning {test_runner.num_workers} workers")
768787
# Put any args we didn't care about into sys.argv for
@@ -874,6 +893,12 @@ def replay_main(args):
874893
action="append",
875894
help="The name of a test to run (e.g. `test_math`). Can be supplied multiple times.",
876895
)
896+
dispatcher_parser.add_argument(
897+
"-l",
898+
"--list",
899+
action="store_true",
900+
help="List tests and exit.",
901+
)
877902
dispatcher_parser.add_argument(
878903
"-R",
879904
"--huntrleaks",

cinderx/TestScripts/cinder_test_runner312.py

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,40 @@ def _computeSkipTests(huntrleaks, use_rr=False) -> Tuple[Set[str], Set[str]]:
329329
return skip_modules, skip_patterns
330330

331331

332+
def _select_tests(exclude: Set[str]) -> List[str]:
333+
# Initial set of tests are the core Python ones.
334+
tests = libregrtest_findtests.findtests(
335+
exclude=exclude,
336+
base_mod="test",
337+
split_test_dirs={"test." + d for d in libregrtest_findtests.SPLITTESTDIRS},
338+
)
339+
340+
# Add CinderX tests
341+
cinderx_tests = libregrtest_findtests.findtests(
342+
testdir=get_test_cinderx_dir(),
343+
exclude=exclude,
344+
split_test_dirs=CINDERX_SPLIT_TEST_DIRS,
345+
base_mod="test_cinderx",
346+
)
347+
tests.extend(cinderx_tests)
348+
349+
# findtests won't discover the static tests that don't start with test_, so manually
350+
# add those (it would find just test_static if we didn't split on that, but we want
351+
# to parallelize all of the static tests)
352+
testdir = libregrtest_findtests.findtestdir(
353+
get_test_cinderx_dir() / Path("test_compiler/test_static")
354+
)
355+
tests.extend(get_cinderx_static_tests(testdir))
356+
357+
return tests
358+
359+
360+
def list_tests(hunterleaks, use_rr):
361+
skip_modules, skip_patterns = _computeSkipTests(hunterleaks, use_rr)
362+
363+
return _select_tests(skip_modules)
364+
365+
332366
class MultiWorkerCinderRegrtest:
333367
def __init__(
334368
self,
@@ -375,7 +409,7 @@ def __init__(
375409
skip_modules, skip_patterns = _computeSkipTests(self._huntrleaks, self._use_rr)
376410

377411
if tests is None:
378-
tests = self._selectTests(skip_modules)
412+
tests = _select_tests(skip_modules)
379413

380414
extra_opts = {}
381415
if sys.version_info >= (3, 14):
@@ -622,6 +656,8 @@ def run(self):
622656
# True, True => fail_env_changed, fail_rerun
623657
if self._results.bad:
624658
self.write_new_failures(starting_bad, missing_failures)
659+
if self._results.no_tests_run():
660+
sys.exit(0)
625661
sys.exit(self._results.get_exitcode(False, False))
626662
sys.exit(0)
627663

@@ -654,8 +690,12 @@ def update_expected_failures(self) -> tuple[list[str], list[str]]:
654690
starting_bad = list(self._results.bad)
655691
starting_bad.sort()
656692
missing_failures = []
693+
executed = self._results.get_executed()
657694
for expected_failure in self._expected_failures:
658-
if expected_failure not in self._results.bad:
695+
if (
696+
expected_failure not in self._results.bad
697+
and expected_failure in executed
698+
):
659699
missing_failures.append(expected_failure)
660700
else:
661701
# Failure was expected, move to the skipped list
@@ -666,33 +706,6 @@ def update_expected_failures(self) -> tuple[list[str], list[str]]:
666706

667707
return starting_bad, missing_failures
668708

669-
def _selectTests(self, exclude: Set[str]) -> List[str]:
670-
# Initial set of tests are the core Python ones.
671-
tests = libregrtest_findtests.findtests(
672-
exclude=exclude,
673-
base_mod="test",
674-
split_test_dirs={"test." + d for d in libregrtest_findtests.SPLITTESTDIRS},
675-
)
676-
677-
# Add CinderX tests
678-
cinderx_tests = libregrtest_findtests.findtests(
679-
testdir=get_test_cinderx_dir(),
680-
exclude=exclude,
681-
split_test_dirs=CINDERX_SPLIT_TEST_DIRS,
682-
base_mod="test_cinderx",
683-
)
684-
tests.extend(cinderx_tests)
685-
686-
# findtests won't discover the static tests that don't start with test_, so manually
687-
# add those (it would find just test_static if we didn't split on that, but we want
688-
# to parallelize all of the static tests)
689-
testdir = libregrtest_findtests.findtestdir(
690-
get_test_cinderx_dir() / Path("test_compiler/test_static")
691-
)
692-
tests.extend(get_cinderx_static_tests(testdir))
693-
694-
return tests
695-
696709

697710
# TASK(T184566736) Remove this work around for a bug in Buck2 which causes
698711
# us to be the parent of fire-and-forget logging processes.
@@ -856,6 +869,12 @@ def worker_main(args):
856869

857870

858871
def dispatcher_main(args):
872+
if args.list:
873+
tests = list_tests(args.huntrleaks, args.use_rr)
874+
tests.sort()
875+
print(json.dumps(tests, indent=0))
876+
sys.exit(0)
877+
859878
sys.path.insert(0, str(get_cinderx_dir() / "PythonLib"))
860879
libregrtest_setup.setup_process()
861880
pathlib.Path(CINDER_RUNNER_LOG_DIR).mkdir(parents=True, exist_ok=True)
@@ -924,7 +943,7 @@ def main():
924943
default=mem_limit_default,
925944
)
926945

927-
subparsers = parser.add_subparsers()
946+
subparsers = parser.add_subparsers(required=False)
928947

929948
worker_parser = subparsers.add_parser("worker")
930949
worker_parser.add_argument(
@@ -987,6 +1006,13 @@ def main():
9871006
action="append",
9881007
help="The name of a test to run (e.g. `test_math`). Can be supplied multiple times.",
9891008
)
1009+
dispatcher_parser.add_argument(
1010+
"-l",
1011+
"--list",
1012+
action="store_true",
1013+
default=False,
1014+
help="List tests and exit.",
1015+
)
9901016
dispatcher_parser.add_argument(
9911017
"--replay",
9921018
type=str,
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#!/bin/bash
2+
3+
# Verify that the tests.bzl file is up to date and accounts for all of the CPython and cinder tests.
4+
# If the tests diff you can re-run this and cp /tmp/tests.txt to cinderx/tests.bzl
5+
6+
set -e
7+
8+
cd "$(dirname "$(readlink -f "$0")")"/../
9+
10+
buck run fbcode//cinderx:ctl3.10 > /tmp/3.10.txt
11+
buck run fbcode//cinderx:ctl3.12 > /tmp/3.12.txt
12+
buck run fbcode//cinderx:ctl3.14 > /tmp/3.14.txt
13+
14+
{
15+
echo "TESTS = {"
16+
echo \"3.10\":
17+
cat /tmp/3.10.txt
18+
echo ","
19+
echo \"3.12\":
20+
cat /tmp/3.12.txt
21+
echo ","
22+
echo \"3.14\":
23+
cat /tmp/3.14.txt
24+
echo "}"
25+
} > /tmp/tests.txt
26+
pyfmt /tmp/tests.txt
27+
28+
diff /tmp/tests.txt tests.bzl

0 commit comments

Comments
 (0)