mirror of
https://github.com/esphome/esphome.git
synced 2026-09-30 08:20:21 +00:00
[ci] Split integration tests into 3 buckets when count > 10
When more than 10 integration tests are scheduled (or any change that
triggers run_all, e.g. core/infra changes that would run all 117 files),
fan out the pytest job into 3 parallel matrix entries. Below the
threshold, a single bucket runs as before, so small targeted PRs see no
extra job overhead.
determine-jobs.py now owns the bucketing end-to-end: it expands run_all
into the explicit glob of tests/integration/test_*.py and pre-splits the
sorted list using the same balanced contiguous-partition formula as
script/clang-tidy. The CI workflow consumes the precomputed buckets via
fromJson() in the matrix, mirroring how component-test-batches works,
so no shell-side splitting is needed.
The previous integration-tests-run-all and integration-test-files
workflow outputs are replaced by a single integration-test-buckets
list-of-objects ({name, tests}); the integration-tests gate boolean is
unchanged.
This commit is contained in:
@@ -6,8 +6,7 @@ what files have changed. It outputs JSON with the following structure:
|
||||
|
||||
{
|
||||
"integration_tests": true/false,
|
||||
"integration_tests_run_all": true/false,
|
||||
"integration_test_files": ["tests/integration/test_foo.py", ...],
|
||||
"integration_test_buckets": [{"name": "1/3", "tests": "tests/integration/test_foo.py ..."}, ...],
|
||||
"clang_tidy": true/false,
|
||||
"clang_format": true/false,
|
||||
"python_linters": true/false,
|
||||
@@ -81,6 +80,25 @@ CLANG_TIDY_SPLIT_THRESHOLD = 65
|
||||
# Isolated components count as 10x, groupable components count as 1x
|
||||
COMPONENT_TEST_BATCH_SIZE = 40
|
||||
|
||||
# Integration test bucketing: when more than the threshold tests are scheduled,
|
||||
# fan out across this many parallel jobs. Below the threshold, a single job runs.
|
||||
INTEGRATION_TESTS_SPLIT_THRESHOLD = 10
|
||||
INTEGRATION_TESTS_SPLIT_BUCKETS = 3
|
||||
|
||||
|
||||
def _split_list(items: list[str], n: int) -> list[list[str]]:
|
||||
"""Split a list into n roughly-equal contiguous parts (matches script/clang-tidy)."""
|
||||
k, m = divmod(len(items), n)
|
||||
return [items[i * k + min(i, m) : (i + 1) * k + min(i + 1, m)] for i in range(n)]
|
||||
|
||||
|
||||
def _all_integration_test_files() -> list[str]:
|
||||
"""Return all integration test file paths, sorted, relative to repo root."""
|
||||
return sorted(
|
||||
str(p.relative_to(root_path))
|
||||
for p in (Path(root_path) / "tests" / "integration").glob("test_*.py")
|
||||
)
|
||||
|
||||
|
||||
class Platform(StrEnum):
|
||||
"""Platform identifiers for memory impact analysis."""
|
||||
@@ -813,6 +831,36 @@ def main() -> None:
|
||||
args.branch
|
||||
)
|
||||
run_integration = integration_run_all or bool(integration_test_files)
|
||||
|
||||
# When run_all is set, expand to the full glob here so determine-jobs.py
|
||||
# remains the single source of truth for which tests run. The workflow
|
||||
# never re-globs the filesystem.
|
||||
if integration_run_all:
|
||||
integration_test_files = _all_integration_test_files()
|
||||
else:
|
||||
integration_test_files = sorted(integration_test_files)
|
||||
|
||||
# Pre-bucket the test list so the CI matrix can consume it directly.
|
||||
# Below threshold => 1 bucket; above threshold => INTEGRATION_TESTS_SPLIT_BUCKETS.
|
||||
integration_test_buckets: list[dict[str, str]]
|
||||
if not run_integration:
|
||||
integration_test_buckets = []
|
||||
elif len(integration_test_files) > INTEGRATION_TESTS_SPLIT_THRESHOLD:
|
||||
parts = [
|
||||
part
|
||||
for part in _split_list(
|
||||
integration_test_files, INTEGRATION_TESTS_SPLIT_BUCKETS
|
||||
)
|
||||
if part
|
||||
]
|
||||
integration_test_buckets = [
|
||||
{"name": f"{i + 1}/{len(parts)}", "tests": " ".join(part)}
|
||||
for i, part in enumerate(parts)
|
||||
]
|
||||
else:
|
||||
integration_test_buckets = [
|
||||
{"name": "1/1", "tests": " ".join(integration_test_files)}
|
||||
]
|
||||
run_clang_tidy = should_run_clang_tidy(args.branch)
|
||||
run_clang_format = should_run_clang_format(args.branch)
|
||||
run_python_linters = should_run_python_linters(args.branch)
|
||||
@@ -944,8 +992,7 @@ def main() -> None:
|
||||
|
||||
output: dict[str, Any] = {
|
||||
"integration_tests": run_integration,
|
||||
"integration_tests_run_all": integration_run_all,
|
||||
"integration_test_files": integration_test_files,
|
||||
"integration_test_buckets": integration_test_buckets,
|
||||
"clang_tidy": run_clang_tidy,
|
||||
"clang_tidy_mode": clang_tidy_mode,
|
||||
"clang_format": run_clang_format,
|
||||
|
||||
Reference in New Issue
Block a user