testery 1.15.2.dev31__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.
- testery-1.15.2.dev31/LICENSE +19 -0
- testery-1.15.2.dev31/PKG-INFO +94 -0
- testery-1.15.2.dev31/README.md +67 -0
- testery-1.15.2.dev31/helpers.py +277 -0
- testery-1.15.2.dev31/setup.cfg +4 -0
- testery-1.15.2.dev31/setup.py +27 -0
- testery-1.15.2.dev31/testery.egg-info/PKG-INFO +94 -0
- testery-1.15.2.dev31/testery.egg-info/SOURCES.txt +15 -0
- testery-1.15.2.dev31/testery.egg-info/dependency_links.txt +1 -0
- testery-1.15.2.dev31/testery.egg-info/entry_points.txt +2 -0
- testery-1.15.2.dev31/testery.egg-info/requires.txt +5 -0
- testery-1.15.2.dev31/testery.egg-info/top_level.txt +3 -0
- testery-1.15.2.dev31/testery.py +1451 -0
- testery-1.15.2.dev31/tests/__init__.py +0 -0
- testery-1.15.2.dev31/tests/conftest.py +252 -0
- testery-1.15.2.dev31/tests/test_integration.py +124 -0
- testery-1.15.2.dev31/tests/test_unit.py +642 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2019 Testery LLC
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: testery
|
|
3
|
+
Version: 1.15.2.dev31
|
|
4
|
+
Summary: Testery CLI
|
|
5
|
+
Home-page: https://github.com/testery/testery-cli
|
|
6
|
+
Author: Testery
|
|
7
|
+
Author-email: chris.harbert@testery.io
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: Click
|
|
14
|
+
Requires-Dist: requests>=2.28.0
|
|
15
|
+
Requires-Dist: requests_toolbelt
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Requires-Dist: python-dotenv
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# Testery CLI
|
|
29
|
+
|
|
30
|
+
To install you must have Python 3 and pip installed, then run:
|
|
31
|
+
|
|
32
|
+
`pip install testery`
|
|
33
|
+
|
|
34
|
+
Upgrade via
|
|
35
|
+
|
|
36
|
+
`pip install testery --upgrade`
|
|
37
|
+
|
|
38
|
+
## Create Test Run
|
|
39
|
+
|
|
40
|
+
Starts a test run.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
testery create-test-run --token <yourTesteryApiToken> --project <projectKeyFromTestery> --build-id <uniqueBuildIdOfYourChoice> --environment <environmentToBeTested> --wait-for-results
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When set, `--fail-on-failure`, will return an exit code of 1 if there are test failures.
|
|
47
|
+
|
|
48
|
+
### Output Formats
|
|
49
|
+
|
|
50
|
+
- teamcity
|
|
51
|
+
- pretty
|
|
52
|
+
- json
|
|
53
|
+
- appveyor
|
|
54
|
+
- octopus
|
|
55
|
+
|
|
56
|
+
## Create Test Environment
|
|
57
|
+
|
|
58
|
+
Creates an environment where tests may be run.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
testery create-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --variable "secure:KEY3=SECRET" --pipeline-stage "<pipeline_stage_name>"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Update Test Environment
|
|
65
|
+
|
|
66
|
+
Updates an environment where tests are run with some new variables or pipeline stage.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
testery update-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --pipeline-stage "<pipeline_stage_name>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Add environment variables from .env file
|
|
73
|
+
|
|
74
|
+
Takes in a .env file and adds or overwrites variables in target environment.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
testery add-env-vars-from-file --token "<your_Testery_Api_Token>" --environment-key "<key>" --env-file "<path/to/file/.env>" --overwrite
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Update Test Stage
|
|
81
|
+
|
|
82
|
+
Updates or creates a pipeline stage where tests are run with some new variables.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
testery update-pipeline-stage --token "<your_Testery_Api_Token>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --create-if-not-exists
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Add Pipeline Stage variables from .env file
|
|
89
|
+
|
|
90
|
+
Takes in a .env file and adds or overwrites variables in target Pipeline.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
testery add-stage-vars-from-file --token "<your_Testery_Api_Token>" --name "<name>" --env-file "<path/to/file/.env>" --overwrite
|
|
94
|
+
```
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# Testery CLI
|
|
2
|
+
|
|
3
|
+
To install you must have Python 3 and pip installed, then run:
|
|
4
|
+
|
|
5
|
+
`pip install testery`
|
|
6
|
+
|
|
7
|
+
Upgrade via
|
|
8
|
+
|
|
9
|
+
`pip install testery --upgrade`
|
|
10
|
+
|
|
11
|
+
## Create Test Run
|
|
12
|
+
|
|
13
|
+
Starts a test run.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
testery create-test-run --token <yourTesteryApiToken> --project <projectKeyFromTestery> --build-id <uniqueBuildIdOfYourChoice> --environment <environmentToBeTested> --wait-for-results
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
When set, `--fail-on-failure`, will return an exit code of 1 if there are test failures.
|
|
20
|
+
|
|
21
|
+
### Output Formats
|
|
22
|
+
|
|
23
|
+
- teamcity
|
|
24
|
+
- pretty
|
|
25
|
+
- json
|
|
26
|
+
- appveyor
|
|
27
|
+
- octopus
|
|
28
|
+
|
|
29
|
+
## Create Test Environment
|
|
30
|
+
|
|
31
|
+
Creates an environment where tests may be run.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
testery create-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --variable "secure:KEY3=SECRET" --pipeline-stage "<pipeline_stage_name>"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Update Test Environment
|
|
38
|
+
|
|
39
|
+
Updates an environment where tests are run with some new variables or pipeline stage.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
testery update-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --pipeline-stage "<pipeline_stage_name>"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Add environment variables from .env file
|
|
46
|
+
|
|
47
|
+
Takes in a .env file and adds or overwrites variables in target environment.
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
testery add-env-vars-from-file --token "<your_Testery_Api_Token>" --environment-key "<key>" --env-file "<path/to/file/.env>" --overwrite
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Update Test Stage
|
|
54
|
+
|
|
55
|
+
Updates or creates a pipeline stage where tests are run with some new variables.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
testery update-pipeline-stage --token "<your_Testery_Api_Token>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --create-if-not-exists
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Add Pipeline Stage variables from .env file
|
|
62
|
+
|
|
63
|
+
Takes in a .env file and adds or overwrites variables in target Pipeline.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
testery add-stage-vars-from-file --token "<your_Testery_Api_Token>" --name "<name>" --env-file "<path/to/file/.env>" --overwrite
|
|
67
|
+
```
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import mimetypes
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from zipfile import ZipFile
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
import dateutil.parser
|
|
9
|
+
import requests
|
|
10
|
+
# we have to do this right now because of how our S3 buckets are named. This should be removed once we've fixed that
|
|
11
|
+
import urllib3
|
|
12
|
+
from dotenv import dotenv_values
|
|
13
|
+
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
|
14
|
+
|
|
15
|
+
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
|
|
16
|
+
|
|
17
|
+
headers = {'Content-type': 'application/json', 'Accept': 'application/json'}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CommonCommands(click.core.Command):
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
self.params.insert(0, click.Option(('--token',), required=True, help='Your Testery API token.'))
|
|
24
|
+
self.params.insert(0, click.Option(('--testery-dev',), is_flag=True, default=False, hidden=True))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def get_api_url(is_dev):
|
|
28
|
+
if is_dev:
|
|
29
|
+
return 'https://api.dev.testery.io/api'
|
|
30
|
+
return 'https://api.testery.io/api'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def report_test_run(test_run, output):
|
|
34
|
+
if output == "teamcity":
|
|
35
|
+
report_teamcity_test_run(test_run)
|
|
36
|
+
elif output == "json":
|
|
37
|
+
print(test_run)
|
|
38
|
+
else:
|
|
39
|
+
report_pretty_test_run(test_run)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def report_pretty_test_run(test_run):
|
|
43
|
+
if test_run['status'] == 'SUBMITTED':
|
|
44
|
+
print("Waiting for test run to start")
|
|
45
|
+
elif test_run['totalCount'] == 0 and test_run['status'] == 'RUNNING':
|
|
46
|
+
print("Getting list of tests to run")
|
|
47
|
+
elif test_run['totalCount'] == 0:
|
|
48
|
+
print("No tests run")
|
|
49
|
+
elif test_run['ignoredCount'] == 0:
|
|
50
|
+
if test_run['status'] == 'RUNNING':
|
|
51
|
+
text = "Running"
|
|
52
|
+
else:
|
|
53
|
+
text = "Completed"
|
|
54
|
+
print("%s: %s of %s pass with %s fail" % (text, test_run['passCount'], test_run['totalCount'], test_run['failCount']))
|
|
55
|
+
else:
|
|
56
|
+
if test_run['status'] == 'RUNNING':
|
|
57
|
+
text = "Running"
|
|
58
|
+
else:
|
|
59
|
+
text = "Completed"
|
|
60
|
+
print("%s: %s of %s pass with %s fail and %s ignored" % (text, test_run['passCount'], test_run['totalCount'], test_run['failCount'], test_run['ignoredCount']))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def report_teamcity_test_run(test_run):
|
|
64
|
+
if test_run['status'] == 'FAIL':
|
|
65
|
+
print("##teamcity[buildProblem description='%s: %s passing, %s failing out of %s total']" % (
|
|
66
|
+
test_run['status'], test_run['passCount'], test_run['failCount'], test_run['totalCount']))
|
|
67
|
+
else:
|
|
68
|
+
print("##teamcity[buildStatus text='%s: %s passing, %s failing out of %s total']" % (
|
|
69
|
+
test_run['status'], test_run['passCount'], test_run['failCount'], test_run['totalCount']))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def api_wait_test_run(api_url, token, test_run_id, output, fail_on_failure):
|
|
73
|
+
headers['Authorization'] = "Bearer " + token
|
|
74
|
+
|
|
75
|
+
test_run = requests.get(api_url + '/test-runs/' + str(test_run_id), headers=headers).json()
|
|
76
|
+
|
|
77
|
+
report_test_run(test_run, output)
|
|
78
|
+
|
|
79
|
+
while test_run['status'] not in ['PASS', 'FAIL', 'CANCELED']:
|
|
80
|
+
time.sleep(15)
|
|
81
|
+
test_run = requests.get(api_url + '/test-runs/' + str(test_run_id), headers=headers).json()
|
|
82
|
+
report_test_run(test_run, output)
|
|
83
|
+
|
|
84
|
+
if (test_run['status'] == 'FAIL' or test_run['status'] == 'CANCELED') and fail_on_failure:
|
|
85
|
+
print('Test run had failures')
|
|
86
|
+
sys.exit(1)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def api_wait_deploy(api_url, token, deploy_id, output, fail_on_failure):
|
|
90
|
+
headers['Authorization'] = "Bearer " + token
|
|
91
|
+
|
|
92
|
+
test_runs = requests.get(api_url + '/test-runs/by-deploy/' + str(deploy_id), headers=headers).json()
|
|
93
|
+
|
|
94
|
+
for test_run in test_runs:
|
|
95
|
+
click.echo("Waiting for test run: %s" % test_run['id'])
|
|
96
|
+
api_wait_test_run(api_url, token, test_run['id'], output, fail_on_failure)
|
|
97
|
+
|
|
98
|
+
click.echo("All test runs for deployment #%s are complete." % deploy_id)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def add_vars_from_file(file: str, body: dict, overwrite: bool = False):
|
|
102
|
+
new_vars = {}
|
|
103
|
+
json = []
|
|
104
|
+
for var in body["variables"]:
|
|
105
|
+
new_vars[var['key']] = var
|
|
106
|
+
config = dotenv_values(file)
|
|
107
|
+
for var in config:
|
|
108
|
+
if var in new_vars:
|
|
109
|
+
if overwrite:
|
|
110
|
+
new_vars[var] = {'key': var, 'value': config[var], 'encrypted': False, 'file': False}
|
|
111
|
+
else:
|
|
112
|
+
new_vars[var] = {'key': var, 'value': config[var], 'encrypted': False, 'file': False}
|
|
113
|
+
for value in new_vars.values():
|
|
114
|
+
json.append(value)
|
|
115
|
+
body["variables"] = json
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def find_environment_by_key(api_url, token, environment_key):
|
|
119
|
+
environments_response = requests.get(api_url + '/environments', headers=headers)
|
|
120
|
+
|
|
121
|
+
environments = environments_response.json()
|
|
122
|
+
|
|
123
|
+
for environment in environments:
|
|
124
|
+
if environment['key'] == environment_key:
|
|
125
|
+
return environment
|
|
126
|
+
|
|
127
|
+
raise Exception("Environment with key not found: " + environment_key)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def find_test_plan_by_key(api_url, token, test_plan_key):
|
|
131
|
+
test_plans_response = requests.get(api_url + '/test-plans', headers=headers)
|
|
132
|
+
|
|
133
|
+
testplans = test_plans_response.json()
|
|
134
|
+
|
|
135
|
+
for test_plan in testplans:
|
|
136
|
+
if test_plan['key'] == test_plan_key:
|
|
137
|
+
return test_plan
|
|
138
|
+
|
|
139
|
+
raise Exception("Test Plan with key not found: " + test_plan_key)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def find_pipeline_stage_by_name(api_url, pipeline_stage_name):
|
|
143
|
+
stages_response = requests.get(api_url + '/pipeline-stages', headers=headers)
|
|
144
|
+
stages_response.raise_for_status()
|
|
145
|
+
|
|
146
|
+
stages = stages_response.json()
|
|
147
|
+
lower_name = pipeline_stage_name.lower()
|
|
148
|
+
for stage in stages:
|
|
149
|
+
if stage['name'].lower() == lower_name:
|
|
150
|
+
return stage
|
|
151
|
+
|
|
152
|
+
stage_names = map(lambda s: s['name'], stages)
|
|
153
|
+
message = "Could not find pipeline stage with name '" + pipeline_stage_name + "'. Known names: " + ', '.join(stage_names)
|
|
154
|
+
raise Exception(message)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def find_project_by_key(api_url, token, project_key):
|
|
158
|
+
response = requests.get(api_url + '/projects', headers=headers)
|
|
159
|
+
|
|
160
|
+
projects = response.json()
|
|
161
|
+
|
|
162
|
+
for project in projects:
|
|
163
|
+
if project['key'] == project_key:
|
|
164
|
+
return project
|
|
165
|
+
|
|
166
|
+
raise Exception("Project with key not found: " + project_key)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def find_test_suite_by_name(api_url, suite_name, project_id):
|
|
170
|
+
test_suites_response = requests.get(api_url + f'/projects/{project_id}/test-suites', headers=headers)
|
|
171
|
+
|
|
172
|
+
test_suites = test_suites_response.json()
|
|
173
|
+
lower_name = suite_name.lower()
|
|
174
|
+
|
|
175
|
+
for suite in test_suites:
|
|
176
|
+
if suite['name'].lower() == lower_name:
|
|
177
|
+
return suite
|
|
178
|
+
|
|
179
|
+
test_suite_names = map(lambda s: s['name'], test_suites)
|
|
180
|
+
message = "Could not find test suite with name '" + suite_name + "'. Known names: " + ', '.join(test_suite_names)
|
|
181
|
+
raise Exception(message)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def handle_variables(variables, request):
|
|
185
|
+
if variables:
|
|
186
|
+
vars = []
|
|
187
|
+
for var in variables:
|
|
188
|
+
(vkey, vval) = var.split("=")
|
|
189
|
+
|
|
190
|
+
if vkey.startswith("secure:"):
|
|
191
|
+
vkey = vkey.replace("secure:", "")
|
|
192
|
+
encrypted = True
|
|
193
|
+
else:
|
|
194
|
+
encrypted = False
|
|
195
|
+
|
|
196
|
+
v = {"key": vkey, "value": vval, "encrypted": encrypted}
|
|
197
|
+
vars.append(v)
|
|
198
|
+
request["variables"] = vars
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def create_zip_file(path, temp_dir):
|
|
202
|
+
zip_file = ZipFile(os.path.join(temp_dir, str(time.time()) + '.zip'), 'w')
|
|
203
|
+
len_path = len(path)
|
|
204
|
+
with zip_file as zip_file:
|
|
205
|
+
exclude = {'.git', 'node_modules'}
|
|
206
|
+
for root, dirs, files in os.walk(path):
|
|
207
|
+
dirs[:] = [d for d in dirs if d not in exclude]
|
|
208
|
+
for file in files:
|
|
209
|
+
file_path = os.path.join(root, file)
|
|
210
|
+
zip_file.write(file_path, file_path[len_path:])
|
|
211
|
+
return zip_file
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def upload_build_artifact_dir(base_api_url, file_path, dir_path):
|
|
215
|
+
for file in os.listdir(file_path):
|
|
216
|
+
new_file_path = os.path.join(file_path, file)
|
|
217
|
+
if os.path.isdir(new_file_path):
|
|
218
|
+
dir_name = os.path.basename(new_file_path)
|
|
219
|
+
upload_build_artifact_dir(
|
|
220
|
+
base_api_url, new_file_path, dir_path + dir_name + '/')
|
|
221
|
+
else:
|
|
222
|
+
upload_build_artifact_file(base_api_url, new_file_path, dir_path)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def upload_build_artifact_file(base_api_url, file_path, dir_path):
|
|
226
|
+
file = dir_path + os.path.basename(file_path)
|
|
227
|
+
|
|
228
|
+
upload_result = requests.get(base_api_url + file, headers=headers)
|
|
229
|
+
|
|
230
|
+
upload_url = upload_result.text
|
|
231
|
+
|
|
232
|
+
if upload_result.status_code != 200 or upload_url == "invalid request":
|
|
233
|
+
raise Exception("Could not upload artifacts. Make sure you specified the correct project key.")
|
|
234
|
+
|
|
235
|
+
result = upload_file(
|
|
236
|
+
upload_url, {'Accept': 'application/json'}, file_path, False)
|
|
237
|
+
|
|
238
|
+
if result.status_code != 200:
|
|
239
|
+
raise Exception("Got status code " + str(result.status_code) +
|
|
240
|
+
" when trying to upload file with path " + file_path)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def upload_file(url, headers, file_path, for_add_file):
|
|
244
|
+
file_name = os.path.basename(file_path)
|
|
245
|
+
mime_type = mimetypes.guess_type(file_path)[0]
|
|
246
|
+
|
|
247
|
+
if mime_type is None:
|
|
248
|
+
mime_type = "text/plain"
|
|
249
|
+
|
|
250
|
+
if for_add_file:
|
|
251
|
+
data = MultipartEncoder(
|
|
252
|
+
fields={'file': (file_name, open(file_path, 'rb'), mime_type)})
|
|
253
|
+
headers['Content-Type'] = data.content_type
|
|
254
|
+
return requests.post(url, headers=headers, data=data)
|
|
255
|
+
else:
|
|
256
|
+
headers['Content-Type'] = mime_type
|
|
257
|
+
data = open(file_path, 'rb')
|
|
258
|
+
return requests.put(url, data=data, verify=False)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def wait_for_maintenance_window():
|
|
262
|
+
status = requests.get('http://status.testery.io/api/v2/scheduled-maintenances/active.json', headers=headers)
|
|
263
|
+
status.raise_for_status()
|
|
264
|
+
scheduled_maintenances = status.json().get('scheduled_maintenances')
|
|
265
|
+
|
|
266
|
+
if len(scheduled_maintenances) > 0:
|
|
267
|
+
scheduled_until = dateutil.parser.parse(scheduled_maintenances[0]['scheduled_until'])
|
|
268
|
+
print("Testery is undergoing scheduled maintenance which is scheduled to end at %s UTC. To make sure you have a stable test run, we are pausing your build. Your build will resume automatically as soon as the system is confirmed to be fully operational. Learn more about the event at http://status.testery.io/\n" % scheduled_until)
|
|
269
|
+
|
|
270
|
+
while len(scheduled_maintenances) > 0:
|
|
271
|
+
print(".")
|
|
272
|
+
time.sleep(300)
|
|
273
|
+
status = requests.get('http://status.testery.io/api/v2/scheduled-maintenances/active.json', headers=headers)
|
|
274
|
+
status.raise_for_status()
|
|
275
|
+
scheduled_maintenances = status.json().get('scheduled_maintenances')
|
|
276
|
+
|
|
277
|
+
print("Testery's scheduled maintenance is now complete. We'd like to thank you for your patience and for being such an awesome customer. Happy testing and good luck with this test run!")
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import setuptools
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setuptools.setup(
|
|
7
|
+
name='testery',
|
|
8
|
+
version='1.15.2-dev31',
|
|
9
|
+
install_requires=['Click', 'requests>=2.28.0', 'requests_toolbelt', 'python-dateutil', 'python-dotenv'],
|
|
10
|
+
py_modules=['testery', 'helpers'],
|
|
11
|
+
entry_points='''
|
|
12
|
+
[console_scripts]
|
|
13
|
+
testery=testery:cli
|
|
14
|
+
''',
|
|
15
|
+
author="Testery",
|
|
16
|
+
author_email="chris.harbert@testery.io",
|
|
17
|
+
description="Testery CLI",
|
|
18
|
+
long_description=long_description,
|
|
19
|
+
long_description_content_type="text/markdown",
|
|
20
|
+
url="https://github.com/testery/testery-cli",
|
|
21
|
+
packages=setuptools.find_packages(),
|
|
22
|
+
classifiers=[
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
],
|
|
27
|
+
)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: testery
|
|
3
|
+
Version: 1.15.2.dev31
|
|
4
|
+
Summary: Testery CLI
|
|
5
|
+
Home-page: https://github.com/testery/testery-cli
|
|
6
|
+
Author: Testery
|
|
7
|
+
Author-email: chris.harbert@testery.io
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: Click
|
|
14
|
+
Requires-Dist: requests>=2.28.0
|
|
15
|
+
Requires-Dist: requests_toolbelt
|
|
16
|
+
Requires-Dist: python-dateutil
|
|
17
|
+
Requires-Dist: python-dotenv
|
|
18
|
+
Dynamic: author
|
|
19
|
+
Dynamic: author-email
|
|
20
|
+
Dynamic: classifier
|
|
21
|
+
Dynamic: description
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: home-page
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: requires-dist
|
|
26
|
+
Dynamic: summary
|
|
27
|
+
|
|
28
|
+
# Testery CLI
|
|
29
|
+
|
|
30
|
+
To install you must have Python 3 and pip installed, then run:
|
|
31
|
+
|
|
32
|
+
`pip install testery`
|
|
33
|
+
|
|
34
|
+
Upgrade via
|
|
35
|
+
|
|
36
|
+
`pip install testery --upgrade`
|
|
37
|
+
|
|
38
|
+
## Create Test Run
|
|
39
|
+
|
|
40
|
+
Starts a test run.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
testery create-test-run --token <yourTesteryApiToken> --project <projectKeyFromTestery> --build-id <uniqueBuildIdOfYourChoice> --environment <environmentToBeTested> --wait-for-results
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
When set, `--fail-on-failure`, will return an exit code of 1 if there are test failures.
|
|
47
|
+
|
|
48
|
+
### Output Formats
|
|
49
|
+
|
|
50
|
+
- teamcity
|
|
51
|
+
- pretty
|
|
52
|
+
- json
|
|
53
|
+
- appveyor
|
|
54
|
+
- octopus
|
|
55
|
+
|
|
56
|
+
## Create Test Environment
|
|
57
|
+
|
|
58
|
+
Creates an environment where tests may be run.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
testery create-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --variable "secure:KEY3=SECRET" --pipeline-stage "<pipeline_stage_name>"
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Update Test Environment
|
|
65
|
+
|
|
66
|
+
Updates an environment where tests are run with some new variables or pipeline stage.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
testery update-environment --token "<your_Testery_Api_Token>" --key "<key>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --pipeline-stage "<pipeline_stage_name>"
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Add environment variables from .env file
|
|
73
|
+
|
|
74
|
+
Takes in a .env file and adds or overwrites variables in target environment.
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
testery add-env-vars-from-file --token "<your_Testery_Api_Token>" --environment-key "<key>" --env-file "<path/to/file/.env>" --overwrite
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Update Test Stage
|
|
81
|
+
|
|
82
|
+
Updates or creates a pipeline stage where tests are run with some new variables.
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
testery update-pipeline-stage --token "<your_Testery_Api_Token>" --name "<name>" --variable "KEY1=FOO1" --variable "KEY2=FOO2" --create-if-not-exists
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Add Pipeline Stage variables from .env file
|
|
89
|
+
|
|
90
|
+
Takes in a .env file and adds or overwrites variables in target Pipeline.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
testery add-stage-vars-from-file --token "<your_Testery_Api_Token>" --name "<name>" --env-file "<path/to/file/.env>" --overwrite
|
|
94
|
+
```
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
helpers.py
|
|
4
|
+
setup.py
|
|
5
|
+
testery.py
|
|
6
|
+
testery.egg-info/PKG-INFO
|
|
7
|
+
testery.egg-info/SOURCES.txt
|
|
8
|
+
testery.egg-info/dependency_links.txt
|
|
9
|
+
testery.egg-info/entry_points.txt
|
|
10
|
+
testery.egg-info/requires.txt
|
|
11
|
+
testery.egg-info/top_level.txt
|
|
12
|
+
tests/__init__.py
|
|
13
|
+
tests/conftest.py
|
|
14
|
+
tests/test_integration.py
|
|
15
|
+
tests/test_unit.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|