pybuilder-integration 104__tar.gz → 105__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.
Files changed (19) hide show
  1. {pybuilder_integration-104 → pybuilder_integration-105}/PKG-INFO +1 -1
  2. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/artifact_manager.py +9 -1
  3. pybuilder_integration-105/pybuilder_integration/directory_utility.py +168 -0
  4. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/properties.py +1 -0
  5. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/tasks.py +26 -3
  6. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/PKG-INFO +1 -1
  7. {pybuilder_integration-104 → pybuilder_integration-105}/setup.py +1 -1
  8. pybuilder_integration-104/pybuilder_integration/directory_utility.py +0 -70
  9. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/__init__.py +0 -0
  10. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/cloudwatchlogs_utility.py +0 -0
  11. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/exec_utility.py +0 -0
  12. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration/tool_utility.py +0 -0
  13. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/SOURCES.txt +0 -0
  14. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/dependency_links.txt +0 -0
  15. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/namespace_packages.txt +0 -0
  16. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/requires.txt +0 -0
  17. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/top_level.txt +0 -0
  18. {pybuilder_integration-104 → pybuilder_integration-105}/pybuilder_integration.egg-info/zip-safe +0 -0
  19. {pybuilder_integration-104 → pybuilder_integration-105}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pybuilder-integration
3
- Version: 104
3
+ Version: 105
4
4
  Summary: A pybuilder plugin that runs integration tests (Tavern & Cypress) against a target.
5
5
  Home-page: https://github.com/rspitler/pybuilder-integration
6
6
  Author:
@@ -8,7 +8,7 @@ from pybuilder.reactor import Reactor
8
8
 
9
9
  from pybuilder_integration import exec_utility
10
10
  from pybuilder_integration.directory_utility import get_latest_distribution_directory, \
11
- get_latest_zipped_distribution_directory
11
+ get_latest_zipped_distribution_directory, read_tavern_service_markers, write_tavern_manifest
12
12
  from pybuilder_integration.properties import *
13
13
 
14
14
 
@@ -200,13 +200,21 @@ def _unzip_downloaded_artifacts(dir_with_zips: str, destination: str, logger: Lo
200
200
  os.makedirs(consolidated_folder, exist_ok=True)
201
201
  logger.debug(f"Creating role file for log retrieval {consolidated_folder}/roles")
202
202
  open(f"{consolidated_folder}/roles", 'w').close()
203
+ consolidated_markers = set()
204
+ has_tavern_manifest = False
203
205
  for dirn in os.listdir(f"{destination}/tavern"):
204
206
  if "consolidated" != dirn:
205
207
  logger.debug(f"Consolidating directory: {dirn}")
208
+ markers = read_tavern_service_markers(f"{destination}/tavern/{dirn}")
209
+ if markers is not None:
210
+ has_tavern_manifest = True
211
+ consolidated_markers.update(markers)
206
212
  shutil.copytree(f"{destination}/tavern/{dirn}", consolidated_folder, dirs_exist_ok=True)
207
213
  shutil.rmtree(f"{destination}/tavern/{dirn}")
208
214
  with open(f"{consolidated_folder}/roles", "a") as fp:
209
215
  fp.write(f"{dirn}\n")
216
+ if has_tavern_manifest:
217
+ write_tavern_manifest(consolidated_folder, consolidated_markers)
210
218
  return destination
211
219
 
212
220
 
@@ -0,0 +1,168 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import shutil
5
+ import tempfile
6
+
7
+
8
+ TAVERN_MANIFEST_DIRECTORY = ".pybuilder-integration"
9
+ TAVERN_MANIFEST_FILENAME = "tavern-manifest.json"
10
+ _TAVERN_MARKER_EXCLUSIONS = {"parametrize", "skip", "skipif", "usefixtures", "xfail"}
11
+
12
+
13
+ def prepare_reports_directory(project):
14
+ return prepare_directory("$dir_reports", project)
15
+
16
+
17
+ def prepare_logs_directory(project):
18
+ return prepare_directory("$dir_logs", project)
19
+
20
+
21
+ def prepare_dist_directory(project):
22
+ return prepare_directory("$dir_dist", project)
23
+
24
+
25
+ def prepare_directory(dir_variable, project):
26
+ package__format = f"{dir_variable}/integration"
27
+ reports_dir = project.expand_path(package__format)
28
+ return _ensure_directory_exists(reports_dir)
29
+
30
+
31
+ def _ensure_directory_exists(path):
32
+ if not os.path.exists(path):
33
+ os.makedirs(path)
34
+ return path
35
+
36
+
37
+ def get_working_distribution_directory(project):
38
+ dist_directory = prepare_dist_directory(project)
39
+ return _ensure_directory_exists(f"{dist_directory}/working")
40
+
41
+
42
+ def get_latest_distribution_directory(project):
43
+ dist_directory = prepare_dist_directory(project)
44
+ from pybuilder_integration import ENVIRONMENT
45
+ environment = project.get_mandatory_property(ENVIRONMENT)
46
+ return _ensure_directory_exists(f"{dist_directory}/LATEST-{environment}")
47
+
48
+
49
+ def get_latest_zipped_distribution_directory(project):
50
+ dist_directory = prepare_dist_directory(project)
51
+ from pybuilder_integration import ENVIRONMENT
52
+ environment = project.get_mandatory_property(ENVIRONMENT)
53
+ return _ensure_directory_exists(f"{dist_directory}/LATEST-{environment}/zipped")
54
+
55
+
56
+ def get_local_zip_artifact_path(tool, project, include_ending=False):
57
+ artifact = f"{prepare_dist_directory(project)}/{tool}-{project.name}"
58
+ if include_ending:
59
+ return f"{artifact}.zip"
60
+ return artifact
61
+
62
+
63
+ def get_tavern_manifest_path(test_dir):
64
+ return os.path.join(test_dir, TAVERN_MANIFEST_DIRECTORY, TAVERN_MANIFEST_FILENAME)
65
+
66
+
67
+ def collect_tavern_service_markers(test_dir):
68
+ markers = set()
69
+ for root, _, files in os.walk(test_dir):
70
+ for filename in files:
71
+ if filename.endswith(".tavern.yaml"):
72
+ markers.update(_collect_markers_from_tavern_file(os.path.join(root, filename)))
73
+ return sorted(markers)
74
+
75
+
76
+ def write_tavern_manifest(test_dir, service_markers=None):
77
+ manifest_path = get_tavern_manifest_path(test_dir)
78
+ manifest_directory = os.path.dirname(manifest_path)
79
+ os.makedirs(manifest_directory, exist_ok=True)
80
+ if service_markers is None:
81
+ service_markers = collect_tavern_service_markers(test_dir)
82
+ manifest_content = json.dumps({"service_markers": sorted(service_markers)}).encode("utf-8")
83
+ descriptor, temporary_path = tempfile.mkstemp(prefix=".tavern-manifest-", dir=manifest_directory)
84
+ try:
85
+ bytes_written = 0
86
+ while bytes_written < len(manifest_content):
87
+ written = os.write(descriptor, manifest_content[bytes_written:])
88
+ if written == 0:
89
+ raise OSError("Could not write Tavern marker manifest")
90
+ bytes_written += written
91
+ os.close(descriptor)
92
+ descriptor = None
93
+ os.replace(temporary_path, manifest_path)
94
+ finally:
95
+ if descriptor is not None:
96
+ os.close(descriptor)
97
+ if os.path.exists(temporary_path):
98
+ os.unlink(temporary_path)
99
+
100
+
101
+ def read_tavern_service_markers(test_dir):
102
+ manifest_path = get_tavern_manifest_path(test_dir)
103
+ if not os.path.exists(manifest_path):
104
+ return None
105
+ try:
106
+ with open(manifest_path) as manifest_file:
107
+ manifest = json.load(manifest_file)
108
+ except (OSError, ValueError):
109
+ return None
110
+ markers = manifest.get("service_markers", [])
111
+ if not isinstance(markers, list) or not all(isinstance(marker, str) for marker in markers):
112
+ return None
113
+ return set(markers)
114
+
115
+
116
+ def _collect_markers_from_tavern_file(path):
117
+ markers = set()
118
+ in_marks = False
119
+ marks_indent = 0
120
+ with open(path) as tavern_file:
121
+ for line in tavern_file:
122
+ match = re.match(r"^(\s*)marks:\s*(.*)$", line)
123
+ if match:
124
+ in_marks = True
125
+ marks_indent = len(match.group(1))
126
+ inline_markers = match.group(2).strip()
127
+ if inline_markers.startswith("[") and inline_markers.endswith("]"):
128
+ for marker in inline_markers[1:-1].split(","):
129
+ _add_tavern_marker(markers, marker)
130
+ continue
131
+ if not in_marks:
132
+ continue
133
+ if not line.strip() or line.lstrip().startswith("#"):
134
+ continue
135
+ indent = len(line) - len(line.lstrip())
136
+ if indent <= marks_indent:
137
+ in_marks = False
138
+ continue
139
+ marker = re.match(r"^\s*-\s*(.+?)\s*(?:#.*)?$", line)
140
+ if marker:
141
+ _add_tavern_marker(markers, marker.group(1))
142
+ return markers
143
+
144
+
145
+ def _add_tavern_marker(markers, value):
146
+ marker = value.strip().strip("'\"")
147
+ if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", marker) and marker not in _TAVERN_MARKER_EXCLUSIONS:
148
+ markers.add(marker)
149
+
150
+
151
+ def package_artifacts(project, test_dir, tool, role):
152
+ # Make a copy for easy access in environment validation
153
+ working_dir = get_working_distribution_directory(project)
154
+ shutil.copytree(test_dir, f"{working_dir}/{tool}",dirs_exist_ok=True)
155
+ if tool == "tavern":
156
+ write_tavern_manifest(f"{working_dir}/{tool}")
157
+ with tempfile.TemporaryDirectory() as tempdir:
158
+ shutil.copytree(test_dir, f"{tempdir}/{role}",dirs_exist_ok=True)
159
+ if tool == "tavern":
160
+ write_tavern_manifest(f"{tempdir}/{role}")
161
+ # package a copy for distribution
162
+ # zip up the test and add them to the integration test dist directory
163
+ base_name = get_local_zip_artifact_path(tool=tool, project=project)
164
+ shutil.make_archive(base_name=base_name,
165
+ base_dir=role,
166
+ format="zip",
167
+ root_dir=tempdir)
168
+
@@ -17,6 +17,7 @@ APPLICATION = "application"
17
17
  APPLICATION_GROUP = "application_group"
18
18
  RUN_PARALLEL = "pytest_parallel"
19
19
  CONSOLIDATE_TESTS = "consolidate_tavern"
20
+ SKIP_NON_MATCHING_TAVERN_ARTIFACTS = "skip_non_matching_tavern_artifacts"
20
21
  TESTING_SCOPE = "testing_scope"
21
22
  PARALLEL_VERIFY_ENVIRONMENT = "parallel_verify_environment"
22
23
  SUBPROCESS_PROPERTIES_FILE_ENV = "PYBUILDER_INTEGRATION_PROPERTIES_FILE"
@@ -18,7 +18,8 @@ from pybuilder_integration import exec_utility, tool_utility, directory_utility
18
18
  from pybuilder_integration.artifact_manager import get_artifact_manager
19
19
  from pybuilder_integration.cloudwatchlogs_utility import CloudwatchLogs
20
20
  from pybuilder_integration.directory_utility import get_working_distribution_directory, \
21
- package_artifacts, prepare_reports_directory, get_local_zip_artifact_path, prepare_logs_directory
21
+ package_artifacts, prepare_reports_directory, get_local_zip_artifact_path, prepare_logs_directory, \
22
+ read_tavern_service_markers
22
23
  from pybuilder_integration.properties import *
23
24
  from pybuilder_integration.tool_utility import install_cypress
24
25
 
@@ -40,6 +41,7 @@ _VERIFY_ENVIRONMENT_SUBPROCESS_PROPERTIES = [
40
41
  APPLICATION_GROUP,
41
42
  RUN_PARALLEL,
42
43
  CONSOLIDATE_TESTS,
44
+ SKIP_NON_MATCHING_TAVERN_ARTIFACTS,
43
45
  TESTING_SCOPE,
44
46
  "abort_upload",
45
47
  "record_cypress",
@@ -449,6 +451,15 @@ def _run_tavern_tests_in_dir(test_dir: str, logger: Logger, project: Project, re
449
451
  logger.info(f"Found {len(os.listdir(test_dir))} files in tavern test directory")
450
452
  # todo is this unique enough for each run?
451
453
  output_file, run_name = get_test_report_file(project, test_dir)
454
+ extra_args = [project.expand(prop) for prop in project.get_property(TAVERN_ADDITIONAL_ARGS, [])]
455
+ requested_marker = _extract_tavern_marker_filter(extra_args)
456
+ declared_markers = read_tavern_service_markers(test_dir)
457
+ skip_non_matching_artifacts = _get_bool_property(project, SKIP_NON_MATCHING_TAVERN_ARTIFACTS, False)
458
+ if skip_non_matching_artifacts and requested_marker and declared_markers and requested_marker not in declared_markers:
459
+ logger.info(
460
+ f"Skipping Tavern tests: artifact markers {sorted(declared_markers)} do not match {requested_marker}"
461
+ )
462
+ return True
452
463
  from sys import path as syspath
453
464
  syspath.insert(0, test_dir)
454
465
  # install any requirements that my exist
@@ -457,7 +468,6 @@ def _run_tavern_tests_in_dir(test_dir: str, logger: Logger, project: Project, re
457
468
  dependency = RequirementsFile(requirements_file)
458
469
  install_dependencies(logger, project, dependency, reactor.pybuilder_venv,
459
470
  f"{prepare_logs_directory(project)}/install_tavern_pip_dependencies.log")
460
- extra_args = [project.expand(prop) for prop in project.get_property(TAVERN_ADDITIONAL_ARGS, [])]
461
471
  args = ["--junit-xml", f"{output_file}", test_dir] + extra_args
462
472
  if project.get_property("verbose"):
463
473
  args.append("-s")
@@ -476,7 +486,7 @@ def _run_tavern_tests_in_dir(test_dir: str, logger: Logger, project: Project, re
476
486
  ret = pytest.main(args)
477
487
  if ret == pytest.ExitCode.NO_TESTS_COLLECTED:
478
488
  fallback_args = _without_tavern_marker_filter(args)
479
- if fallback_args != args:
489
+ if fallback_args != args and (not skip_non_matching_artifacts or not declared_markers):
480
490
  logger.warn(
481
491
  "No Tavern tests matched the marker filter; rerunning the full suite"
482
492
  )
@@ -519,6 +529,19 @@ def _without_tavern_marker_filter(args):
519
529
  return fallback_args
520
530
 
521
531
 
532
+ def _extract_tavern_marker_filter(args):
533
+ for index, arg in enumerate(args):
534
+ if arg in {"-m", "--markexpr"} and index + 1 < len(args):
535
+ marker = args[index + 1]
536
+ elif arg.startswith("--markexpr="):
537
+ marker = arg.split("=", 1)[1]
538
+ else:
539
+ continue
540
+ if marker.replace("_", "").isalnum() and marker:
541
+ return marker
542
+ return None
543
+
544
+
522
545
  def get_test_report_file(project, test_dir, tool="tavern"):
523
546
  run_name = os.path.basename(test_dir)
524
547
  output_file = os.path.join(prepare_reports_directory(project), f"{tool}-{run_name}.out.xml")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pybuilder-integration
3
- Version: 104
3
+ Version: 105
4
4
  Summary: A pybuilder plugin that runs integration tests (Tavern & Cypress) against a target.
5
5
  Home-page: https://github.com/rspitler/pybuilder-integration
6
6
  Author:
@@ -21,7 +21,7 @@ class install(_install):
21
21
  if __name__ == '__main__':
22
22
  setup(
23
23
  name = 'pybuilder-integration',
24
- version = '104',
24
+ version = '105',
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,
@@ -1,70 +0,0 @@
1
- import os
2
- import shutil
3
- import tempfile
4
-
5
-
6
-
7
- def prepare_reports_directory(project):
8
- return prepare_directory("$dir_reports", project)
9
-
10
-
11
- def prepare_logs_directory(project):
12
- return prepare_directory("$dir_logs", project)
13
-
14
-
15
- def prepare_dist_directory(project):
16
- return prepare_directory("$dir_dist", project)
17
-
18
-
19
- def prepare_directory(dir_variable, project):
20
- package__format = f"{dir_variable}/integration"
21
- reports_dir = project.expand_path(package__format)
22
- return _ensure_directory_exists(reports_dir)
23
-
24
-
25
- def _ensure_directory_exists(path):
26
- if not os.path.exists(path):
27
- os.makedirs(path)
28
- return path
29
-
30
-
31
- def get_working_distribution_directory(project):
32
- dist_directory = prepare_dist_directory(project)
33
- return _ensure_directory_exists(f"{dist_directory}/working")
34
-
35
-
36
- def get_latest_distribution_directory(project):
37
- dist_directory = prepare_dist_directory(project)
38
- from pybuilder_integration import ENVIRONMENT
39
- environment = project.get_mandatory_property(ENVIRONMENT)
40
- return _ensure_directory_exists(f"{dist_directory}/LATEST-{environment}")
41
-
42
-
43
- def get_latest_zipped_distribution_directory(project):
44
- dist_directory = prepare_dist_directory(project)
45
- from pybuilder_integration import ENVIRONMENT
46
- environment = project.get_mandatory_property(ENVIRONMENT)
47
- return _ensure_directory_exists(f"{dist_directory}/LATEST-{environment}/zipped")
48
-
49
-
50
- def get_local_zip_artifact_path(tool, project, include_ending=False):
51
- artifact = f"{prepare_dist_directory(project)}/{tool}-{project.name}"
52
- if include_ending:
53
- return f"{artifact}.zip"
54
- return artifact
55
-
56
-
57
- def package_artifacts(project, test_dir, tool, role):
58
- # Make a copy for easy access in environment validation
59
- working_dir = get_working_distribution_directory(project)
60
- shutil.copytree(test_dir, f"{working_dir}/{tool}",dirs_exist_ok=True)
61
- with tempfile.TemporaryDirectory() as tempdir:
62
- shutil.copytree(test_dir, f"{tempdir}/{role}",dirs_exist_ok=True)
63
- # package a copy for distribution
64
- # zip up the test and add them to the integration test dist directory
65
- base_name = get_local_zip_artifact_path(tool=tool, project=project)
66
- shutil.make_archive(base_name=base_name,
67
- base_dir=role,
68
- format="zip",
69
- root_dir=tempdir)
70
-