Skip to content

Commit 122d7fd

Browse files
authored
Merge pull request #164 from shanejbrown/parallel-tests
Add parallel tests to Github actions workflow
2 parents 4bb7d95 + 6d98dd5 commit 122d7fd

File tree

8 files changed

+115
-19
lines changed

8 files changed

+115
-19
lines changed

.github/workflows/build.yaml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
name: Build
22
on:
3-
- push
4-
- pull_request
3+
# Only run workflow when there is push to main or when there is a pull request to main
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
510
jobs:
611
test:
712
# When running with act (https://github.com/nektos/act), these lines need to be appended with the ACT variable
@@ -50,7 +55,10 @@ jobs:
5055
chmod 700 ~/.ssh
5156
chmod 600 ~/.ssh/buildrunner-deploy-*
5257
- name: Test with pytest
53-
run: pytest -v --junitxml=test-reports/test-results.xml
58+
run: |
59+
pytest -v -m "not serial" --numprocesses=auto --junitxml=test-reports/non-serial-test-results.xml
60+
pytest -v -m "serial" --junitxml=test-reports/serial-test-results.xml
61+
python scripts/combine_xml.py test-reports/serial-test-results.xml test-reports/non-serial-test-results.xml > test-reports/test-result.xml
5462
- name: Publish test results
5563
uses: EnricoMi/publish-unit-test-result-action/linux@v2
5664
if: always()

buildrunner/__init__.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from retry import retry
2727
from vcsinfo import detect_vcs, VCSUnsupported, VCSMissingRevision
28+
from docker.errors import ImageNotFound
2829

2930
from buildrunner import docker, loggers
3031
from buildrunner.config import (
@@ -516,11 +517,16 @@ def run(self): # pylint: disable=too-many-statements,too-many-branches,too-many
516517
# cleanup the source image
517518
if self._source_image:
518519
self.log.write(f"Destroying source image {self._source_image}\n")
519-
_docker_client.remove_image(
520-
self._source_image,
521-
noprune=False,
522-
force=True,
523-
)
520+
try:
521+
_docker_client.remove_image(
522+
self._source_image,
523+
noprune=False,
524+
force=True,
525+
)
526+
except ImageNotFound:
527+
self.log.warning(
528+
f"Failed to remove source image {self._source_image}\n"
529+
)
524530

525531
if self.cleanup_images:
526532
self.log.write("Removing local copy of generated images\n")

pytest.ini

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[pytest]
2+
addopts = --strict-markers
3+
markers =
4+
serial

scripts/combine_xml.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Copyright 2024 Adobe
3+
All Rights Reserved.
4+
5+
NOTICE: Adobe permits you to use, modify, and distribute this file in accordance
6+
with the terms of the Adobe license agreement accompanying it.
7+
"""
8+
9+
import sys
10+
from xml.etree import ElementTree
11+
12+
13+
def run(files):
14+
first = None
15+
for filename in files:
16+
data = ElementTree.parse(filename).getroot()
17+
if first is None:
18+
first = data
19+
else:
20+
first.extend(data)
21+
if first is not None:
22+
print(ElementTree.tostring(first, encoding="unicode"))
23+
24+
25+
if __name__ == "__main__":
26+
run(sys.argv[1:])

tests/test_buildrunner_files.py

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@
1010
TEST_DIR = os.path.dirname(__file__)
1111
top_dir_path = os.path.realpath(os.path.dirname(test_dir_path))
1212

13+
serial_test_files = [
14+
"test-general-buildx.yaml",
15+
"test-general.yaml",
16+
"test-push-artifact-buildx.yaml",
17+
"test-security-scan.yaml",
18+
]
19+
1320

1421
def _get_test_args(file_name: str) -> Optional[List[str]]:
1522
if file_name == "test-timeout.yaml":
@@ -52,14 +59,22 @@ def _get_exit_code(file_name: str) -> int:
5259
return os.EX_OK
5360

5461

55-
def _get_test_runs(test_dir: str) -> List[Tuple[str, str, Optional[List[str]], int]]:
56-
file_names = sorted(
57-
[
58-
file_name
59-
for file_name in os.listdir(test_dir)
60-
if file_name.startswith("test-") and file_name.endswith(".yaml")
61-
]
62-
)
62+
def _get_test_runs(
63+
test_dir: str, serial_tests: bool
64+
) -> List[Tuple[str, str, Optional[List[str]], int]]:
65+
file_names = []
66+
for file_name in os.listdir(test_dir):
67+
if serial_tests:
68+
if file_name in serial_test_files:
69+
file_names.append(file_name)
70+
else:
71+
if (
72+
file_name.startswith("test-")
73+
and file_name.endswith(".yaml")
74+
and file_name not in serial_test_files
75+
):
76+
file_names.append(file_name)
77+
6378
return [
6479
(test_dir, file_name, _get_test_args(file_name), _get_exit_code(file_name))
6580
for file_name in file_names
@@ -107,19 +122,42 @@ def fixture_set_env():
107122

108123

109124
@pytest.mark.parametrize(
110-
"test_dir, file_name, args, exit_code", _get_test_runs(f"{TEST_DIR}/test-files")
125+
"test_dir, file_name, args, exit_code",
126+
_get_test_runs(test_dir=f"{TEST_DIR}/test-files", serial_tests=False),
111127
)
112128
def test_buildrunner_dir(test_dir: str, file_name, args, exit_code):
113129
_test_buildrunner_file(test_dir, file_name, args, exit_code)
114130

115131

132+
@pytest.mark.serial
133+
@pytest.mark.parametrize(
134+
"test_dir, file_name, args, exit_code",
135+
_get_test_runs(test_dir=f"{TEST_DIR}/test-files", serial_tests=True),
136+
)
137+
def test_serial_buildrunner_dir(test_dir: str, file_name, args, exit_code):
138+
_test_buildrunner_file(test_dir, file_name, args, exit_code)
139+
140+
116141
@pytest.mark.skipif(
117142
"arm64" not in platform.uname().machine,
118143
reason="This test should only be run on arm64 architecture",
119144
)
120145
@pytest.mark.parametrize(
121146
"test_dir, file_name, args, exit_code",
122-
_get_test_runs(f"{TEST_DIR}/test-files/arm-arch"),
147+
_get_test_runs(test_dir=f"{TEST_DIR}/test-files/arm-arch", serial_tests=False),
123148
)
124149
def test_buildrunner_arm_dir(test_dir: str, file_name, args, exit_code):
125150
_test_buildrunner_file(test_dir, file_name, args, exit_code)
151+
152+
153+
@pytest.mark.serial
154+
@pytest.mark.skipif(
155+
"arm64" not in platform.uname().machine,
156+
reason="This test should only be run on arm64 architecture",
157+
)
158+
@pytest.mark.parametrize(
159+
"test_dir, file_name, args, exit_code",
160+
_get_test_runs(test_dir=f"{TEST_DIR}/test-files/arm-arch", serial_tests=True),
161+
)
162+
def test_serial_buildrunner_arm_dir(test_dir: str, file_name, args, exit_code):
163+
_test_buildrunner_file(test_dir, file_name, args, exit_code)

tests/test_caching.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def setup_cache_test_files(
119119
return test_files
120120

121121

122+
@pytest.mark.serial
122123
def test_restore_cache_basic(runner, tmp_dir_name, mock_logger, log_output):
123124
"""
124125
Tests basic restore cache functionality
@@ -149,6 +150,7 @@ def test_restore_cache_basic(runner, tmp_dir_name, mock_logger, log_output):
149150
assert f"{file}\n" in output
150151

151152

153+
@pytest.mark.serial
152154
def test_restore_cache_no_cache(runner, mock_logger, log_output):
153155
"""
154156
Tests restore cache when a match is not found
@@ -179,6 +181,7 @@ def test_restore_cache_no_cache(runner, mock_logger, log_output):
179181
assert f"{file}\n" not in output
180182

181183

184+
@pytest.mark.serial
182185
def test_restore_cache_prefix_matching(runner, tmp_dir_name, mock_logger, log_output):
183186
"""
184187
Tests restore cache when there is prefix matching
@@ -218,6 +221,7 @@ def test_restore_cache_prefix_matching(runner, tmp_dir_name, mock_logger, log_ou
218221
assert f"{file}\n" in output
219222

220223

224+
@pytest.mark.serial
221225
def test_restore_cache_prefix_timestamps(runner, tmp_dir_name, mock_logger, log_output):
222226
"""
223227
Tests that when the cache prefix matches it chooses the most recent archive file
@@ -263,6 +267,7 @@ def test_restore_cache_prefix_timestamps(runner, tmp_dir_name, mock_logger, log_
263267
assert f"{file}\n" in output
264268

265269

270+
@pytest.mark.serial
266271
def test_save_cache_basic(runner, tmp_dir_name, mock_logger):
267272
"""
268273
Test basic save cache functionality
@@ -297,6 +302,7 @@ def test_save_cache_basic(runner, tmp_dir_name, mock_logger):
297302
assert file in extracted_files
298303

299304

305+
@pytest.mark.serial
300306
def test_save_cache_multiple_cache_keys(runner, tmp_dir_name, mock_logger):
301307
"""
302308
Test save cache functionality when there are multiple cache keys.
@@ -382,6 +388,7 @@ def test_save_cache_multiple_cache_keys(runner, tmp_dir_name, mock_logger):
382388
assert file in extracted_files
383389

384390

391+
@pytest.mark.serial
385392
def test_save_cache_multiple_caches(runner, tmp_dir_name, mock_logger):
386393
venv_cache_name = "venv"
387394
venv_docker_path = "/root/venv_cache"

tests/test_multiplatform.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,7 @@ def test_tag_native_platform_keep_images(name, platforms, expected_image_tags):
260260
docker.image.remove(name, force=True)
261261

262262

263+
@pytest.mark.serial
263264
def test_push():
264265
try:
265266
with MultiplatformImageBuilder() as remote_mp:
@@ -302,6 +303,7 @@ def test_push():
302303
docker.image.remove(build_name, force=True)
303304

304305

306+
@pytest.mark.serial
305307
def test_push_with_dest_names():
306308
dest_names = None
307309
try:
@@ -349,6 +351,7 @@ def test_push_with_dest_names():
349351
docker.image.remove(dest_name, force=True)
350352

351353

354+
@pytest.mark.serial
352355
@pytest.mark.parametrize(
353356
"name, platforms, expected_image_tags",
354357
[
@@ -402,6 +405,7 @@ def test_build(
402405
), f"Failed to find {missing_images} in {[image.repo for image in built_image.built_images]}"
403406

404407

408+
@pytest.mark.serial
405409
@patch("buildrunner.docker.multiplatform_image_builder.docker.image.remove")
406410
@patch("buildrunner.docker.multiplatform_image_builder.docker.push")
407411
@patch(
@@ -544,6 +548,7 @@ def test_build_multiple_builds(
544548
]
545549

546550

551+
@pytest.mark.serial
547552
@pytest.mark.parametrize(
548553
"builder, cache_builders, return_cache_options",
549554
[
@@ -591,6 +596,7 @@ def test_use_build_registry():
591596
registry_mpib._stop_local_registry()
592597

593598

599+
@pytest.mark.serial
594600
@pytest.mark.parametrize(
595601
"side_effect, expected_call_count",
596602
[
@@ -664,6 +670,7 @@ def test_push_retries(mock_docker, mock_config, side_effect, expected_call_count
664670
assert mock_docker.call_count == expected_call_count
665671

666672

673+
@pytest.mark.serial
667674
@pytest.mark.parametrize(
668675
"tagged_images, expected_call_count",
669676
[

tests/test_push_artifact.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_artifacts_with_legacy_builder(test_name, artifacts_in_file):
123123
)
124124

125125

126-
# Test legacy builder
126+
# Test buildx builder
127127
@pytest.mark.parametrize(
128128
"test_name, artifacts_in_file",
129129
[

0 commit comments

Comments
 (0)