pybuilder-integration 106__tar.gz → 107__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-106 → pybuilder_integration-107}/PKG-INFO +1 -1
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/tasks.py +196 -28
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/PKG-INFO +1 -1
- {pybuilder_integration-106 → pybuilder_integration-107}/setup.py +1 -1
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/__init__.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/artifact_manager.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/cloudwatchlogs_utility.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/directory_utility.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/exec_utility.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/properties.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/tool_utility.py +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/SOURCES.txt +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/dependency_links.txt +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/namespace_packages.txt +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/requires.txt +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/top_level.txt +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/zip-safe +0 -0
- {pybuilder_integration-106 → pybuilder_integration-107}/setup.cfg +0 -0
|
@@ -149,31 +149,40 @@ def _run_verify_environment_passes_in_subprocesses(project: Project, logger: Log
|
|
|
149
149
|
os.remove(properties_file)
|
|
150
150
|
|
|
151
151
|
|
|
152
|
+
_CHILD_OUTPUT_SOURCES = (
|
|
153
|
+
("logs", "logs"),
|
|
154
|
+
("reports", "reports"),
|
|
155
|
+
("reports", "test-reports"),
|
|
156
|
+
)
|
|
157
|
+
_SUBPROCESS_WORKDIR_SKIP = {".pybuilder", "target", ".git", ".nudge-ci-venv"}
|
|
158
|
+
|
|
159
|
+
|
|
152
160
|
def _merge_parallel_verify_environment_outputs(project: Project, passes):
|
|
153
161
|
parent_target = project.expand_path("$dir_target")
|
|
154
162
|
parent_logs_directory = prepare_logs_directory(project)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
163
|
+
parent_destinations = {
|
|
164
|
+
"logs": parent_logs_directory,
|
|
165
|
+
"reports": prepare_reports_directory(project),
|
|
166
|
+
}
|
|
159
167
|
parent_cypress_log = os.path.join(parent_logs_directory, "cypress_run.log")
|
|
160
168
|
has_parent_cypress_log = False
|
|
161
169
|
for pass_config in passes:
|
|
162
170
|
stage_target = os.path.join(parent_target, "verify_environment", pass_config["name"])
|
|
163
|
-
for
|
|
164
|
-
source = os.path.join(stage_target,
|
|
165
|
-
if os.path.exists(source):
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
171
|
+
for destination_kind, source_dirname in _CHILD_OUTPUT_SOURCES:
|
|
172
|
+
source = os.path.join(stage_target, source_dirname, "integration")
|
|
173
|
+
if not os.path.exists(source):
|
|
174
|
+
continue
|
|
175
|
+
destination = os.path.join(
|
|
176
|
+
parent_destinations[destination_kind],
|
|
177
|
+
"parallel",
|
|
178
|
+
pass_config["name"],
|
|
179
|
+
)
|
|
180
|
+
shutil.copytree(source, destination, dirs_exist_ok=True)
|
|
181
|
+
if destination_kind == "logs":
|
|
182
|
+
cypress_log = os.path.join(source, "cypress_run.log")
|
|
183
|
+
if os.path.exists(cypress_log):
|
|
184
|
+
_copy_cypress_log(cypress_log, parent_cypress_log, has_parent_cypress_log)
|
|
185
|
+
has_parent_cypress_log = True
|
|
177
186
|
|
|
178
187
|
|
|
179
188
|
def _copy_cypress_log(source, destination, append):
|
|
@@ -184,12 +193,160 @@ def _copy_cypress_log(source, destination, append):
|
|
|
184
193
|
shutil.copyfileobj(source_file, destination_file)
|
|
185
194
|
|
|
186
195
|
|
|
187
|
-
def
|
|
188
|
-
|
|
189
|
-
|
|
196
|
+
def _verify_environment_subprocess_stage_dir(project: Project, pass_config):
|
|
197
|
+
return os.path.join(
|
|
198
|
+
project.expand_path("$dir_target"),
|
|
199
|
+
"verify_environment",
|
|
200
|
+
pass_config["name"],
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _verify_environment_subprocess_workdir_path(project: Project, pass_config):
|
|
205
|
+
return os.path.join(_verify_environment_subprocess_stage_dir(project, pass_config), "project")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _prepare_verify_environment_subprocess_workdir(project: Project, pass_config):
|
|
209
|
+
stage_dir = _verify_environment_subprocess_stage_dir(project, pass_config)
|
|
210
|
+
workdir = _verify_environment_subprocess_workdir_path(project, pass_config)
|
|
211
|
+
if os.path.exists(stage_dir):
|
|
212
|
+
shutil.rmtree(stage_dir)
|
|
213
|
+
os.makedirs(workdir)
|
|
214
|
+
for name in os.listdir(project.basedir):
|
|
215
|
+
if name in _SUBPROCESS_WORKDIR_SKIP:
|
|
216
|
+
continue
|
|
217
|
+
os.symlink(os.path.join(project.basedir, name), os.path.join(workdir, name))
|
|
218
|
+
_link_parent_target_tree(project, workdir)
|
|
219
|
+
source_plugins = os.path.join(project.basedir, ".pybuilder")
|
|
220
|
+
if os.path.isdir(source_plugins):
|
|
221
|
+
destination_plugins = os.path.join(workdir, ".pybuilder")
|
|
222
|
+
shutil.copytree(source_plugins, destination_plugins, symlinks=True)
|
|
223
|
+
_materialize_external_venv_pythons(os.path.join(destination_plugins, "plugins"))
|
|
224
|
+
return workdir
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def _link_parent_target_tree(project: Project, workdir):
|
|
228
|
+
parent_target = project.expand_path("$dir_target")
|
|
229
|
+
child_target = os.path.join(workdir, "target")
|
|
230
|
+
os.makedirs(child_target, exist_ok=True)
|
|
231
|
+
if not os.path.isdir(parent_target):
|
|
232
|
+
return
|
|
233
|
+
for name in os.listdir(parent_target):
|
|
234
|
+
if name == "verify_environment":
|
|
235
|
+
continue
|
|
236
|
+
os.symlink(os.path.join(parent_target, name), os.path.join(child_target, name))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _is_venv_python_launcher(name):
|
|
240
|
+
if name in {"python", "python3"}:
|
|
241
|
+
return True
|
|
242
|
+
if name.startswith("python3."):
|
|
243
|
+
return name[len("python"):].replace(".", "").isdigit()
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _is_real_directory(path):
|
|
248
|
+
return os.path.isdir(path) and not os.path.islink(path)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _is_path_within(path, root):
|
|
252
|
+
root_real = os.path.realpath(root)
|
|
253
|
+
try:
|
|
254
|
+
return os.path.commonpath([root_real, os.path.realpath(path)]) == root_real
|
|
255
|
+
except ValueError:
|
|
256
|
+
return False
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def _materialize_external_venv_pythons(plugins_root):
|
|
260
|
+
if not _is_real_directory(plugins_root):
|
|
261
|
+
return
|
|
262
|
+
plugins_root_real = os.path.realpath(plugins_root)
|
|
263
|
+
for version_dir in os.listdir(plugins_root):
|
|
264
|
+
version_root = os.path.join(plugins_root, version_dir)
|
|
265
|
+
bin_dir = os.path.join(version_root, "bin")
|
|
266
|
+
if not _is_real_directory(version_root) or not _is_real_directory(bin_dir):
|
|
267
|
+
continue
|
|
268
|
+
bin_dir_real = os.path.realpath(bin_dir)
|
|
269
|
+
if not _is_path_within(bin_dir_real, plugins_root_real):
|
|
270
|
+
continue
|
|
271
|
+
version_root_real = os.path.realpath(version_root)
|
|
272
|
+
for name in os.listdir(bin_dir_real):
|
|
273
|
+
if not _is_venv_python_launcher(name):
|
|
274
|
+
continue
|
|
275
|
+
path = os.path.join(bin_dir_real, name)
|
|
276
|
+
if not os.path.islink(path):
|
|
277
|
+
continue
|
|
278
|
+
target = os.path.realpath(path)
|
|
279
|
+
if not os.path.isfile(target):
|
|
280
|
+
continue
|
|
281
|
+
if _is_path_within(target, version_root_real):
|
|
282
|
+
continue
|
|
283
|
+
# mkstemp uses O_EXCL so a planted *.materialize symlink cannot
|
|
284
|
+
# redirect the write outside the staged workdir.
|
|
285
|
+
fd, temporary_copy = tempfile.mkstemp(
|
|
286
|
+
prefix=f".{name}.",
|
|
287
|
+
suffix=".materialize",
|
|
288
|
+
dir=bin_dir_real,
|
|
289
|
+
)
|
|
290
|
+
try:
|
|
291
|
+
with os.fdopen(fd, "wb") as destination, open(target, "rb") as source:
|
|
292
|
+
shutil.copyfileobj(source, destination)
|
|
293
|
+
destination.flush()
|
|
294
|
+
os.fchmod(destination.fileno(), 0o700)
|
|
295
|
+
os.replace(temporary_copy, path)
|
|
296
|
+
except Exception:
|
|
297
|
+
try:
|
|
298
|
+
os.unlink(temporary_copy)
|
|
299
|
+
except OSError:
|
|
300
|
+
pass
|
|
301
|
+
raise
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def _plugin_python_executable(plugins_root):
|
|
305
|
+
if not os.path.isdir(plugins_root):
|
|
306
|
+
return None
|
|
307
|
+
for version_dir in sorted(os.listdir(plugins_root)):
|
|
308
|
+
candidate = os.path.join(plugins_root, version_dir, "bin", "python")
|
|
309
|
+
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
|
|
310
|
+
return candidate
|
|
311
|
+
return None
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
_PYBUILDER_CLI_MAIN = "import sys; from pybuilder.cli import main; raise SystemExit(main(*sys.argv[1:]))"
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def _isolated_plugin_python_command(plugin_python, args):
|
|
318
|
+
pyb_script = os.path.join(os.path.dirname(plugin_python), "pyb")
|
|
319
|
+
if os.path.isfile(pyb_script):
|
|
320
|
+
return [plugin_python, pyb_script] + args
|
|
321
|
+
return [plugin_python, "-c", _PYBUILDER_CLI_MAIN] + args
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def _uses_isolated_plugin_python(command):
|
|
325
|
+
if len(command) < 2:
|
|
326
|
+
return False
|
|
327
|
+
if command[1] == "-c":
|
|
328
|
+
return len(command) >= 3 and command[2] == _PYBUILDER_CLI_MAIN
|
|
329
|
+
return os.path.basename(command[1]) == "pyb"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _verify_environment_subprocess_env(workdir, command, properties_file):
|
|
190
333
|
env = os.environ.copy()
|
|
191
334
|
env[SUBPROCESS_PROPERTIES_FILE_ENV] = properties_file
|
|
192
|
-
|
|
335
|
+
env.pop("GITHUB_STEP_SUMMARY", None)
|
|
336
|
+
env.pop("PYTHONHOME", None)
|
|
337
|
+
env.pop("VIRTUAL_ENV", None)
|
|
338
|
+
env.pop("PYTHONPATH", None)
|
|
339
|
+
return env
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
def _start_verify_environment_subprocess(project: Project, logger: Logger, pass_config, properties_file):
|
|
343
|
+
log_file = os.path.join(prepare_logs_directory(project), f"verify_environment_{pass_config['name']}.log")
|
|
344
|
+
workdir = _prepare_verify_environment_subprocess_workdir(project, pass_config)
|
|
345
|
+
command = _build_verify_environment_subprocess_command(project, pass_config, workdir)
|
|
346
|
+
env = _verify_environment_subprocess_env(workdir, command, properties_file)
|
|
347
|
+
logger.info(
|
|
348
|
+
f"Starting {pass_config['name']} verify_environment subprocess in {workdir}: {' '.join(command)}"
|
|
349
|
+
)
|
|
193
350
|
log_handle = os.fdopen(
|
|
194
351
|
os.open(log_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600),
|
|
195
352
|
"w",
|
|
@@ -199,7 +356,7 @@ def _start_verify_environment_subprocess(project: Project, logger: Logger, pass_
|
|
|
199
356
|
process = subprocess.Popen(command,
|
|
200
357
|
stdout=log_handle,
|
|
201
358
|
stderr=subprocess.STDOUT,
|
|
202
|
-
cwd=
|
|
359
|
+
cwd=workdir,
|
|
203
360
|
env=env,
|
|
204
361
|
shell=False)
|
|
205
362
|
except Exception:
|
|
@@ -209,18 +366,29 @@ def _start_verify_environment_subprocess(project: Project, logger: Logger, pass_
|
|
|
209
366
|
return process, log_file
|
|
210
367
|
|
|
211
368
|
|
|
212
|
-
def _build_verify_environment_subprocess_command(project: Project, pass_config):
|
|
213
|
-
stage_target =
|
|
214
|
-
|
|
215
|
-
"
|
|
369
|
+
def _build_verify_environment_subprocess_command(project: Project, pass_config, workdir=None):
|
|
370
|
+
stage_target = _verify_environment_subprocess_stage_dir(project, pass_config)
|
|
371
|
+
args = [
|
|
372
|
+
"--no-venvs",
|
|
216
373
|
pass_config["task"],
|
|
217
374
|
"-P", f"{WORKING_TEST_DIR}={pass_config['test_dir']}",
|
|
218
375
|
"-P", f"{PARALLEL_VERIFY_ENVIRONMENT}=False",
|
|
219
376
|
"-P", f"{PROMOTE_ARTIFACT}=False",
|
|
220
377
|
"-P", f"dir_target={stage_target}",
|
|
221
378
|
"-P", f"dir_logs={os.path.join(stage_target, 'logs')}",
|
|
222
|
-
"-P", f"dir_reports={os.path.join(stage_target, 'reports')}",
|
|
379
|
+
"-P", f"dir_reports={os.path.join(stage_target, 'test-reports')}",
|
|
223
380
|
]
|
|
381
|
+
if not workdir:
|
|
382
|
+
raise BuildFailedException(
|
|
383
|
+
f"Cannot start isolated verify_environment subprocess for {pass_config['name']}: workdir is required"
|
|
384
|
+
)
|
|
385
|
+
plugin_python = _plugin_python_executable(os.path.join(workdir, ".pybuilder", "plugins"))
|
|
386
|
+
if not plugin_python:
|
|
387
|
+
raise BuildFailedException(
|
|
388
|
+
f"Cannot start isolated verify_environment subprocess for {pass_config['name']}: "
|
|
389
|
+
"copied plugin python is missing or not executable"
|
|
390
|
+
)
|
|
391
|
+
return _isolated_plugin_python_command(plugin_python, args)
|
|
224
392
|
|
|
225
393
|
|
|
226
394
|
def _write_subprocess_properties_file(project: Project):
|
|
@@ -21,7 +21,7 @@ class install(_install):
|
|
|
21
21
|
if __name__ == '__main__':
|
|
22
22
|
setup(
|
|
23
23
|
name = 'pybuilder-integration',
|
|
24
|
-
version = '
|
|
24
|
+
version = '107',
|
|
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-106 → pybuilder_integration-107}/pybuilder_integration/artifact_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/directory_utility.py
RENAMED
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/exec_utility.py
RENAMED
|
File without changes
|
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration/tool_utility.py
RENAMED
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/requires.txt
RENAMED
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/top_level.txt
RENAMED
|
File without changes
|
{pybuilder_integration-106 → pybuilder_integration-107}/pybuilder_integration.egg-info/zip-safe
RENAMED
|
File without changes
|
|
File without changes
|