Skip to content

Commit bbcbab0

Browse files
Distribute large integration test files across workers (#10761)
Add a --dist option to scripts/ci/run-tests and run the integration tests with --dist loadgroup. Tests are grouped per file as before, except the large offline skeleton tests, which are distributed per test, and the S3 plugin tests, which are distributed per class.
1 parent 01229e4 commit bbcbab0

3 files changed

Lines changed: 61 additions & 4 deletions

File tree

‎scripts/ci/run-integ-tests‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,6 @@ def run(command):
3131

3232
if __name__ == "__main__":
3333
with cd(os.path.join(REPO_ROOT, "tests")):
34-
run(f"{REPO_ROOT}/scripts/ci/run-tests integration")
34+
# Tests are grouped per file (see tests/integration/conftest.py) except for
35+
# large offline test files, which are distributed per test.
36+
run(f"{REPO_ROOT}/scripts/ci/run-tests --dist loadgroup integration")

‎scripts/ci/run-tests‎

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,10 @@ def process_args(args):
5959
elif os.getenv('GITHUB_ACTIONS', 'false') == 'true':
6060
# Limiting parallelism in GitHub, to avoid the
6161
# 'Error: The operation was canceled.' issues in 2026
62-
max_processes=3
62+
max_processes = 3
6363
else:
64-
max_processes=4
65-
test_args += f'--numprocesses=auto --dist=loadfile --maxprocesses={max_processes} '
64+
max_processes = 4
65+
test_args += f'--numprocesses=auto --dist={args.dist} --maxprocesses={max_processes} '
6666
dirs = " ".join(args.test_dirs)
6767

6868
return runner, test_args, dirs
@@ -106,6 +106,14 @@ if __name__ == "__main__":
106106
default=[],
107107
help=("Ignore a test subdirectory. Can be specified multiple times."),
108108
)
109+
parser.add_argument(
110+
"--dist",
111+
default="loadfile",
112+
help=(
113+
"The pytest-xdist distribution mode to use. Defaults to "
114+
"loadfile, which runs all tests in a file on the same worker."
115+
),
116+
)
109117
parser.add_argument(
110118
"--tests-path",
111119
default=None,

‎tests/integration/conftest.py‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License"). You
4+
# may not use this file except in compliance with the License. A copy of
5+
# the License is located at
6+
#
7+
# http://aws.amazon.com/apache2.0/
8+
#
9+
# or in the "license" file accompanying this file. This file is
10+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11+
# ANY KIND, either express or implied. See the License for the specific
12+
# language governing permissions and limitations under the License.
13+
import pytest
14+
15+
# These files only exercise local service models (no AWS calls) and are
16+
# parametrized over every CLI command or operation, so they contain most of
17+
# the integration tests. They are distributed across workers per test
18+
# instead of running every test in the file on a single worker.
19+
_DISTRIBUTE_PER_TEST = (
20+
'integration/customizations/test_generatecliskeleton.py',
21+
'integration/botocore/test_utils.py',
22+
)
23+
24+
# These files contain many slow, independent test classes, so they are
25+
# distributed across workers per class instead of per file. Their shared
26+
# fixtures (e.g. S3 buckets) are package scoped, so each worker creates its
27+
# own.
28+
_DISTRIBUTE_PER_CLASS = ('integration/customizations/s3/test_plugin.py',)
29+
30+
31+
@pytest.hookimpl(tryfirst=True)
32+
def pytest_collection_modifyitems(items):
33+
# With ``--dist loadgroup``, tests in the same ``xdist_group`` run on the
34+
# same worker and ungrouped tests are distributed individually. Grouping
35+
# the remaining tests by file preserves the ``--dist loadfile`` behavior
36+
# they rely on, such as sharing module and class scoped fixtures that
37+
# create AWS resources.
38+
for item in items:
39+
parts = item.nodeid.split('::')
40+
path = parts[0]
41+
if path.endswith(_DISTRIBUTE_PER_TEST):
42+
continue
43+
if path.endswith(_DISTRIBUTE_PER_CLASS) and len(parts) > 2:
44+
group = '::'.join(parts[:2])
45+
else:
46+
group = path
47+
item.add_marker(pytest.mark.xdist_group(group))

0 commit comments

Comments
 (0)