pybuilder-integration 102__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.
- {pybuilder_integration-102 → pybuilder_integration-105}/PKG-INFO +8 -2
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/__init__.py +23 -1
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/artifact_manager.py +10 -2
- pybuilder_integration-105/pybuilder_integration/directory_utility.py +168 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/properties.py +4 -0
- pybuilder_integration-105/pybuilder_integration/tasks.py +548 -0
- pybuilder_integration-105/pybuilder_integration/tool_utility.py +53 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/PKG-INFO +8 -2
- {pybuilder_integration-102 → pybuilder_integration-105}/setup.py +2 -1
- pybuilder_integration-102/pybuilder_integration/directory_utility.py +0 -70
- pybuilder_integration-102/pybuilder_integration/tasks.py +0 -249
- pybuilder_integration-102/pybuilder_integration/tool_utility.py +0 -24
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/cloudwatchlogs_utility.py +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/exec_utility.py +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/SOURCES.txt +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/dependency_links.txt +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/namespace_packages.txt +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/requires.txt +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/top_level.txt +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/zip-safe +0 -0
- {pybuilder_integration-102 → pybuilder_integration-105}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pybuilder-integration
|
|
3
|
-
Version:
|
|
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:
|
|
@@ -13,5 +13,11 @@ Classifier: Programming Language :: Python
|
|
|
13
13
|
Requires-Dist: pytest
|
|
14
14
|
Requires-Dist: tavern==1.25.2
|
|
15
15
|
Requires-Dist: boto3
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: summary
|
|
16
22
|
|
|
17
23
|
A pybuilder plugin that runs integration tests against a target. This is intended to be a broader scope than unit-tests encompassing dependant functionality.
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
2
3
|
|
|
3
4
|
from pybuilder.core import task, Project, Logger, depends, after, init
|
|
4
5
|
from pybuilder.reactor import Reactor
|
|
5
6
|
|
|
6
|
-
import pybuilder_integration.tasks
|
|
7
|
+
import pybuilder_integration.tasks as tasks
|
|
7
8
|
from pybuilder_integration.properties import *
|
|
8
9
|
|
|
9
10
|
|
|
@@ -18,6 +19,17 @@ def init_plugin(project):
|
|
|
18
19
|
project.plugin_depends_on("pytest")
|
|
19
20
|
project.build_depends_on('pytest-xdist')
|
|
20
21
|
project.plugin_depends_on("tavern")
|
|
22
|
+
_apply_integration_properties_file(project)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _apply_integration_properties_file(project):
|
|
26
|
+
properties_file = os.environ.get(SUBPROCESS_PROPERTIES_FILE_ENV)
|
|
27
|
+
if not properties_file or not os.path.exists(properties_file):
|
|
28
|
+
return
|
|
29
|
+
with open(properties_file) as fp:
|
|
30
|
+
properties = json.load(fp)
|
|
31
|
+
for key, value in properties.items():
|
|
32
|
+
project.set_property(key, value)
|
|
21
33
|
|
|
22
34
|
|
|
23
35
|
@task(description="Runs integration tests against a CI/Prod environment."
|
|
@@ -34,6 +46,16 @@ def verify_environment(project: Project, logger: Logger, reactor: Reactor):
|
|
|
34
46
|
tasks.verify_environment(project, logger, reactor)
|
|
35
47
|
|
|
36
48
|
|
|
49
|
+
@task(description="Run current build integration tests only. Intended for verify_environment subprocesses.")
|
|
50
|
+
def verify_environment_current(project: Project, logger: Logger, reactor: Reactor):
|
|
51
|
+
tasks._verify_environment_current(project, logger, reactor)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@task(description="Run downloaded LATEST integration tests only. Intended for verify_environment subprocesses.")
|
|
55
|
+
def verify_environment_latest(project: Project, logger: Logger, reactor: Reactor):
|
|
56
|
+
tasks._verify_environment_latest(project, logger, reactor)
|
|
57
|
+
|
|
58
|
+
|
|
37
59
|
@task(description="Run integration tests using a cypress spec. Requires NPM installed.\n"
|
|
38
60
|
f"\t{INTEGRATION_TARGET_URL} - (required) Full URL target for cypress tests\n"
|
|
39
61
|
f"\t{INTEGRATION_PUBLIC_TARGET_URL} - (required) Full public URL target for cypress tests\n"
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/artifact_manager.py
RENAMED
|
@@ -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
|
-
fp.
|
|
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,4 +17,8 @@ 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"
|
|
22
|
+
PARALLEL_VERIFY_ENVIRONMENT = "parallel_verify_environment"
|
|
23
|
+
SUBPROCESS_PROPERTIES_FILE_ENV = "PYBUILDER_INTEGRATION_PROPERTIES_FILE"
|
|
24
|
+
CYPRESS_CACHE_FOLDER = "cypress_cache_folder"
|
|
@@ -0,0 +1,548 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import re
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import sys
|
|
7
|
+
import tempfile
|
|
8
|
+
import xml.etree.ElementTree as ET
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
from pybuilder.core import Project, Logger, RequirementsFile
|
|
12
|
+
from pybuilder.errors import BuildFailedException
|
|
13
|
+
from pybuilder.install_utils import install_dependencies
|
|
14
|
+
from pybuilder.reactor import Reactor
|
|
15
|
+
from pybuilder.utils import Timer
|
|
16
|
+
|
|
17
|
+
from pybuilder_integration import exec_utility, tool_utility, directory_utility
|
|
18
|
+
from pybuilder_integration.artifact_manager import get_artifact_manager
|
|
19
|
+
from pybuilder_integration.cloudwatchlogs_utility import CloudwatchLogs
|
|
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, \
|
|
22
|
+
read_tavern_service_markers
|
|
23
|
+
from pybuilder_integration.properties import *
|
|
24
|
+
from pybuilder_integration.tool_utility import install_cypress
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
_VERIFY_ENVIRONMENT_SUBPROCESS_PROPERTIES = [
|
|
28
|
+
INTEGRATION_ARTIFACT_BUCKET,
|
|
29
|
+
ENVIRONMENT,
|
|
30
|
+
ARTIFACT_MANAGER,
|
|
31
|
+
CYPRESS_TEST_DIR,
|
|
32
|
+
CYPRESS_CONFIG_FILE,
|
|
33
|
+
INTEGRATION_TARGET_URL,
|
|
34
|
+
INTEGRATION_PUBLIC_TARGET_URL,
|
|
35
|
+
TAVERN_ADDITIONAL_ARGS,
|
|
36
|
+
TAVERN_TEST_DIR,
|
|
37
|
+
PROMOTE_ARTIFACT,
|
|
38
|
+
ROLE,
|
|
39
|
+
SHOULD_SKIP_LATEST,
|
|
40
|
+
APPLICATION,
|
|
41
|
+
APPLICATION_GROUP,
|
|
42
|
+
RUN_PARALLEL,
|
|
43
|
+
CONSOLIDATE_TESTS,
|
|
44
|
+
SKIP_NON_MATCHING_TAVERN_ARTIFACTS,
|
|
45
|
+
TESTING_SCOPE,
|
|
46
|
+
"abort_upload",
|
|
47
|
+
"record_cypress",
|
|
48
|
+
"verbose",
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
def integration_artifact_push(project: Project, logger: Logger, reactor: Reactor):
|
|
52
|
+
logger.info("Starting upload of integration artifacts")
|
|
53
|
+
manager = get_artifact_manager(project)
|
|
54
|
+
for tool in ["tavern", "cypress"]:
|
|
55
|
+
artifact_file = get_local_zip_artifact_path(tool=tool, project=project, include_ending=True)
|
|
56
|
+
if os.path.exists(artifact_file):
|
|
57
|
+
logger.info(
|
|
58
|
+
f"Starting upload of integration artifact: {os.path.basename(artifact_file)} to: {manager.friendly_name}"
|
|
59
|
+
)
|
|
60
|
+
manager.upload(file=artifact_file, project=project, logger=logger, reactor=reactor)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def verify_environment(project: Project, logger: Logger, reactor: Reactor):
|
|
64
|
+
if _get_bool_property(project, PARALLEL_VERIFY_ENVIRONMENT, False):
|
|
65
|
+
_verify_environment_parallel(project, logger, reactor)
|
|
66
|
+
return
|
|
67
|
+
_verify_environment_current(project, logger, reactor)
|
|
68
|
+
latest_directory = _download_latest_environment_artifacts(project, logger, reactor)
|
|
69
|
+
_verify_environment_latest(project, logger, reactor, latest_directory)
|
|
70
|
+
if _get_bool_property(project, PROMOTE_ARTIFACT, True):
|
|
71
|
+
integration_artifact_push(project=project, logger=logger, reactor=reactor)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _verify_environment_current(project: Project, logger: Logger, reactor: Reactor, dist_directory=None):
|
|
75
|
+
if dist_directory is None:
|
|
76
|
+
dist_directory = project.get_property(WORKING_TEST_DIR, get_working_distribution_directory(project))
|
|
77
|
+
logger.info(f"Preparing to run tests found in: {dist_directory}")
|
|
78
|
+
_run_tests_in_directory(dist_directory, logger, project, reactor)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _download_latest_environment_artifacts(project: Project, logger: Logger, reactor: Reactor):
|
|
82
|
+
artifact_manager = get_artifact_manager(project=project)
|
|
83
|
+
return artifact_manager.download_artifacts(project=project, logger=logger, reactor=reactor)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _verify_environment_latest(project: Project, logger: Logger, reactor: Reactor, latest_directory=None):
|
|
87
|
+
if latest_directory is None:
|
|
88
|
+
latest_directory = project.get_property(WORKING_TEST_DIR, None)
|
|
89
|
+
if latest_directory is None:
|
|
90
|
+
latest_directory = get_artifact_manager(project=project).download_artifacts(project=project,
|
|
91
|
+
logger=logger,
|
|
92
|
+
reactor=reactor)
|
|
93
|
+
_run_tests_in_directory(latest_directory, logger, project, reactor, latest=True)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _verify_environment_parallel(project: Project, logger: Logger, reactor: Reactor):
|
|
97
|
+
dist_directory = project.get_property(WORKING_TEST_DIR, get_working_distribution_directory(project))
|
|
98
|
+
logger.info(f"Preparing to run tests found in: {dist_directory}")
|
|
99
|
+
latest_directory = _download_latest_environment_artifacts(project, logger, reactor)
|
|
100
|
+
passes = [
|
|
101
|
+
{
|
|
102
|
+
"name": "current",
|
|
103
|
+
"task": "verify_environment_current",
|
|
104
|
+
"test_dir": dist_directory,
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"name": "latest",
|
|
108
|
+
"task": "verify_environment_latest",
|
|
109
|
+
"test_dir": latest_directory,
|
|
110
|
+
},
|
|
111
|
+
]
|
|
112
|
+
_run_verify_environment_passes_in_subprocesses(project, logger, passes)
|
|
113
|
+
if _get_bool_property(project, PROMOTE_ARTIFACT, True):
|
|
114
|
+
integration_artifact_push(project=project, logger=logger, reactor=reactor)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _run_verify_environment_passes_in_subprocesses(project: Project, logger: Logger, passes):
|
|
118
|
+
properties_file = _write_subprocess_properties_file(project)
|
|
119
|
+
processes = []
|
|
120
|
+
try:
|
|
121
|
+
for pass_config in passes:
|
|
122
|
+
process, log_file = _start_verify_environment_subprocess(project, logger, pass_config, properties_file)
|
|
123
|
+
processes.append((pass_config, process, log_file))
|
|
124
|
+
|
|
125
|
+
failures = []
|
|
126
|
+
for pass_config, process, log_file in processes:
|
|
127
|
+
exit_code = process.wait()
|
|
128
|
+
log_handle = getattr(process, "_pybuilder_integration_log_handle", None)
|
|
129
|
+
if log_handle:
|
|
130
|
+
log_handle.close()
|
|
131
|
+
if exit_code != 0:
|
|
132
|
+
failures.append(f"{pass_config['name']} exit_code={exit_code} log={log_file}")
|
|
133
|
+
|
|
134
|
+
if failures:
|
|
135
|
+
raise BuildFailedException("Parallel verify_environment failed: " + "; ".join(failures))
|
|
136
|
+
finally:
|
|
137
|
+
if os.path.exists(properties_file):
|
|
138
|
+
os.remove(properties_file)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _start_verify_environment_subprocess(project: Project, logger: Logger, pass_config, properties_file):
|
|
142
|
+
log_file = os.path.join(prepare_logs_directory(project), f"verify_environment_{pass_config['name']}.log")
|
|
143
|
+
command = _build_verify_environment_subprocess_command(project, pass_config)
|
|
144
|
+
env = os.environ.copy()
|
|
145
|
+
env[SUBPROCESS_PROPERTIES_FILE_ENV] = properties_file
|
|
146
|
+
logger.info(f"Starting {pass_config['name']} verify_environment subprocess: {' '.join(command)}")
|
|
147
|
+
log_handle = os.fdopen(
|
|
148
|
+
os.open(log_file, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600),
|
|
149
|
+
"w",
|
|
150
|
+
encoding="utf-8",
|
|
151
|
+
)
|
|
152
|
+
try:
|
|
153
|
+
process = subprocess.Popen(command,
|
|
154
|
+
stdout=log_handle,
|
|
155
|
+
stderr=subprocess.STDOUT,
|
|
156
|
+
cwd=project.basedir,
|
|
157
|
+
env=env,
|
|
158
|
+
shell=False)
|
|
159
|
+
except Exception:
|
|
160
|
+
log_handle.close()
|
|
161
|
+
raise
|
|
162
|
+
process._pybuilder_integration_log_handle = log_handle
|
|
163
|
+
return process, log_file
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def _build_verify_environment_subprocess_command(project: Project, pass_config):
|
|
167
|
+
stage_target = os.path.join(project.expand_path("$dir_target"), "verify_environment", pass_config["name"])
|
|
168
|
+
return [
|
|
169
|
+
sys.executable,
|
|
170
|
+
"-c",
|
|
171
|
+
"from pybuilder.cli import main; main()",
|
|
172
|
+
pass_config["task"],
|
|
173
|
+
"-P", f"{WORKING_TEST_DIR}={pass_config['test_dir']}",
|
|
174
|
+
"-P", f"{PARALLEL_VERIFY_ENVIRONMENT}=False",
|
|
175
|
+
"-P", f"{PROMOTE_ARTIFACT}=False",
|
|
176
|
+
"-P", f"dir_target={stage_target}",
|
|
177
|
+
"-P", f"dir_logs={os.path.join(stage_target, 'logs')}",
|
|
178
|
+
"-P", f"dir_reports={os.path.join(stage_target, 'reports')}",
|
|
179
|
+
]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _write_subprocess_properties_file(project: Project):
|
|
183
|
+
properties = _collect_json_safe_project_properties(project)
|
|
184
|
+
fd, path = tempfile.mkstemp(prefix="verify_environment_", suffix=".json", dir=prepare_logs_directory(project))
|
|
185
|
+
with os.fdopen(fd, "w") as fp:
|
|
186
|
+
json.dump(properties, fp)
|
|
187
|
+
return path
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def _collect_json_safe_project_properties(project: Project):
|
|
191
|
+
safe_properties = {}
|
|
192
|
+
for name in _VERIFY_ENVIRONMENT_SUBPROCESS_PROPERTIES:
|
|
193
|
+
value = project.get_property(name, None)
|
|
194
|
+
if value is None:
|
|
195
|
+
continue
|
|
196
|
+
try:
|
|
197
|
+
json.dumps(value)
|
|
198
|
+
except (TypeError, ValueError):
|
|
199
|
+
continue
|
|
200
|
+
safe_properties[name] = value
|
|
201
|
+
return safe_properties
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _get_bool_property(project: Project, property_name, default=False):
|
|
205
|
+
value = project.get_property(property_name, default)
|
|
206
|
+
if isinstance(value, str):
|
|
207
|
+
return value.lower() in ["1", "true", "yes", "on"]
|
|
208
|
+
return bool(value)
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def _should_run_latest(test_dir, project):
|
|
212
|
+
if project.get_property(SHOULD_SKIP_LATEST, False):
|
|
213
|
+
if project.get_property(ROLE) == test_dir:
|
|
214
|
+
return False
|
|
215
|
+
return True
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def _run_tests_in_directory(dist_directory, logger, project, reactor, latest=False):
|
|
219
|
+
logger.debug(f"Run tests in directory: {os.listdir(dist_directory)} ")
|
|
220
|
+
_run_tavern_tests_in_dist_dir(dist_directory, latest, logger, project, reactor)
|
|
221
|
+
_run_cypress_tests_in_dist_dir(dist_directory, latest, logger, project, reactor)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _run_cypress_tests_in_dist_dir(dist_directory, latest, logger, project, reactor):
|
|
225
|
+
total_time = Timer.start()
|
|
226
|
+
cypress_test_path = f"{dist_directory}/cypress"
|
|
227
|
+
if os.path.exists(cypress_test_path):
|
|
228
|
+
logger.debug(f"Run cypress tests in directory: {cypress_test_path} ")
|
|
229
|
+
logger.debug(f"Run cypress tests in directory files: {os.listdir(cypress_test_path)} ")
|
|
230
|
+
logger.info(f"Found cypress tests - starting run latest: {latest}")
|
|
231
|
+
if latest:
|
|
232
|
+
for test_dir in os.listdir(cypress_test_path):
|
|
233
|
+
directory_to_test = f"{cypress_test_path}/{test_dir}"
|
|
234
|
+
if os.path.isdir(directory_to_test) and _should_run_latest(test_dir, project):
|
|
235
|
+
logger.info(f"Running {directory_to_test}")
|
|
236
|
+
_run_cypress_tests_in_directory(work_dir=directory_to_test,
|
|
237
|
+
logger=logger,
|
|
238
|
+
project=project,
|
|
239
|
+
reactor=reactor)
|
|
240
|
+
else:
|
|
241
|
+
_run_cypress_tests_in_directory(work_dir=cypress_test_path,
|
|
242
|
+
logger=logger,
|
|
243
|
+
project=project,
|
|
244
|
+
reactor=reactor)
|
|
245
|
+
total_time.stop()
|
|
246
|
+
logger.info(f"Ran Cypress tests: {total_time.get_millis()}")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _run_tavern_tests_in_dist_dir(dist_directory, latest, logger, project, reactor):
|
|
250
|
+
total_time = Timer.start()
|
|
251
|
+
tavern_test_path = f"{dist_directory}/tavern"
|
|
252
|
+
if os.path.exists(tavern_test_path):
|
|
253
|
+
logger.info(f"Found tavern tests - starting run latest: {latest}")
|
|
254
|
+
logger.debug(f"Run tavern tests in directory: {tavern_test_path} ")
|
|
255
|
+
logger.debug(f"Run tavern tests in directory files: {os.listdir(tavern_test_path)} ")
|
|
256
|
+
if latest:
|
|
257
|
+
for tavern_dir in os.listdir(tavern_test_path):
|
|
258
|
+
tavern_test_direcotry = f"{tavern_test_path}/{tavern_dir}"
|
|
259
|
+
if os.path.isdir(tavern_test_direcotry) and _should_run_latest(tavern_dir, project):
|
|
260
|
+
logger.info(f"Running {tavern_test_direcotry}")
|
|
261
|
+
_run_tavern_tests_in_dir(test_dir=tavern_test_direcotry,
|
|
262
|
+
logger=logger,
|
|
263
|
+
project=project,
|
|
264
|
+
reactor=reactor,
|
|
265
|
+
role=os.path.basename(tavern_dir))
|
|
266
|
+
else:
|
|
267
|
+
_run_tavern_tests_in_dir(test_dir=f"{tavern_test_path}",
|
|
268
|
+
logger=logger,
|
|
269
|
+
project=project,
|
|
270
|
+
reactor=reactor)
|
|
271
|
+
total_time.stop()
|
|
272
|
+
logger.info(f"Ran Tavern tests: {total_time.get_millis()}")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def verify_cypress(project: Project, logger: Logger, reactor: Reactor):
|
|
276
|
+
# Get directories with test and cypress executable
|
|
277
|
+
work_dir = project.expand_path(f"${CYPRESS_TEST_DIR}")
|
|
278
|
+
if _run_cypress_tests_in_directory(work_dir=work_dir, logger=logger, project=project, reactor=reactor):
|
|
279
|
+
package_artifacts(project, work_dir, "cypress", project.get_property(ROLE))
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _run_cypress_tests_in_directory(work_dir, logger, project, reactor: Reactor):
|
|
283
|
+
total_time = Timer.start()
|
|
284
|
+
target_url = project.get_mandatory_property(INTEGRATION_TARGET_URL)
|
|
285
|
+
environment = project.get_mandatory_property(ENVIRONMENT)
|
|
286
|
+
if not os.path.exists(work_dir):
|
|
287
|
+
logger.info("Skipping cypress run: no tests")
|
|
288
|
+
return False
|
|
289
|
+
logger.info(f"Found {len(os.listdir(work_dir))} files in cypress test directory")
|
|
290
|
+
logger.debug(f"Files: {os.listdir(work_dir)} ")
|
|
291
|
+
# Validate NPM install and Install cypress
|
|
292
|
+
package_json = os.path.join(work_dir, "package.json")
|
|
293
|
+
if os.path.exists(package_json):
|
|
294
|
+
logger.info("Found package.json installing dependencies")
|
|
295
|
+
tool_utility.install_npm_dependencies(work_dir, project=project, logger=logger, reactor=reactor)
|
|
296
|
+
else:
|
|
297
|
+
install_cypress(logger=logger, project=project, reactor=reactor, work_dir=work_dir)
|
|
298
|
+
total_time.stop()
|
|
299
|
+
logger.info(f"Configured Cypress Environment: {total_time.get_millis()}")
|
|
300
|
+
total_time = Timer.start()
|
|
301
|
+
executable = os.path.join(work_dir, "node_modules/cypress/bin/cypress")
|
|
302
|
+
results_file, run_name = get_test_report_file(project=project, test_dir=work_dir, tool="cypress")
|
|
303
|
+
# Run the actual tests against the baseURL provided by ${integration_target}
|
|
304
|
+
test_report_folder = directory_utility.prepare_reports_directory(project)
|
|
305
|
+
args = ["run", "--config",
|
|
306
|
+
f"baseUrl={target_url},"
|
|
307
|
+
f"videosFolder={test_report_folder}/videos,"
|
|
308
|
+
f"screenshotsFolder={test_report_folder}/screenshots",
|
|
309
|
+
"--reporter-options",
|
|
310
|
+
f"mochaFile={results_file}"]
|
|
311
|
+
if _get_bool_property(project, "record_cypress", True):
|
|
312
|
+
args.append('--record')
|
|
313
|
+
_add_config_file(logger, project, args, environment, work_dir)
|
|
314
|
+
environment_variables = project.get_property(ENVIRONMENT_VARIABLES, {})
|
|
315
|
+
logger.info(f"Running cypress on host: {target_url}")
|
|
316
|
+
try:
|
|
317
|
+
exec_utility.exec_command(command_name=executable, args=args,
|
|
318
|
+
failure_message="Failed to execute cypress tests", log_file_name='cypress_run.log',
|
|
319
|
+
project=project, reactor=reactor, logger=logger, working_dir=work_dir, report=False,
|
|
320
|
+
env_vars=environment_variables)
|
|
321
|
+
# workaround but cypress output are relative to location of cypress.json, so we need to collapse
|
|
322
|
+
if os.path.exists(f"{work_dir}/target"):
|
|
323
|
+
shutil.copytree(f"{work_dir}/target", project.expand_path("$dir_target"), dirs_exist_ok=True)
|
|
324
|
+
finally:
|
|
325
|
+
_write_cypress_summary(project, logger)
|
|
326
|
+
total_time.stop()
|
|
327
|
+
logger.info(f"Ran Cypress Tests: {total_time.get_millis()}")
|
|
328
|
+
return True
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _write_cypress_summary(project, logger):
|
|
332
|
+
"""Parse cypress_run.log and write a portable Markdown summary artifact."""
|
|
333
|
+
log_path = os.path.join(prepare_logs_directory(project), "cypress_run.log")
|
|
334
|
+
summary_path = os.path.join(prepare_reports_directory(project), "cypress-summary.md")
|
|
335
|
+
try:
|
|
336
|
+
output = _build_cypress_summary(log_path)
|
|
337
|
+
with os.fdopen(
|
|
338
|
+
os.open(summary_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600),
|
|
339
|
+
"w",
|
|
340
|
+
encoding="utf-8",
|
|
341
|
+
) as summary_file:
|
|
342
|
+
summary_file.write(output)
|
|
343
|
+
github_summary_path = os.environ.get("GITHUB_STEP_SUMMARY")
|
|
344
|
+
if github_summary_path:
|
|
345
|
+
with os.fdopen(
|
|
346
|
+
os.open(github_summary_path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600),
|
|
347
|
+
"a",
|
|
348
|
+
encoding="utf-8",
|
|
349
|
+
) as f:
|
|
350
|
+
f.write(output)
|
|
351
|
+
print(output)
|
|
352
|
+
return summary_path
|
|
353
|
+
except Exception as e:
|
|
354
|
+
logger.warn(f"[cypress_summary] failed to generate summary: {e}")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _build_cypress_summary(log_path):
|
|
358
|
+
if not os.path.exists(log_path):
|
|
359
|
+
return "## Cypress Test Results\n\n> warning `cypress_run.log` not found\n"
|
|
360
|
+
|
|
361
|
+
raw = open(log_path, errors="replace").read()
|
|
362
|
+
clean = re.sub(r"\x1b\[[0-9;]*m", "", raw)
|
|
363
|
+
|
|
364
|
+
total = failed = skipped = 0
|
|
365
|
+
failures = []
|
|
366
|
+
for xml_block in re.findall(r"<testsuites[\s\S]*?</testsuites>", clean):
|
|
367
|
+
try:
|
|
368
|
+
root = ET.fromstring(xml_block)
|
|
369
|
+
total += int(root.get("tests", 0))
|
|
370
|
+
failed += int(root.get("failures", 0))
|
|
371
|
+
skipped += int(root.get("skipped", 0) or 0)
|
|
372
|
+
for tc in root.iter("testcase"):
|
|
373
|
+
failure = tc.find("failure")
|
|
374
|
+
if failure is not None:
|
|
375
|
+
suite = tc.get("classname", "").split(".")[-1]
|
|
376
|
+
name = tc.get("name", "")
|
|
377
|
+
msg = (failure.get("message") or failure.text or "").split("\n")[0][:120]
|
|
378
|
+
failures.append((f"{suite} > {name}", msg))
|
|
379
|
+
except ET.ParseError:
|
|
380
|
+
pass
|
|
381
|
+
|
|
382
|
+
passed = total - failed - skipped
|
|
383
|
+
if total == 0:
|
|
384
|
+
icon = "WARN"
|
|
385
|
+
label = "No tests ran - check if Cypress found any spec files"
|
|
386
|
+
elif failed == 0:
|
|
387
|
+
icon = "PASS"
|
|
388
|
+
label = "All tests passed"
|
|
389
|
+
else:
|
|
390
|
+
icon = "FAIL"
|
|
391
|
+
label = f"{failed} test(s) failed"
|
|
392
|
+
|
|
393
|
+
lines = [
|
|
394
|
+
"## Cypress Test Results\n",
|
|
395
|
+
f"### {icon} {label}\n",
|
|
396
|
+
"| Total | Passed | Failed | Skipped |",
|
|
397
|
+
"|-------|--------|--------|---------|",
|
|
398
|
+
f"| {total} | {passed} | {failed} | {skipped} |",
|
|
399
|
+
]
|
|
400
|
+
|
|
401
|
+
if failures:
|
|
402
|
+
lines.append("\n### Failed Tests\n")
|
|
403
|
+
for title, msg in failures[:20]:
|
|
404
|
+
lines.append(f"- **{title}**")
|
|
405
|
+
if msg:
|
|
406
|
+
lines.append(f" - _{msg}_")
|
|
407
|
+
if len(failures) > 20:
|
|
408
|
+
lines.append(f"\n_...and {len(failures) - 20} more_")
|
|
409
|
+
|
|
410
|
+
return "\n".join(lines) + "\n"
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def _add_config_file(logger, project, args, environment, work_dir):
|
|
414
|
+
|
|
415
|
+
# Environment variable override
|
|
416
|
+
cypress_config_file_override = os.environ.get('CYPRESS_CONFIG_FILE')
|
|
417
|
+
if cypress_config_file_override:
|
|
418
|
+
logger.info(f"Found CYPRESS_CONFIG_FILE environment variable: {cypress_config_file_override}")
|
|
419
|
+
project.set_property(CYPRESS_CONFIG_FILE, cypress_config_file_override)
|
|
420
|
+
|
|
421
|
+
# Check project for config file else test file system for environment named file
|
|
422
|
+
cypress_config_file = project.get_property(CYPRESS_CONFIG_FILE)
|
|
423
|
+
if cypress_config_file and os.path.exists(os.path.join(work_dir, cypress_config_file)):
|
|
424
|
+
logger.info(f"Setting --config-file parameter from project to {cypress_config_file}")
|
|
425
|
+
args.append("--config-file")
|
|
426
|
+
args.append(cypress_config_file)
|
|
427
|
+
else:
|
|
428
|
+
logger.info(f"Attempting to discover cypress config file from environment: {environment}")
|
|
429
|
+
for ending in ['ts', 'json']:
|
|
430
|
+
config_file_path = f'{environment}-config.{ending}'
|
|
431
|
+
if os.path.exists(os.path.join(work_dir, config_file_path)):
|
|
432
|
+
logger.info(f"Setting --config-file parameter from env to {config_file_path}")
|
|
433
|
+
args.append("--config-file")
|
|
434
|
+
args.append(config_file_path)
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
def verify_tavern(project: Project, logger: Logger, reactor: Reactor):
|
|
439
|
+
# Expand the directory to get full path
|
|
440
|
+
test_dir = project.expand_path(f"${TAVERN_TEST_DIR}")
|
|
441
|
+
# Run the tests in the directory
|
|
442
|
+
if _run_tavern_tests_in_dir(test_dir, logger, project, reactor):
|
|
443
|
+
package_artifacts(project, test_dir, "tavern", project.get_property(ROLE))
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _run_tavern_tests_in_dir(test_dir: str, logger: Logger, project: Project, reactor: Reactor, role=None):
|
|
447
|
+
logger.info("Running tavern tests: {}".format(test_dir))
|
|
448
|
+
if not os.path.exists(test_dir):
|
|
449
|
+
logger.info("Skipping tavern run: no tests")
|
|
450
|
+
return False
|
|
451
|
+
logger.info(f"Found {len(os.listdir(test_dir))} files in tavern test directory")
|
|
452
|
+
# todo is this unique enough for each run?
|
|
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
|
|
463
|
+
from sys import path as syspath
|
|
464
|
+
syspath.insert(0, test_dir)
|
|
465
|
+
# install any requirements that my exist
|
|
466
|
+
requirements_file = os.path.join(test_dir, "requirements.txt")
|
|
467
|
+
if os.path.exists(requirements_file):
|
|
468
|
+
dependency = RequirementsFile(requirements_file)
|
|
469
|
+
install_dependencies(logger, project, dependency, reactor.pybuilder_venv,
|
|
470
|
+
f"{prepare_logs_directory(project)}/install_tavern_pip_dependencies.log")
|
|
471
|
+
args = ["--junit-xml", f"{output_file}", test_dir] + extra_args
|
|
472
|
+
if project.get_property("verbose"):
|
|
473
|
+
args.append("-s")
|
|
474
|
+
args.append("-v")
|
|
475
|
+
if _get_bool_property(project, RUN_PARALLEL, False):
|
|
476
|
+
args.extend(['-n', 'auto'])
|
|
477
|
+
os.environ['TARGET'] = project.get_property(INTEGRATION_TARGET_URL)
|
|
478
|
+
os.environ['PUBLIC_TARGET'] = project.get_property(INTEGRATION_PUBLIC_TARGET_URL)
|
|
479
|
+
os.environ[ENVIRONMENT] = project.get_property(ENVIRONMENT)
|
|
480
|
+
logger.info(f"Running against target: {project.get_property(INTEGRATION_TARGET_URL)} and "
|
|
481
|
+
f"public target: {project.get_property(INTEGRATION_PUBLIC_TARGET_URL)}")
|
|
482
|
+
cache_wd = os.getcwd()
|
|
483
|
+
try:
|
|
484
|
+
os.chdir(test_dir)
|
|
485
|
+
logger.debug(f"Running args: {args} ")
|
|
486
|
+
ret = pytest.main(args)
|
|
487
|
+
if ret == pytest.ExitCode.NO_TESTS_COLLECTED:
|
|
488
|
+
fallback_args = _without_tavern_marker_filter(args)
|
|
489
|
+
if fallback_args != args and (not skip_non_matching_artifacts or not declared_markers):
|
|
490
|
+
logger.warn(
|
|
491
|
+
"No Tavern tests matched the marker filter; rerunning the full suite"
|
|
492
|
+
)
|
|
493
|
+
logger.debug(f"Running fallback args: {fallback_args} ")
|
|
494
|
+
ret = pytest.main(fallback_args)
|
|
495
|
+
finally:
|
|
496
|
+
os.chdir(cache_wd)
|
|
497
|
+
|
|
498
|
+
if ret != 0:
|
|
499
|
+
if role:
|
|
500
|
+
roles = []
|
|
501
|
+
if project.get_property(CONSOLIDATE_TESTS, False):
|
|
502
|
+
with open(f"{test_dir}/roles") as fp:
|
|
503
|
+
for line in fp:
|
|
504
|
+
roles.append(line.strip())
|
|
505
|
+
else:
|
|
506
|
+
roles.append(role)
|
|
507
|
+
|
|
508
|
+
for service in roles:
|
|
509
|
+
CloudwatchLogs(project.get_property(ENVIRONMENT), project.get_property(APPLICATION), service,
|
|
510
|
+
logger).print_latest()
|
|
511
|
+
raise BuildFailedException(f"Tavern tests failed see complete output here - {output_file}")
|
|
512
|
+
return True
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def _without_tavern_marker_filter(args):
|
|
516
|
+
marker_options = {"-m", "--markexpr"}
|
|
517
|
+
fallback_args = []
|
|
518
|
+
skip_next = False
|
|
519
|
+
for arg in args:
|
|
520
|
+
if skip_next:
|
|
521
|
+
skip_next = False
|
|
522
|
+
continue
|
|
523
|
+
if arg in marker_options:
|
|
524
|
+
skip_next = True
|
|
525
|
+
continue
|
|
526
|
+
if arg.startswith("--markexpr="):
|
|
527
|
+
continue
|
|
528
|
+
fallback_args.append(arg)
|
|
529
|
+
return fallback_args
|
|
530
|
+
|
|
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
|
+
|
|
545
|
+
def get_test_report_file(project, test_dir, tool="tavern"):
|
|
546
|
+
run_name = os.path.basename(test_dir)
|
|
547
|
+
output_file = os.path.join(prepare_reports_directory(project), f"{tool}-{run_name}.out.xml")
|
|
548
|
+
return output_file, run_name
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from pybuilder.core import Logger, Project
|
|
2
|
+
from pybuilder.reactor import Reactor
|
|
3
|
+
|
|
4
|
+
from pybuilder_integration.exec_utility import exec_command
|
|
5
|
+
from pybuilder_integration.properties import CYPRESS_CACHE_FOLDER
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def install_cypress(logger: Logger, project: Project, reactor: Reactor, work_dir):
|
|
9
|
+
_verify_npm(reactor)
|
|
10
|
+
logger.info(f"Ensuring cypress is installed")
|
|
11
|
+
exec_command('npm', ['install', "cypress"], f'Failed to install cypress - required for integration tests',
|
|
12
|
+
f'{"cypress"}_npm_install.log', project, reactor, logger, report=False, working_dir=work_dir,
|
|
13
|
+
env_vars=_get_npm_env(project))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _verify_npm(reactor):
|
|
17
|
+
reactor.pybuilder_venv.verify_can_execute(
|
|
18
|
+
command_and_arguments=["npm", "--version"], prerequisite="npm", caller="integration_tests")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def install_npm_dependencies(work_dir, project, logger, reactor):
|
|
22
|
+
_verify_npm(reactor)
|
|
23
|
+
install_command = 'ci' if _has_package_lock(work_dir) else 'install'
|
|
24
|
+
exec_command('npm', [install_command], f'Failed to install package.json - required for integration tests',
|
|
25
|
+
f'package_json_npm_install.log', project, reactor, logger, report=False, working_dir=work_dir,
|
|
26
|
+
env_vars=_get_npm_env(project))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _has_package_lock(work_dir):
|
|
30
|
+
return any(
|
|
31
|
+
[
|
|
32
|
+
_path_exists(work_dir, "package-lock.json"),
|
|
33
|
+
_path_exists(work_dir, "npm-shrinkwrap.json"),
|
|
34
|
+
]
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _path_exists(work_dir, filename):
|
|
39
|
+
import os
|
|
40
|
+
return os.path.exists(os.path.join(work_dir, filename))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _get_npm_env(project):
|
|
44
|
+
import os
|
|
45
|
+
cache_folder = project.get_property(CYPRESS_CACHE_FOLDER, None)
|
|
46
|
+
if cache_folder:
|
|
47
|
+
return {"CYPRESS_CACHE_FOLDER": project.expand_path(cache_folder)}
|
|
48
|
+
cache_folder = os.environ.get("CYPRESS_CACHE_FOLDER")
|
|
49
|
+
if not cache_folder:
|
|
50
|
+
return {}
|
|
51
|
+
return {"CYPRESS_CACHE_FOLDER": cache_folder}
|
|
52
|
+
|
|
53
|
+
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: pybuilder-integration
|
|
3
|
-
Version:
|
|
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:
|
|
@@ -13,5 +13,11 @@ Classifier: Programming Language :: Python
|
|
|
13
13
|
Requires-Dist: pytest
|
|
14
14
|
Requires-Dist: tavern==1.25.2
|
|
15
15
|
Requires-Dist: boto3
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: home-page
|
|
19
|
+
Dynamic: license
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: summary
|
|
16
22
|
|
|
17
23
|
A pybuilder plugin that runs integration tests against a target. This is intended to be a broader scope than unit-tests encompassing dependant functionality.
|
|
@@ -21,7 +21,7 @@ class install(_install):
|
|
|
21
21
|
if __name__ == '__main__':
|
|
22
22
|
setup(
|
|
23
23
|
name = 'pybuilder-integration',
|
|
24
|
-
version = '
|
|
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,
|
|
@@ -53,6 +53,7 @@ if __name__ == '__main__':
|
|
|
53
53
|
'tavern==1.25.2',
|
|
54
54
|
'boto3'
|
|
55
55
|
],
|
|
56
|
+
extras_require = {},
|
|
56
57
|
dependency_links = [],
|
|
57
58
|
zip_safe = True,
|
|
58
59
|
cmdclass = {'install': install},
|
|
@@ -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
|
-
|
|
@@ -1,249 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import shutil
|
|
3
|
-
|
|
4
|
-
import pytest
|
|
5
|
-
from pybuilder.core import Project, Logger, RequirementsFile
|
|
6
|
-
from pybuilder.errors import BuildFailedException
|
|
7
|
-
from pybuilder.install_utils import install_dependencies
|
|
8
|
-
from pybuilder.reactor import Reactor
|
|
9
|
-
from pybuilder.utils import Timer
|
|
10
|
-
|
|
11
|
-
from pybuilder_integration import exec_utility, tool_utility, directory_utility
|
|
12
|
-
from pybuilder_integration.artifact_manager import get_artifact_manager
|
|
13
|
-
from pybuilder_integration.cloudwatchlogs_utility import CloudwatchLogs
|
|
14
|
-
from pybuilder_integration.directory_utility import get_working_distribution_directory, \
|
|
15
|
-
package_artifacts, prepare_reports_directory, get_local_zip_artifact_path, prepare_logs_directory
|
|
16
|
-
from pybuilder_integration.properties import *
|
|
17
|
-
from pybuilder_integration.tool_utility import install_cypress
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def integration_artifact_push(project: Project, logger: Logger, reactor: Reactor):
|
|
21
|
-
logger.info("Starting upload of integration artifacts")
|
|
22
|
-
manager = get_artifact_manager(project)
|
|
23
|
-
for tool in ["tavern", "cypress"]:
|
|
24
|
-
artifact_file = get_local_zip_artifact_path(tool=tool, project=project, include_ending=True)
|
|
25
|
-
if os.path.exists(artifact_file):
|
|
26
|
-
logger.info(
|
|
27
|
-
f"Starting upload of integration artifact: {os.path.basename(artifact_file)} to: {manager.friendly_name}"
|
|
28
|
-
)
|
|
29
|
-
manager.upload(file=artifact_file, project=project, logger=logger, reactor=reactor)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
def verify_environment(project: Project, logger: Logger, reactor: Reactor):
|
|
33
|
-
dist_directory = project.get_property(WORKING_TEST_DIR, get_working_distribution_directory(project))
|
|
34
|
-
logger.info(f"Preparing to run tests found in: {dist_directory}")
|
|
35
|
-
_run_tests_in_directory(dist_directory, logger, project, reactor)
|
|
36
|
-
artifact_manager = get_artifact_manager(project=project)
|
|
37
|
-
latest_directory = artifact_manager.download_artifacts(project=project, logger=logger, reactor=reactor)
|
|
38
|
-
_run_tests_in_directory(latest_directory, logger, project, reactor, latest=True)
|
|
39
|
-
if project.get_property(PROMOTE_ARTIFACT, True):
|
|
40
|
-
integration_artifact_push(project=project, logger=logger, reactor=reactor)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
def _should_run_latest(test_dir, project):
|
|
44
|
-
if project.get_property(SHOULD_SKIP_LATEST, False):
|
|
45
|
-
if project.get_property(ROLE) == test_dir:
|
|
46
|
-
return False
|
|
47
|
-
return True
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def _run_tests_in_directory(dist_directory, logger, project, reactor, latest=False):
|
|
51
|
-
logger.debug(f"Run tests in directory: {os.listdir(dist_directory)} ")
|
|
52
|
-
_run_tavern_tests_in_dist_dir(dist_directory, latest, logger, project, reactor)
|
|
53
|
-
_run_cypress_tests_in_dist_dir(dist_directory, latest, logger, project, reactor)
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def _run_cypress_tests_in_dist_dir(dist_directory, latest, logger, project, reactor):
|
|
57
|
-
total_time = Timer.start()
|
|
58
|
-
cypress_test_path = f"{dist_directory}/cypress"
|
|
59
|
-
if os.path.exists(cypress_test_path):
|
|
60
|
-
logger.debug(f"Run cypress tests in directory: {cypress_test_path} ")
|
|
61
|
-
logger.debug(f"Run cypress tests in directory files: {os.listdir(cypress_test_path)} ")
|
|
62
|
-
logger.info(f"Found cypress tests - starting run latest: {latest}")
|
|
63
|
-
if latest:
|
|
64
|
-
for test_dir in os.listdir(cypress_test_path):
|
|
65
|
-
directory_to_test = f"{cypress_test_path}/{test_dir}"
|
|
66
|
-
if os.path.isdir(directory_to_test) and _should_run_latest(test_dir, project):
|
|
67
|
-
logger.info(f"Running {directory_to_test}")
|
|
68
|
-
_run_cypress_tests_in_directory(work_dir=directory_to_test,
|
|
69
|
-
logger=logger,
|
|
70
|
-
project=project,
|
|
71
|
-
reactor=reactor)
|
|
72
|
-
else:
|
|
73
|
-
_run_cypress_tests_in_directory(work_dir=cypress_test_path,
|
|
74
|
-
logger=logger,
|
|
75
|
-
project=project,
|
|
76
|
-
reactor=reactor)
|
|
77
|
-
total_time.stop()
|
|
78
|
-
logger.info(f"Ran Cypress tests: {total_time.get_millis()}")
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def _run_tavern_tests_in_dist_dir(dist_directory, latest, logger, project, reactor):
|
|
82
|
-
total_time = Timer.start()
|
|
83
|
-
tavern_test_path = f"{dist_directory}/tavern"
|
|
84
|
-
if os.path.exists(tavern_test_path):
|
|
85
|
-
logger.info(f"Found tavern tests - starting run latest: {latest}")
|
|
86
|
-
logger.debug(f"Run tavern tests in directory: {tavern_test_path} ")
|
|
87
|
-
logger.debug(f"Run tavern tests in directory files: {os.listdir(tavern_test_path)} ")
|
|
88
|
-
if latest:
|
|
89
|
-
for tavern_dir in os.listdir(tavern_test_path):
|
|
90
|
-
tavern_test_direcotry = f"{tavern_test_path}/{tavern_dir}"
|
|
91
|
-
if os.path.isdir(tavern_test_direcotry) and _should_run_latest(tavern_dir, project):
|
|
92
|
-
logger.info(f"Running {tavern_test_direcotry}")
|
|
93
|
-
_run_tavern_tests_in_dir(test_dir=tavern_test_direcotry,
|
|
94
|
-
logger=logger,
|
|
95
|
-
project=project,
|
|
96
|
-
reactor=reactor,
|
|
97
|
-
role=os.path.basename(tavern_dir))
|
|
98
|
-
else:
|
|
99
|
-
_run_tavern_tests_in_dir(test_dir=f"{tavern_test_path}",
|
|
100
|
-
logger=logger,
|
|
101
|
-
project=project,
|
|
102
|
-
reactor=reactor)
|
|
103
|
-
total_time.stop()
|
|
104
|
-
logger.info(f"Ran Tavern tests: {total_time.get_millis()}")
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def verify_cypress(project: Project, logger: Logger, reactor: Reactor):
|
|
108
|
-
# Get directories with test and cypress executable
|
|
109
|
-
work_dir = project.expand_path(f"${CYPRESS_TEST_DIR}")
|
|
110
|
-
if _run_cypress_tests_in_directory(work_dir=work_dir, logger=logger, project=project, reactor=reactor):
|
|
111
|
-
package_artifacts(project, work_dir, "cypress", project.get_property(ROLE))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
def _run_cypress_tests_in_directory(work_dir, logger, project, reactor: Reactor):
|
|
115
|
-
total_time = Timer.start()
|
|
116
|
-
target_url = project.get_mandatory_property(INTEGRATION_TARGET_URL)
|
|
117
|
-
environment = project.get_mandatory_property(ENVIRONMENT)
|
|
118
|
-
if not os.path.exists(work_dir):
|
|
119
|
-
logger.info("Skipping cypress run: no tests")
|
|
120
|
-
return False
|
|
121
|
-
logger.info(f"Found {len(os.listdir(work_dir))} files in cypress test directory")
|
|
122
|
-
logger.debug(f"Files: {os.listdir(work_dir)} ")
|
|
123
|
-
# Validate NPM install and Install cypress
|
|
124
|
-
package_json = os.path.join(work_dir, "package.json")
|
|
125
|
-
if os.path.exists(package_json):
|
|
126
|
-
logger.info("Found package.json installing dependencies")
|
|
127
|
-
tool_utility.install_npm_dependencies(work_dir, project=project, logger=logger, reactor=reactor)
|
|
128
|
-
else:
|
|
129
|
-
install_cypress(logger=logger, project=project, reactor=reactor, work_dir=work_dir)
|
|
130
|
-
total_time.stop()
|
|
131
|
-
logger.info(f"Configured Cypress Environment: {total_time.get_millis()}")
|
|
132
|
-
total_time = Timer.start()
|
|
133
|
-
executable = os.path.join(work_dir, "node_modules/cypress/bin/cypress")
|
|
134
|
-
results_file, run_name = get_test_report_file(project=project, test_dir=work_dir, tool="cypress")
|
|
135
|
-
# Run the actual tests against the baseURL provided by ${integration_target}
|
|
136
|
-
test_report_folder = directory_utility.prepare_reports_directory(project)
|
|
137
|
-
args = ["run", "--config",
|
|
138
|
-
f"baseUrl={target_url},"
|
|
139
|
-
f"videosFolder={test_report_folder}/videos,"
|
|
140
|
-
f"screenshotsFolder={test_report_folder}/screenshots",
|
|
141
|
-
"--reporter-options",
|
|
142
|
-
f"mochaFile={results_file}"]
|
|
143
|
-
if project.get_property("record_cypress", True):
|
|
144
|
-
args.append('--record')
|
|
145
|
-
_add_config_file(logger, project, args, environment, work_dir)
|
|
146
|
-
environment_variables = project.get_property(ENVIRONMENT_VARIABLES, {})
|
|
147
|
-
logger.info(f"Running cypress on host: {target_url}")
|
|
148
|
-
exec_utility.exec_command(command_name=executable, args=args,
|
|
149
|
-
failure_message="Failed to execute cypress tests", log_file_name='cypress_run.log',
|
|
150
|
-
project=project, reactor=reactor, logger=logger, working_dir=work_dir, report=False,
|
|
151
|
-
env_vars=environment_variables)
|
|
152
|
-
# workaround but cypress output are relative to location of cypress.json, so we need to collapse
|
|
153
|
-
if os.path.exists(f"{work_dir}/target"):
|
|
154
|
-
shutil.copytree(f"{work_dir}/target", "./target", dirs_exist_ok=True)
|
|
155
|
-
total_time.stop()
|
|
156
|
-
logger.info(f"Ran Cypress Tests: {total_time.get_millis()}")
|
|
157
|
-
return True
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
def _add_config_file(logger, project, args, environment, work_dir):
|
|
161
|
-
|
|
162
|
-
# Environment variable override
|
|
163
|
-
cypress_config_file_override = os.environ.get('CYPRESS_CONFIG_FILE')
|
|
164
|
-
if cypress_config_file_override:
|
|
165
|
-
logger.info(f"Found CYPRESS_CONFIG_FILE environment variable: {cypress_config_file_override}")
|
|
166
|
-
project.set_property(CYPRESS_CONFIG_FILE, cypress_config_file_override)
|
|
167
|
-
|
|
168
|
-
# Check project for config file else test file system for environment named file
|
|
169
|
-
cypress_config_file = project.get_property(CYPRESS_CONFIG_FILE)
|
|
170
|
-
if cypress_config_file and os.path.exists(os.path.join(work_dir, cypress_config_file)):
|
|
171
|
-
logger.info(f"Setting --config-file parameter from project to {cypress_config_file}")
|
|
172
|
-
args.append("--config-file")
|
|
173
|
-
args.append(cypress_config_file)
|
|
174
|
-
else:
|
|
175
|
-
logger.info(f"Attempting to discover cypress config file from environment: {environment}")
|
|
176
|
-
for ending in ['ts', 'json']:
|
|
177
|
-
config_file_path = f'{environment}-config.{ending}'
|
|
178
|
-
if os.path.exists(os.path.join(work_dir, config_file_path)):
|
|
179
|
-
logger.info(f"Setting --config-file parameter from env to {config_file_path}")
|
|
180
|
-
args.append("--config-file")
|
|
181
|
-
args.append(config_file_path)
|
|
182
|
-
return
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def verify_tavern(project: Project, logger: Logger, reactor: Reactor):
|
|
186
|
-
# Expand the directory to get full path
|
|
187
|
-
test_dir = project.expand_path(f"${TAVERN_TEST_DIR}")
|
|
188
|
-
# Run the tests in the directory
|
|
189
|
-
if _run_tavern_tests_in_dir(test_dir, logger, project, reactor):
|
|
190
|
-
package_artifacts(project, test_dir, "tavern", project.get_property(ROLE))
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
def _run_tavern_tests_in_dir(test_dir: str, logger: Logger, project: Project, reactor: Reactor, role=None):
|
|
194
|
-
logger.info("Running tavern tests: {}".format(test_dir))
|
|
195
|
-
if not os.path.exists(test_dir):
|
|
196
|
-
logger.info("Skipping tavern run: no tests")
|
|
197
|
-
return False
|
|
198
|
-
logger.info(f"Found {len(os.listdir(test_dir))} files in tavern test directory")
|
|
199
|
-
# todo is this unique enough for each run?
|
|
200
|
-
output_file, run_name = get_test_report_file(project, test_dir)
|
|
201
|
-
from sys import path as syspath
|
|
202
|
-
syspath.insert(0, test_dir)
|
|
203
|
-
# install any requirements that my exist
|
|
204
|
-
requirements_file = os.path.join(test_dir, "requirements.txt")
|
|
205
|
-
if os.path.exists(requirements_file):
|
|
206
|
-
dependency = RequirementsFile(requirements_file)
|
|
207
|
-
install_dependencies(logger, project, dependency, reactor.pybuilder_venv,
|
|
208
|
-
f"{prepare_logs_directory(project)}/install_tavern_pip_dependencies.log")
|
|
209
|
-
extra_args = [project.expand(prop) for prop in project.get_property(TAVERN_ADDITIONAL_ARGS, [])]
|
|
210
|
-
args = ["--junit-xml", f"{output_file}", test_dir] + extra_args
|
|
211
|
-
if project.get_property("verbose"):
|
|
212
|
-
args.append("-s")
|
|
213
|
-
args.append("-v")
|
|
214
|
-
if project.get_property(RUN_PARALLEL, False):
|
|
215
|
-
args.extend(['-n', 'auto'])
|
|
216
|
-
os.environ['TARGET'] = project.get_property(INTEGRATION_TARGET_URL)
|
|
217
|
-
os.environ['PUBLIC_TARGET'] = project.get_property(INTEGRATION_PUBLIC_TARGET_URL)
|
|
218
|
-
os.environ[ENVIRONMENT] = project.get_property(ENVIRONMENT)
|
|
219
|
-
logger.info(f"Running against target: {project.get_property(INTEGRATION_TARGET_URL)} and "
|
|
220
|
-
f"public target: {project.get_property(INTEGRATION_PUBLIC_TARGET_URL)}")
|
|
221
|
-
cache_wd = os.getcwd()
|
|
222
|
-
try:
|
|
223
|
-
os.chdir(test_dir)
|
|
224
|
-
logger.debug(f"Running args: {args} ")
|
|
225
|
-
ret = pytest.main(args)
|
|
226
|
-
finally:
|
|
227
|
-
os.chdir(cache_wd)
|
|
228
|
-
|
|
229
|
-
if ret != 0:
|
|
230
|
-
if role:
|
|
231
|
-
roles = []
|
|
232
|
-
if project.get_property(CONSOLIDATE_TESTS, False):
|
|
233
|
-
with open(f"{test_dir}/roles") as fp:
|
|
234
|
-
for line in fp:
|
|
235
|
-
roles.append(line.strip())
|
|
236
|
-
else:
|
|
237
|
-
roles.append(role)
|
|
238
|
-
|
|
239
|
-
for service in roles:
|
|
240
|
-
CloudwatchLogs(project.get_property(ENVIRONMENT), project.get_property(APPLICATION), service,
|
|
241
|
-
logger).print_latest()
|
|
242
|
-
raise BuildFailedException(f"Tavern tests failed see complete output here - {output_file}")
|
|
243
|
-
return True
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
def get_test_report_file(project, test_dir, tool="tavern"):
|
|
247
|
-
run_name = os.path.basename(test_dir)
|
|
248
|
-
output_file = os.path.join(prepare_reports_directory(project), f"{tool}-{run_name}.out.xml")
|
|
249
|
-
return output_file, run_name
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from pybuilder.core import Logger, Project
|
|
2
|
-
from pybuilder.reactor import Reactor
|
|
3
|
-
|
|
4
|
-
from pybuilder_integration.exec_utility import exec_command
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def install_cypress(logger: Logger, project: Project, reactor: Reactor, work_dir):
|
|
8
|
-
_verify_npm(reactor)
|
|
9
|
-
logger.info(f"Ensuring cypress is installed")
|
|
10
|
-
exec_command('npm', ['install', "cypress"], f'Failed to install cypress - required for integration tests',
|
|
11
|
-
f'{"cypress"}_npm_install.log', project, reactor, logger, report=False, working_dir=work_dir)
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def _verify_npm(reactor):
|
|
15
|
-
reactor.pybuilder_venv.verify_can_execute(
|
|
16
|
-
command_and_arguments=["npm", "--version"], prerequisite="npm", caller="integration_tests")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def install_npm_dependencies(work_dir, project, logger, reactor):
|
|
20
|
-
_verify_npm(reactor)
|
|
21
|
-
exec_command('npm', ['install'], f'Failed to install package.json - required for integration tests',
|
|
22
|
-
f'package_json_npm_install.log', project, reactor, logger, report=False, working_dir=work_dir)
|
|
23
|
-
|
|
24
|
-
|
|
File without changes
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration/exec_utility.py
RENAMED
|
File without changes
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/requires.txt
RENAMED
|
File without changes
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/top_level.txt
RENAMED
|
File without changes
|
{pybuilder_integration-102 → pybuilder_integration-105}/pybuilder_integration.egg-info/zip-safe
RENAMED
|
File without changes
|
|
File without changes
|