pybuilder-integration 109__tar.gz → 110__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pybuilder_integration-109 → pybuilder_integration-110}/PKG-INFO +1 -1
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/tasks.py +111 -15
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/PKG-INFO +1 -1
- {pybuilder_integration-109 → pybuilder_integration-110}/setup.py +1 -1
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/__init__.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/artifact_manager.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/cloudwatchlogs_utility.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/directory_utility.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/exec_utility.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/properties.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/tool_utility.py +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/SOURCES.txt +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/dependency_links.txt +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/namespace_packages.txt +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/requires.txt +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/top_level.txt +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/zip-safe +0 -0
- {pybuilder_integration-109 → pybuilder_integration-110}/setup.cfg +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import atexit
|
|
2
2
|
import errno
|
|
3
|
+
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
@@ -957,19 +958,33 @@ def _run_tavern_suites_parallel(suite_dirs, logger, project, reactor):
|
|
|
957
958
|
)
|
|
958
959
|
return
|
|
959
960
|
|
|
960
|
-
suite_sites = {}
|
|
961
|
-
for tavern_test_directory in runnable:
|
|
962
|
-
suite_sites[tavern_test_directory] = _install_tavern_suite_site(
|
|
963
|
-
tavern_test_directory, logger, project, reactor
|
|
964
|
-
)
|
|
965
|
-
|
|
966
961
|
workers = _parallel_tavern_suite_workers(project, len(runnable))
|
|
967
962
|
logger.info(f"Running {len(runnable)} tavern suites in parallel (workers={workers})")
|
|
963
|
+
fingerprint_by_dir = {suite_dir: _tavern_requirements_fingerprint(suite_dir) for suite_dir in runnable}
|
|
964
|
+
plugin_fingerprint = _plugin_venv_requirements_fingerprint(fingerprint_by_dir)
|
|
965
|
+
installer = _ParallelTavernRequirementInstaller(plugin_fingerprint)
|
|
966
|
+
if plugin_fingerprint is not None:
|
|
967
|
+
sample_dir = next(
|
|
968
|
+
suite_dir for suite_dir, fingerprint in fingerprint_by_dir.items()
|
|
969
|
+
if fingerprint == plugin_fingerprint
|
|
970
|
+
)
|
|
971
|
+
logger.info(
|
|
972
|
+
f"Installing tavern requirements from {os.path.basename(sample_dir)} "
|
|
973
|
+
f"into the plugin venv before parallel pytest"
|
|
974
|
+
)
|
|
975
|
+
installer.install(sample_dir, plugin_fingerprint, logger, project, reactor)
|
|
968
976
|
failures = []
|
|
969
977
|
|
|
970
978
|
def run_suite(tavern_test_directory):
|
|
971
979
|
role = os.path.basename(tavern_test_directory)
|
|
972
980
|
logger.info(f"Running {tavern_test_directory}")
|
|
981
|
+
site = installer.install(
|
|
982
|
+
tavern_test_directory,
|
|
983
|
+
fingerprint_by_dir[tavern_test_directory],
|
|
984
|
+
logger,
|
|
985
|
+
project,
|
|
986
|
+
reactor,
|
|
987
|
+
)
|
|
973
988
|
_run_tavern_tests_in_dir(
|
|
974
989
|
test_dir=tavern_test_directory,
|
|
975
990
|
logger=logger,
|
|
@@ -978,7 +993,7 @@ def _run_tavern_suites_parallel(suite_dirs, logger, project, reactor):
|
|
|
978
993
|
role=role,
|
|
979
994
|
isolated=True,
|
|
980
995
|
skip_install=True,
|
|
981
|
-
extra_pythonpath=
|
|
996
|
+
extra_pythonpath=site,
|
|
982
997
|
)
|
|
983
998
|
return role
|
|
984
999
|
|
|
@@ -998,6 +1013,71 @@ def _run_tavern_suites_parallel(suite_dirs, logger, project, reactor):
|
|
|
998
1013
|
)
|
|
999
1014
|
|
|
1000
1015
|
|
|
1016
|
+
class _ParallelTavernRequirementInstaller:
|
|
1017
|
+
def __init__(self, plugin_venv_fingerprint):
|
|
1018
|
+
self._plugin_venv_fingerprint = plugin_venv_fingerprint
|
|
1019
|
+
self._lock = threading.Lock()
|
|
1020
|
+
self._events = {}
|
|
1021
|
+
self._results = {}
|
|
1022
|
+
self._errors = {}
|
|
1023
|
+
|
|
1024
|
+
def install(self, test_dir, fingerprint, logger, project, reactor):
|
|
1025
|
+
if fingerprint is None:
|
|
1026
|
+
return None
|
|
1027
|
+
with self._lock:
|
|
1028
|
+
if fingerprint in self._results:
|
|
1029
|
+
return self._results[fingerprint]
|
|
1030
|
+
if fingerprint in self._errors:
|
|
1031
|
+
raise self._errors[fingerprint]
|
|
1032
|
+
event = self._events.get(fingerprint)
|
|
1033
|
+
if event is None:
|
|
1034
|
+
event = threading.Event()
|
|
1035
|
+
self._events[fingerprint] = event
|
|
1036
|
+
leader = True
|
|
1037
|
+
else:
|
|
1038
|
+
leader = False
|
|
1039
|
+
if not leader:
|
|
1040
|
+
event.wait()
|
|
1041
|
+
with self._lock:
|
|
1042
|
+
if fingerprint in self._errors:
|
|
1043
|
+
raise self._errors[fingerprint]
|
|
1044
|
+
if fingerprint in self._results:
|
|
1045
|
+
return self._results[fingerprint]
|
|
1046
|
+
raise BuildFailedException(
|
|
1047
|
+
f"Tavern requirements install for {os.path.basename(test_dir)} finished without a result"
|
|
1048
|
+
)
|
|
1049
|
+
try:
|
|
1050
|
+
if fingerprint == self._plugin_venv_fingerprint:
|
|
1051
|
+
_install_tavern_requirements(test_dir, logger, project, reactor)
|
|
1052
|
+
site = None
|
|
1053
|
+
else:
|
|
1054
|
+
site = _install_tavern_suite_site(test_dir, logger, project, reactor, fingerprint)
|
|
1055
|
+
with self._lock:
|
|
1056
|
+
self._results[fingerprint] = site
|
|
1057
|
+
return site
|
|
1058
|
+
except Exception as error:
|
|
1059
|
+
with self._lock:
|
|
1060
|
+
self._errors[fingerprint] = error
|
|
1061
|
+
raise
|
|
1062
|
+
finally:
|
|
1063
|
+
event.set()
|
|
1064
|
+
|
|
1065
|
+
|
|
1066
|
+
def _plugin_venv_requirements_fingerprint(fingerprint_by_dir):
|
|
1067
|
+
counts = {}
|
|
1068
|
+
order = []
|
|
1069
|
+
for fingerprint in fingerprint_by_dir.values():
|
|
1070
|
+
if fingerprint is None:
|
|
1071
|
+
continue
|
|
1072
|
+
if fingerprint not in counts:
|
|
1073
|
+
order.append(fingerprint)
|
|
1074
|
+
counts[fingerprint] = 0
|
|
1075
|
+
counts[fingerprint] += 1
|
|
1076
|
+
if not order:
|
|
1077
|
+
return None
|
|
1078
|
+
return max(order, key=lambda fingerprint: (counts[fingerprint], -order.index(fingerprint)))
|
|
1079
|
+
|
|
1080
|
+
|
|
1001
1081
|
def verify_cypress(project: Project, logger: Logger, reactor: Reactor):
|
|
1002
1082
|
# Get directories with test and cypress executable
|
|
1003
1083
|
work_dir = project.expand_path(f"${CYPRESS_TEST_DIR}")
|
|
@@ -1227,10 +1307,18 @@ def _plugin_venv_python(reactor):
|
|
|
1227
1307
|
raise BuildFailedException("plugin venv python is missing or not executable")
|
|
1228
1308
|
|
|
1229
1309
|
|
|
1230
|
-
def
|
|
1310
|
+
def _tavern_requirements_fingerprint(test_dir):
|
|
1311
|
+
requirements_file = os.path.join(test_dir, "requirements.txt")
|
|
1312
|
+
if not os.path.isfile(requirements_file):
|
|
1313
|
+
return None
|
|
1314
|
+
with open(requirements_file, "rb") as requirements:
|
|
1315
|
+
return hashlib.sha256(requirements.read()).hexdigest()
|
|
1316
|
+
|
|
1317
|
+
|
|
1318
|
+
def _tavern_suite_site_directory(project, fingerprint):
|
|
1231
1319
|
root = project.expand_path("$dir_target/integration/tavern-sites")
|
|
1232
1320
|
directory_utility._ensure_directory_exists(root)
|
|
1233
|
-
site = os.path.join(root,
|
|
1321
|
+
site = os.path.join(root, fingerprint)
|
|
1234
1322
|
if os.path.islink(site) or os.path.isfile(site):
|
|
1235
1323
|
os.unlink(site)
|
|
1236
1324
|
elif os.path.isdir(site):
|
|
@@ -1239,37 +1327,45 @@ def _tavern_suite_site_directory(project, role):
|
|
|
1239
1327
|
return site
|
|
1240
1328
|
|
|
1241
1329
|
|
|
1330
|
+
def _tavern_pip_cache_directory(project, fingerprint):
|
|
1331
|
+
cache = os.path.join(project.expand_path("$dir_target/integration/tavern-pip-cache"), fingerprint)
|
|
1332
|
+
return directory_utility._ensure_directory_exists(cache)
|
|
1333
|
+
|
|
1334
|
+
|
|
1242
1335
|
def _isolated_suite_subprocess_env(extra_pythonpath=None):
|
|
1243
1336
|
env = os.environ.copy()
|
|
1244
1337
|
env.pop("GITHUB_STEP_SUMMARY", None)
|
|
1245
1338
|
env.pop("PYTHONHOME", None)
|
|
1246
1339
|
env.pop("VIRTUAL_ENV", None)
|
|
1247
1340
|
env.pop("PYTHONPATH", None)
|
|
1341
|
+
env["PYTHONDONTWRITEBYTECODE"] = "1"
|
|
1248
1342
|
if extra_pythonpath:
|
|
1249
1343
|
env["PYTHONPATH"] = extra_pythonpath
|
|
1250
1344
|
return env
|
|
1251
1345
|
|
|
1252
1346
|
|
|
1253
|
-
def _install_tavern_suite_site(test_dir, logger, project, reactor):
|
|
1347
|
+
def _install_tavern_suite_site(test_dir, logger, project, reactor, fingerprint):
|
|
1254
1348
|
requirements_file = os.path.join(test_dir, "requirements.txt")
|
|
1255
|
-
if not os.path.
|
|
1349
|
+
if not os.path.isfile(requirements_file):
|
|
1256
1350
|
return None
|
|
1257
1351
|
role = os.path.basename(test_dir)
|
|
1258
|
-
site = _tavern_suite_site_directory(project,
|
|
1352
|
+
site = _tavern_suite_site_directory(project, fingerprint)
|
|
1353
|
+
env = _isolated_suite_subprocess_env()
|
|
1354
|
+
env["PIP_CACHE_DIR"] = _tavern_pip_cache_directory(project, fingerprint)
|
|
1259
1355
|
command = [_plugin_venv_python(reactor), "-m", "pip", "install", "-r", requirements_file, "-t", site]
|
|
1260
|
-
logger.info(f"Installing tavern requirements
|
|
1356
|
+
logger.info(f"Installing tavern requirements from {role} into isolated site {site}")
|
|
1261
1357
|
process = subprocess.Popen(
|
|
1262
1358
|
command,
|
|
1263
1359
|
stdout=subprocess.PIPE,
|
|
1264
1360
|
stderr=subprocess.STDOUT,
|
|
1265
1361
|
cwd=test_dir,
|
|
1266
|
-
env=
|
|
1362
|
+
env=env,
|
|
1267
1363
|
shell=False,
|
|
1268
1364
|
)
|
|
1269
1365
|
returncode = _stream_subprocess_output(process, logger, role)
|
|
1270
1366
|
if returncode != 0:
|
|
1271
1367
|
raise BuildFailedException(
|
|
1272
|
-
f"Failed to install tavern requirements
|
|
1368
|
+
f"Failed to install tavern requirements from {role} into isolated site {site}"
|
|
1273
1369
|
)
|
|
1274
1370
|
return site
|
|
1275
1371
|
|
|
@@ -21,7 +21,7 @@ class install(_install):
|
|
|
21
21
|
if __name__ == '__main__':
|
|
22
22
|
setup(
|
|
23
23
|
name = 'pybuilder-integration',
|
|
24
|
-
version = '
|
|
24
|
+
version = '110',
|
|
25
25
|
description = 'A pybuilder plugin that runs integration tests (Tavern & Cypress) against a target.',
|
|
26
26
|
long_description = 'A pybuilder plugin that runs integration tests against a target. This is intended to be a broader scope than unit-tests encompassing dependant functionality.',
|
|
27
27
|
long_description_content_type = None,
|
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/artifact_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/directory_utility.py
RENAMED
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/exec_utility.py
RENAMED
|
File without changes
|
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration/tool_utility.py
RENAMED
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/requires.txt
RENAMED
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/top_level.txt
RENAMED
|
File without changes
|
{pybuilder_integration-109 → pybuilder_integration-110}/pybuilder_integration.egg-info/zip-safe
RENAMED
|
File without changes
|
|
File without changes
|