smart-tests-cli 2.0.0__py3-none-any.whl
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.
- smart_tests/__init__.py +0 -0
- smart_tests/__main__.py +60 -0
- smart_tests/app.py +67 -0
- smart_tests/args4p/README.md +102 -0
- smart_tests/args4p/__init__.py +13 -0
- smart_tests/args4p/argument.py +45 -0
- smart_tests/args4p/command.py +593 -0
- smart_tests/args4p/converters/__init__.py +75 -0
- smart_tests/args4p/decorators.py +98 -0
- smart_tests/args4p/exceptions.py +12 -0
- smart_tests/args4p/option.py +85 -0
- smart_tests/args4p/parameter.py +84 -0
- smart_tests/args4p/typer/__init__.py +42 -0
- smart_tests/commands/__init__.py +0 -0
- smart_tests/commands/compare/__init__.py +11 -0
- smart_tests/commands/compare/subsets.py +58 -0
- smart_tests/commands/detect_flakes.py +105 -0
- smart_tests/commands/inspect/__init__.py +13 -0
- smart_tests/commands/inspect/model.py +52 -0
- smart_tests/commands/inspect/subset.py +138 -0
- smart_tests/commands/record/__init__.py +19 -0
- smart_tests/commands/record/attachment.py +38 -0
- smart_tests/commands/record/build.py +356 -0
- smart_tests/commands/record/case_event.py +190 -0
- smart_tests/commands/record/commit.py +157 -0
- smart_tests/commands/record/session.py +120 -0
- smart_tests/commands/record/tests.py +498 -0
- smart_tests/commands/stats/__init__.py +11 -0
- smart_tests/commands/stats/test_sessions.py +45 -0
- smart_tests/commands/subset.py +567 -0
- smart_tests/commands/test_path_writer.py +51 -0
- smart_tests/commands/verify.py +153 -0
- smart_tests/jar/exe_deploy.jar +0 -0
- smart_tests/plugins/__init__.py +0 -0
- smart_tests/test_runners/__init__.py +0 -0
- smart_tests/test_runners/adb.py +24 -0
- smart_tests/test_runners/ant.py +35 -0
- smart_tests/test_runners/bazel.py +103 -0
- smart_tests/test_runners/behave.py +62 -0
- smart_tests/test_runners/codeceptjs.py +33 -0
- smart_tests/test_runners/ctest.py +164 -0
- smart_tests/test_runners/cts.py +189 -0
- smart_tests/test_runners/cucumber.py +451 -0
- smart_tests/test_runners/cypress.py +46 -0
- smart_tests/test_runners/dotnet.py +106 -0
- smart_tests/test_runners/file.py +20 -0
- smart_tests/test_runners/flutter.py +251 -0
- smart_tests/test_runners/go_test.py +99 -0
- smart_tests/test_runners/googletest.py +34 -0
- smart_tests/test_runners/gradle.py +96 -0
- smart_tests/test_runners/jest.py +52 -0
- smart_tests/test_runners/maven.py +149 -0
- smart_tests/test_runners/minitest.py +40 -0
- smart_tests/test_runners/nunit.py +190 -0
- smart_tests/test_runners/playwright.py +252 -0
- smart_tests/test_runners/prove.py +74 -0
- smart_tests/test_runners/pytest.py +358 -0
- smart_tests/test_runners/raw.py +238 -0
- smart_tests/test_runners/robot.py +125 -0
- smart_tests/test_runners/rspec.py +5 -0
- smart_tests/test_runners/smart_tests.py +235 -0
- smart_tests/test_runners/vitest.py +49 -0
- smart_tests/test_runners/xctest.py +79 -0
- smart_tests/testpath.py +154 -0
- smart_tests/utils/__init__.py +0 -0
- smart_tests/utils/authentication.py +78 -0
- smart_tests/utils/ci_provider.py +7 -0
- smart_tests/utils/commands.py +14 -0
- smart_tests/utils/commit_ingester.py +59 -0
- smart_tests/utils/common_tz.py +12 -0
- smart_tests/utils/edit_distance.py +11 -0
- smart_tests/utils/env_keys.py +19 -0
- smart_tests/utils/exceptions.py +34 -0
- smart_tests/utils/fail_fast_mode.py +99 -0
- smart_tests/utils/file_name_pattern.py +4 -0
- smart_tests/utils/git_log_parser.py +53 -0
- smart_tests/utils/glob.py +44 -0
- smart_tests/utils/gzipgen.py +46 -0
- smart_tests/utils/http_client.py +169 -0
- smart_tests/utils/java.py +61 -0
- smart_tests/utils/link.py +149 -0
- smart_tests/utils/logger.py +53 -0
- smart_tests/utils/no_build.py +2 -0
- smart_tests/utils/sax.py +119 -0
- smart_tests/utils/session.py +73 -0
- smart_tests/utils/smart_tests_client.py +134 -0
- smart_tests/utils/subprocess.py +12 -0
- smart_tests/utils/tracking.py +95 -0
- smart_tests/utils/typer_types.py +241 -0
- smart_tests/version.py +7 -0
- smart_tests_cli-2.0.0.dist-info/METADATA +168 -0
- smart_tests_cli-2.0.0.dist-info/RECORD +96 -0
- smart_tests_cli-2.0.0.dist-info/WHEEL +5 -0
- smart_tests_cli-2.0.0.dist-info/entry_points.txt +2 -0
- smart_tests_cli-2.0.0.dist-info/licenses/LICENSE.txt +202 -0
- smart_tests_cli-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, Union
|
|
3
|
+
|
|
4
|
+
from requests import Session
|
|
5
|
+
|
|
6
|
+
from smart_tests.app import Application
|
|
7
|
+
from smart_tests.utils.authentication import get_org_workspace
|
|
8
|
+
from smart_tests.utils.http_client import _HttpClient, _join_paths
|
|
9
|
+
from smart_tests.version import __version__
|
|
10
|
+
|
|
11
|
+
from .commands import Command
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Tracking:
|
|
15
|
+
# General events
|
|
16
|
+
class Event(Enum):
|
|
17
|
+
SHALLOW_CLONE = 'SHALLOW_CLONE' # this event is an example
|
|
18
|
+
PERFORMANCE = 'PERFORMANCE'
|
|
19
|
+
|
|
20
|
+
# Error events
|
|
21
|
+
class ErrorEvent(Enum):
|
|
22
|
+
UNKNOWN_ERROR = 'UNKNOWN_ERROR'
|
|
23
|
+
INTERNAL_CLI_ERROR = 'INTERNAL_CLI_ERROR'
|
|
24
|
+
WARNING_ERROR = 'WARNING_ERROR'
|
|
25
|
+
USER_ERROR = 'USER_ERROR'
|
|
26
|
+
# Errors related to requests package
|
|
27
|
+
NETWORK_ERROR = 'NETWORK_ERROR'
|
|
28
|
+
TIMEOUT_ERROR = 'TIMEOUT_ERROR'
|
|
29
|
+
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR'
|
|
30
|
+
UNEXPECTED_HTTP_STATUS_ERROR = 'UNEXPECTED_HTTP_STATUS_ERROR'
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TrackingClient:
|
|
34
|
+
def __init__(self, command: Command, base_url: str = "", session: Session | None = None,
|
|
35
|
+
app: Application | None = None):
|
|
36
|
+
self.http_client = _HttpClient(
|
|
37
|
+
base_url=base_url,
|
|
38
|
+
session=session,
|
|
39
|
+
app=app
|
|
40
|
+
)
|
|
41
|
+
self.command = command
|
|
42
|
+
|
|
43
|
+
def send_event(
|
|
44
|
+
self,
|
|
45
|
+
event_name: Tracking.Event,
|
|
46
|
+
metadata: Dict[str, Any] | None = None
|
|
47
|
+
):
|
|
48
|
+
org, workspace = get_org_workspace()
|
|
49
|
+
if metadata is None:
|
|
50
|
+
metadata = {}
|
|
51
|
+
metadata["organization"] = org or ""
|
|
52
|
+
metadata["workspace"] = workspace or ""
|
|
53
|
+
self._post_payload(
|
|
54
|
+
event_name=event_name,
|
|
55
|
+
metadata=metadata,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def send_error_event(
|
|
59
|
+
self,
|
|
60
|
+
event_name: Tracking.ErrorEvent,
|
|
61
|
+
stack_trace: str,
|
|
62
|
+
api: str = "",
|
|
63
|
+
metadata: Dict[str, Any] | None = None
|
|
64
|
+
):
|
|
65
|
+
org, workspace = get_org_workspace()
|
|
66
|
+
if metadata is None:
|
|
67
|
+
metadata = {}
|
|
68
|
+
metadata["stackTrace"] = stack_trace
|
|
69
|
+
metadata["organization"] = org or ""
|
|
70
|
+
metadata["workspace"] = workspace or ""
|
|
71
|
+
metadata["api"] = api
|
|
72
|
+
self._post_payload(
|
|
73
|
+
event_name=event_name,
|
|
74
|
+
metadata=metadata,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _post_payload(
|
|
78
|
+
self,
|
|
79
|
+
event_name: Union[Tracking.Event, Tracking.ErrorEvent],
|
|
80
|
+
metadata: Dict[str, Any]
|
|
81
|
+
):
|
|
82
|
+
payload = {
|
|
83
|
+
"command": self.command.value,
|
|
84
|
+
"eventName": event_name.value,
|
|
85
|
+
"cliVersion": __version__,
|
|
86
|
+
"metadata": metadata,
|
|
87
|
+
}
|
|
88
|
+
path = _join_paths(
|
|
89
|
+
'/intake',
|
|
90
|
+
'cli_tracking'
|
|
91
|
+
)
|
|
92
|
+
try:
|
|
93
|
+
self.http_client.request('post', payload=payload, path=path)
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import re
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import dateutil.parser
|
|
6
|
+
from dateutil.tz import tzlocal
|
|
7
|
+
|
|
8
|
+
from smart_tests.args4p.exceptions import BadCmdLineException
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Percentage:
|
|
12
|
+
def __init__(self, value: float):
|
|
13
|
+
self.value = value
|
|
14
|
+
|
|
15
|
+
def __str__(self):
|
|
16
|
+
return f"{self.value * 100}%"
|
|
17
|
+
|
|
18
|
+
def __float__(self):
|
|
19
|
+
return self.value
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def parse_percentage(value: str) -> Percentage:
|
|
23
|
+
try:
|
|
24
|
+
missing_percent = False
|
|
25
|
+
if value.endswith('%'):
|
|
26
|
+
x = float(value[:-1]) / 100
|
|
27
|
+
if 0 <= x <= 1:
|
|
28
|
+
return Percentage(x)
|
|
29
|
+
else:
|
|
30
|
+
missing_percent = True
|
|
31
|
+
except ValueError:
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
msg = "Expected percentage like 50% but got '{}'".format(value)
|
|
35
|
+
if missing_percent and sys.platform.startswith("win"):
|
|
36
|
+
msg += " ('%' is a special character in batch files, so please write '50%%' to pass in '50%')"
|
|
37
|
+
raise BadCmdLineException(msg)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class Duration:
|
|
41
|
+
def __init__(self, seconds: float):
|
|
42
|
+
self.seconds = seconds
|
|
43
|
+
|
|
44
|
+
def __str__(self):
|
|
45
|
+
return f"{self.seconds}s"
|
|
46
|
+
|
|
47
|
+
def __float__(self):
|
|
48
|
+
return self.seconds
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def parse_duration(value: str) -> Duration:
|
|
52
|
+
try:
|
|
53
|
+
return Duration(convert_to_seconds(value))
|
|
54
|
+
except ValueError:
|
|
55
|
+
raise BadCmdLineException("Expected duration like 3600, 30m, 1h15m but got '{}'".format(value))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class KeyValue:
|
|
59
|
+
def __init__(self, key: str, value: str):
|
|
60
|
+
self.key = key
|
|
61
|
+
self.value = value
|
|
62
|
+
|
|
63
|
+
def __str__(self):
|
|
64
|
+
return f"{self.key}={self.value}"
|
|
65
|
+
|
|
66
|
+
def __iter__(self):
|
|
67
|
+
return iter((self.key, self.value))
|
|
68
|
+
|
|
69
|
+
def __getitem__(self, index):
|
|
70
|
+
return (self.key, self.value)[index]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def parse_key_value(value: str) -> KeyValue:
|
|
74
|
+
"""
|
|
75
|
+
Handles options that take key/value pairs.
|
|
76
|
+
|
|
77
|
+
The preferred syntax is "--option key=value" and that's what we should be advertising in docs and help,
|
|
78
|
+
but for compatibility (?) we accept "--option key:value"
|
|
79
|
+
|
|
80
|
+
Typically, this is used with multiple=True to produce `Sequence[Tuple[str, str]]`.
|
|
81
|
+
"""
|
|
82
|
+
error_message = "Expected a key-value pair formatted as --option key=value, but got '{}'"
|
|
83
|
+
|
|
84
|
+
for delimiter in ['=', ':']:
|
|
85
|
+
if delimiter in value:
|
|
86
|
+
kv = value.split(delimiter, 1)
|
|
87
|
+
if len(kv) != 2:
|
|
88
|
+
raise BadCmdLineException(error_message.format(value))
|
|
89
|
+
return KeyValue(kv[0].strip(), kv[1].strip())
|
|
90
|
+
|
|
91
|
+
raise BadCmdLineException(error_message.format(value))
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class Fraction:
|
|
95
|
+
def __init__(self, numerator: int, denominator: int):
|
|
96
|
+
self.numerator = numerator
|
|
97
|
+
self.denominator = denominator
|
|
98
|
+
|
|
99
|
+
def __str__(self):
|
|
100
|
+
return f"{self.numerator}/{self.denominator}"
|
|
101
|
+
|
|
102
|
+
def __iter__(self):
|
|
103
|
+
return iter((self.numerator, self.denominator))
|
|
104
|
+
|
|
105
|
+
def __getitem__(self, index):
|
|
106
|
+
return (self.numerator, self.denominator)[index]
|
|
107
|
+
|
|
108
|
+
def __float__(self):
|
|
109
|
+
return self.numerator / self.denominator
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_fraction(value: str) -> Fraction:
|
|
113
|
+
try:
|
|
114
|
+
v = value.strip().split('/')
|
|
115
|
+
if len(v) == 2:
|
|
116
|
+
n = int(v[0])
|
|
117
|
+
d = int(v[1])
|
|
118
|
+
return Fraction(n, d)
|
|
119
|
+
except ValueError:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
raise BadCmdLineException("Expected fraction like 1/2 but got '{}'".format(value))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class DateTimeWithTimezone:
|
|
126
|
+
def __init__(self, dt: datetime.datetime):
|
|
127
|
+
self.dt = dt
|
|
128
|
+
|
|
129
|
+
def __str__(self):
|
|
130
|
+
return self.dt.isoformat()
|
|
131
|
+
|
|
132
|
+
def datetime(self):
|
|
133
|
+
return self.dt
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def parse_datetime_with_timezone(value: str) -> DateTimeWithTimezone:
|
|
137
|
+
try:
|
|
138
|
+
dt = dateutil.parser.parse(value)
|
|
139
|
+
if dt.tzinfo is None:
|
|
140
|
+
dt = dt.replace(tzinfo=tzlocal())
|
|
141
|
+
return DateTimeWithTimezone(dt)
|
|
142
|
+
except ValueError:
|
|
143
|
+
raise BadCmdLineException("Expected datetime like 2023-10-01T12:00:00 but got '{}'".format(value))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def convert_to_seconds(s: str) -> float:
|
|
147
|
+
"""Convert duration string to seconds"""
|
|
148
|
+
units = {'s': 1, 'm': 60, 'h': 60 * 60, 'd': 60 * 60 * 24, 'w': 60 * 60 * 24 * 7}
|
|
149
|
+
|
|
150
|
+
if s.isdigit():
|
|
151
|
+
return float(s)
|
|
152
|
+
|
|
153
|
+
duration = 0
|
|
154
|
+
for m in re.finditer(r'(?P<val>\d+)(?P<unit>[smhdw]?)', s, flags=re.I):
|
|
155
|
+
val = m.group('val')
|
|
156
|
+
unit = m.group('unit')
|
|
157
|
+
|
|
158
|
+
if val is None or unit is None:
|
|
159
|
+
raise ValueError(f"unable to parse: {s}")
|
|
160
|
+
|
|
161
|
+
u = units.get(unit)
|
|
162
|
+
if u is None:
|
|
163
|
+
raise ValueError(f"unable to parse: {s}")
|
|
164
|
+
|
|
165
|
+
duration += int(val) * u
|
|
166
|
+
|
|
167
|
+
return float(duration)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
# Can the output deal with Unicode emojis?
|
|
171
|
+
try:
|
|
172
|
+
'\U0001f389'.encode(sys.stdout.encoding or "ascii")
|
|
173
|
+
# If stdout encoding is unavailable, such as in case of pipe, err on the safe side (EMOJI=False)
|
|
174
|
+
# This is a judgement call, but given that emojis do not serve functional purposes and purely decorative
|
|
175
|
+
# erring on the safe side seems like a reasonable call.
|
|
176
|
+
EMOJI = True
|
|
177
|
+
except UnicodeEncodeError:
|
|
178
|
+
EMOJI = False
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def emoji(s: str, fallback: str = '') -> str:
|
|
182
|
+
"""
|
|
183
|
+
Used to safely use Emoji where we can.
|
|
184
|
+
|
|
185
|
+
Returns 's' in an environment where stdout can deal with emojis, but 'fallback' otherwise.
|
|
186
|
+
"""
|
|
187
|
+
return s if EMOJI else fallback
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def ignorable_error(e: Exception) -> str:
|
|
191
|
+
return "An error occurred on Smart Tests CLI. You can ignore this message since the process will continue. " \
|
|
192
|
+
f"Error: {e}"
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def parse_key_value_list(values: list) -> list:
|
|
196
|
+
"""Parse a list of key-value strings into KeyValue objects"""
|
|
197
|
+
return [parse_key_value(v) for v in values]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
# Backward compatibility functions for existing usage
|
|
201
|
+
def validate_key_value(value: str):
|
|
202
|
+
"""Validate and parse a key-value string, returning a tuple for backward compatibility"""
|
|
203
|
+
kv = parse_key_value(value)
|
|
204
|
+
return (kv.key, kv.value)
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def validate_datetime_with_tz(value: str):
|
|
208
|
+
"""Validate and parse a datetime string, returning a datetime object for backward compatibility"""
|
|
209
|
+
dt_obj = parse_datetime_with_timezone(value)
|
|
210
|
+
return dt_obj.dt
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def validate_past_datetime(dt_value: datetime.datetime):
|
|
214
|
+
"""Validate that the provided datetime is in the past"""
|
|
215
|
+
if dt_value is None:
|
|
216
|
+
return dt_value
|
|
217
|
+
|
|
218
|
+
if not isinstance(dt_value, datetime.datetime):
|
|
219
|
+
raise BadCmdLineException("Expected a datetime object.")
|
|
220
|
+
|
|
221
|
+
now = datetime.datetime.now(tz=tzlocal())
|
|
222
|
+
if dt_value > now:
|
|
223
|
+
raise BadCmdLineException("The provided timestamp must be in the past.")
|
|
224
|
+
|
|
225
|
+
return dt_value
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _key_value_compat(value: str):
|
|
229
|
+
"""Compatibility wrapper that returns tuple instead of KeyValue object"""
|
|
230
|
+
kv = parse_key_value(value)
|
|
231
|
+
return (kv.key, kv.value)
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def _datetime_with_tz_compat(value: str):
|
|
235
|
+
"""Compatibility wrapper that returns datetime instead of DateTimeWithTimezone object"""
|
|
236
|
+
dt_obj = parse_datetime_with_timezone(value)
|
|
237
|
+
return dt_obj.dt
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
KEY_VALUE = _key_value_compat
|
|
241
|
+
DATETIME_WITH_TZ = _datetime_with_tz_compat
|
smart_tests/version.py
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: smart-tests-cli
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Smart Tests CLI
|
|
5
|
+
Author-email: CloudBees <support@cloudbees.com>
|
|
6
|
+
License: Apache Software License v2
|
|
7
|
+
Project-URL: Homepage, https://www.cloudbees.com/capabilities/cloudbees-smart-tests
|
|
8
|
+
Project-URL: Repository, https://github.com/launchableinc/cli
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Requires-Python: >=3.13
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE.txt
|
|
15
|
+
Requires-Dist: requests>=2.32.5
|
|
16
|
+
Requires-Dist: urllib3>=2.5
|
|
17
|
+
Requires-Dist: junitparser>=4.0.0
|
|
18
|
+
Requires-Dist: more-itertools>=7.1.0
|
|
19
|
+
Requires-Dist: python-dateutil>=2.9
|
|
20
|
+
Requires-Dist: tabulate>=0.9
|
|
21
|
+
Requires-Dist: click<8.2,>=8.1
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Usage
|
|
25
|
+
|
|
26
|
+
See https://help.launchableinc.com/resources/cli-reference/ and
|
|
27
|
+
https://help.launchableinc.com/sending-data-to-launchable/using-the-launchable-cli/getting-started/.
|
|
28
|
+
|
|
29
|
+
# Development
|
|
30
|
+
|
|
31
|
+
## Preparation
|
|
32
|
+
|
|
33
|
+
We recommend uv for dependency management:
|
|
34
|
+
|
|
35
|
+
```shell
|
|
36
|
+
# Install uv
|
|
37
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
38
|
+
|
|
39
|
+
# Install dependencies
|
|
40
|
+
uv sync --dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
In order to automatically format files with autopep8, this repository contains a
|
|
44
|
+
configuration for [pre-commit](https://pre-commit.com). Install the hook with
|
|
45
|
+
`uv run pre-commit install`.
|
|
46
|
+
|
|
47
|
+
## Load development environment
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
# Activate virtual environment
|
|
51
|
+
source .venv/bin/activate
|
|
52
|
+
# or use uv run for individual commands
|
|
53
|
+
uv run <command>
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Run tests cli
|
|
57
|
+
|
|
58
|
+
```shell
|
|
59
|
+
# Using poethepoet (recommended)
|
|
60
|
+
uv run poe test
|
|
61
|
+
|
|
62
|
+
# Direct command
|
|
63
|
+
uv run python -m unittest
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Run tests exe_deploy.jar
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
bazel test ...
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Available Development Tasks
|
|
73
|
+
|
|
74
|
+
This project uses [poethepoet](https://poethepoet.natn.io/) for task management. Available tasks:
|
|
75
|
+
|
|
76
|
+
```shell
|
|
77
|
+
# Show all available tasks
|
|
78
|
+
uv run poe --help
|
|
79
|
+
|
|
80
|
+
# Run tests
|
|
81
|
+
uv run poe test
|
|
82
|
+
|
|
83
|
+
# Run tests with XML output
|
|
84
|
+
uv run poe test-xml
|
|
85
|
+
|
|
86
|
+
# Run linting
|
|
87
|
+
uv run poe lint
|
|
88
|
+
|
|
89
|
+
# Run type checking
|
|
90
|
+
uv run poe type
|
|
91
|
+
|
|
92
|
+
# Format code
|
|
93
|
+
uv run poe format
|
|
94
|
+
|
|
95
|
+
# Build package
|
|
96
|
+
uv run poe build
|
|
97
|
+
|
|
98
|
+
# Install package locally
|
|
99
|
+
uv run poe install
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Add dependency
|
|
103
|
+
|
|
104
|
+
```shell
|
|
105
|
+
# Add runtime dependency
|
|
106
|
+
uv add some-package
|
|
107
|
+
|
|
108
|
+
# Add development dependency
|
|
109
|
+
uv add --dev some-dev-package
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Updating Python Version
|
|
113
|
+
|
|
114
|
+
When updating the Python version requirement, update the following files:
|
|
115
|
+
|
|
116
|
+
1. **`.python-version`** - Used by pyenv, uv, and local development
|
|
117
|
+
2. **`pyproject.toml`** - Update `requires-python = ">=X.Y"`
|
|
118
|
+
3. **`setup.cfg`** - Update `python_requires = >=X.Y`
|
|
119
|
+
4. **`.github/workflows/python-package.yml`** - Update `python-version: ["X.Y"]`
|
|
120
|
+
5. **`.github/workflows/python-publish.yml`** - Update `uv python install X.Y`
|
|
121
|
+
6. **`README.md`** - Update prerequisite section
|
|
122
|
+
7. **`CLAUDE.md`** - Update development notes
|
|
123
|
+
|
|
124
|
+
# How to release
|
|
125
|
+
|
|
126
|
+
Create new release on Github, then Github Actions automatically uploads the
|
|
127
|
+
module to PyPI.
|
|
128
|
+
|
|
129
|
+
## How to update smart_tests/jar/exe_deploy.jar
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
./build-java.sh
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
# Installing CLI
|
|
136
|
+
|
|
137
|
+
You can install the `smart-tests` command from either source or [pypi](https://pypi.org/project/smart-tests/).
|
|
138
|
+
|
|
139
|
+
## Prerequisite
|
|
140
|
+
|
|
141
|
+
- \>= Python 3.13
|
|
142
|
+
- \>= Java 8
|
|
143
|
+
|
|
144
|
+
## Install from source
|
|
145
|
+
|
|
146
|
+
```sh
|
|
147
|
+
$ pwd
|
|
148
|
+
~/cli
|
|
149
|
+
|
|
150
|
+
$ python setup.py install
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Install from pypi
|
|
154
|
+
|
|
155
|
+
```sh
|
|
156
|
+
$ pip3 install --user --upgrade smart-tests~=1.0
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Versioning
|
|
160
|
+
|
|
161
|
+
This module follows [Semantic versioning](https://semver.org/) such as X.Y.Z.
|
|
162
|
+
|
|
163
|
+
* Major (X)
|
|
164
|
+
* Drastic update breaking backward compatibility
|
|
165
|
+
* Minor (Y)
|
|
166
|
+
* Add new plugins, options with backward compatibility
|
|
167
|
+
* Patch (Z)-
|
|
168
|
+
* Fix bugs or minor behaviors
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
smart_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
smart_tests/__main__.py,sha256=k4FIJjpCPmaJyfEqH7Q0YRXERFAPM-HpSVBaa9sbRAA,1820
|
|
3
|
+
smart_tests/app.py,sha256=8ydkzRvYQ4hpTMeKbaozW5m5SpLL4LhxRO1knPfkXcU,2999
|
|
4
|
+
smart_tests/testpath.py,sha256=aVdNC3q97m6p6p3KhmzHJmyRa_bTS1AsQKBPkfeYwbU,5289
|
|
5
|
+
smart_tests/version.py,sha256=5C6VKSV_QmMEFor7D6WP0Rl6joDXnrBlPPU3EoMhrW8,200
|
|
6
|
+
smart_tests/args4p/README.md,sha256=Q66SR7eLzWxx3Ow1JscrCipqXubQEd3aA74YqdMN5Dg,3775
|
|
7
|
+
smart_tests/args4p/__init__.py,sha256=RmEeQKwg35AdsXoKQy4Od-dPKRerMkwLO-XOtESdbgI,301
|
|
8
|
+
smart_tests/args4p/argument.py,sha256=AbvQk0tNbTmzjcGV_O2kYa6FwYqCI6Ca1e8Hg-49nwI,1662
|
|
9
|
+
smart_tests/args4p/command.py,sha256=RTgqKPgl0CC1xJqVfquyJWgS_hTWR6ZLajv4U7776vw,22878
|
|
10
|
+
smart_tests/args4p/decorators.py,sha256=tyumCTk6zacWRWaUJ4zxTp-twkYL1u09qYEkJG42pfE,2751
|
|
11
|
+
smart_tests/args4p/exceptions.py,sha256=NmQDYNTP4I-lABSuOU-gxQpNs_ta9ISV8mqD88LOxAA,306
|
|
12
|
+
smart_tests/args4p/option.py,sha256=ccS8q08-shlk5rFyMn8cS7Ipn2rJwzAVlqDvMzbzfIA,3083
|
|
13
|
+
smart_tests/args4p/parameter.py,sha256=N7n3hrdhQrFd-G68CkYpsMaIFNruzcNdo5nvCiu0v3k,3261
|
|
14
|
+
smart_tests/args4p/converters/__init__.py,sha256=vXnB161nOXNUd-_Rq3cleJaviQhZvR7lb7DGUfM_9bI,2495
|
|
15
|
+
smart_tests/args4p/typer/__init__.py,sha256=6AgBbi73qDXtyWZOLJ8gmgEEebw5wdsGNAC5V0cbVCE,1297
|
|
16
|
+
smart_tests/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
smart_tests/commands/detect_flakes.py,sha256=Qnbq0Gd6Cnzj4ZlU8hwjeA3rJIQICQtV-nNxLrfjcNE,3986
|
|
18
|
+
smart_tests/commands/subset.py,sha256=jPADKoxLxeM89ozdTXeiubchYRIHV84nqPgLJpPp4u8,24617
|
|
19
|
+
smart_tests/commands/test_path_writer.py,sha256=oaTN0ft0nyRxtw8QFHGrfRUMZtj6zntSBcBu9IDEuGM,1879
|
|
20
|
+
smart_tests/commands/verify.py,sha256=in3dAnCR4Sd4OyOnM1jMYWl06fSQmTpUvrEUlUwfHbo,5849
|
|
21
|
+
smart_tests/commands/compare/__init__.py,sha256=y1XfRibrCuktoTykHgdi1ZGIlq5YuOtswMPcDpyEyaY,178
|
|
22
|
+
smart_tests/commands/compare/subsets.py,sha256=0Z_4WvH7cGxwod6mDAq9D2tN5vUZJG1ETSOA6Zv6Rmk,2222
|
|
23
|
+
smart_tests/commands/inspect/__init__.py,sha256=dwf1yRVpH74sjSUAIH9gkiKJuUdd1k04jaGZ4Z7fV4k,262
|
|
24
|
+
smart_tests/commands/inspect/model.py,sha256=iqlYcuB-f44DBqVvzyrdHHzwaXoWopa63_4hH-oSc_0,1461
|
|
25
|
+
smart_tests/commands/inspect/subset.py,sha256=fo609fCXbZ-TZTOz-FXj93QWlLPW6ONLIQFY_EmcWaU,4325
|
|
26
|
+
smart_tests/commands/record/__init__.py,sha256=d3I_SpA4OoqyeS5ORTe8AdSGEfJYg4mP-6ONaEvp-PQ,455
|
|
27
|
+
smart_tests/commands/record/attachment.py,sha256=nA9TUOK6-VD7CMmEjR-3tjGjPTEv7dzVXnBZ112zJEk,1198
|
|
28
|
+
smart_tests/commands/record/build.py,sha256=yhk68195kLkvgY0SqONHfuV-ALi7QgW72Za-BIq4lMs,15625
|
|
29
|
+
smart_tests/commands/record/case_event.py,sha256=cl2Pu44_QD7_cRFjtJoA-dsdnGfCgiHU5Go5mcdfNQk,6403
|
|
30
|
+
smart_tests/commands/record/commit.py,sha256=Z33nSV3Iz5rSMEl_jKgE0V7A_Q-mdw1kmUcqChTCf6w,5329
|
|
31
|
+
smart_tests/commands/record/session.py,sha256=_CE79yOk9YFI2_rosP92VuxeSLFHCJ1Cb5ycmA-taFY,4391
|
|
32
|
+
smart_tests/commands/record/tests.py,sha256=LgOx_8VUabPB-0x1eJoe-kbSO-jcr72jGZWbY4syfEw,19903
|
|
33
|
+
smart_tests/commands/stats/__init__.py,sha256=dUljXynXv-43lJq93bMoJO3oVCsiozv0mZv3rHebF38,192
|
|
34
|
+
smart_tests/commands/stats/test_sessions.py,sha256=QbZFBNUIGsFhbsA0pWr4U1wSBpZwKUblq3MWTOEfCwE,1252
|
|
35
|
+
smart_tests/jar/exe_deploy.jar,sha256=1PDhaNtkHgWkEYCOtznSYKZ8W-zDsQspr2nVuLkQQrY,12594800
|
|
36
|
+
smart_tests/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
smart_tests/test_runners/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
smart_tests/test_runners/adb.py,sha256=YYcMOlFWBZH89TcMaFfGa6XD70tZHQ73OSxl7efrsuE,616
|
|
39
|
+
smart_tests/test_runners/ant.py,sha256=3IfnJYyR8pV6vHfs2kzsJbXeytSGFUl6B9JHYhwXkSE,942
|
|
40
|
+
smart_tests/test_runners/bazel.py,sha256=IoAvFScmPk4l6AcigqBgHb8ZG3w4MeH-HYvXe0MRBvc,3536
|
|
41
|
+
smart_tests/test_runners/behave.py,sha256=6NPniB7y9VA1TOUBxyXeJnDV1YiHag1LoXGO8n9A-PE,1538
|
|
42
|
+
smart_tests/test_runners/codeceptjs.py,sha256=2d_9gbi0KXawU1VGDQ4X8KW_q5ezj1ijNQ4FMcwYHko,1016
|
|
43
|
+
smart_tests/test_runners/ctest.py,sha256=lL3UkJwRts-hsijcDmUO3FrkrNXTfpwEB3mQ8TaLATw,5468
|
|
44
|
+
smart_tests/test_runners/cts.py,sha256=W6lJQDooWBaSg1ueEiHNCQzx3ue3irdcl6cxoEIFvuM,7076
|
|
45
|
+
smart_tests/test_runners/cucumber.py,sha256=Ci-5odG8N18_As6M7pEQIle7sOf8UsTJcBDJG5VeCf8,16074
|
|
46
|
+
smart_tests/test_runners/cypress.py,sha256=HUozYxyns5_HCtfZDQX6flxB2Ail9bjHPYgWOsmjP1Y,1276
|
|
47
|
+
smart_tests/test_runners/dotnet.py,sha256=d052z6NXOTpnkxX9xJCdG87nj3YOayEQyiH-9duyilA,3281
|
|
48
|
+
smart_tests/test_runners/file.py,sha256=pWTaEtg9PowRstp4Drf38vWb655Ce2oUYndtf6PNrVc,526
|
|
49
|
+
smart_tests/test_runners/flutter.py,sha256=RiG4cimK-uU3NkFRCSWoceQgTp1S0ZfqwtGA_dEZfkM,8325
|
|
50
|
+
smart_tests/test_runners/go_test.py,sha256=f0foXNEUPRTguKloBFgtCqc0rNqRnNqeE4fMp4ESDic,3620
|
|
51
|
+
smart_tests/test_runners/googletest.py,sha256=SBo-9OZAM4shL5ok5xKd21pO_jhv2K6eJCEphvZz58Q,988
|
|
52
|
+
smart_tests/test_runners/gradle.py,sha256=eOg9-X1u7lZGpj016bGjzi4iq-PqZ0Y2NvQmmtNqE4c,3172
|
|
53
|
+
smart_tests/test_runners/jest.py,sha256=4lQYBxJ24z-G2YyAW13pWld5mH6-SBTjsbumqPfe010,1334
|
|
54
|
+
smart_tests/test_runners/maven.py,sha256=IH_VHxMEq4EMvTrGDHqWdTMGB9y9ABF2_AmJYUVib4Y,5146
|
|
55
|
+
smart_tests/test_runners/minitest.py,sha256=hB8UkdgtPkJy71Wx5kflkQvgbyVp5T4ZMtlNIMagAIU,1538
|
|
56
|
+
smart_tests/test_runners/nunit.py,sha256=K_-uxY2QEqgvWLu5FOoZfR64AHJZsou006NlupdjxBw,7493
|
|
57
|
+
smart_tests/test_runners/playwright.py,sha256=rxvdUJrKoh7Hjec_RRoGrKUkWsuPBtSM8obzE354rR0,10572
|
|
58
|
+
smart_tests/test_runners/prove.py,sha256=OfvGIbXK3K9DkdJLlSAcLyQn2Ec12CiPRNiToktECD0,2436
|
|
59
|
+
smart_tests/test_runners/pytest.py,sha256=RFd3RiAp1_EwzR0XdOVpRFAu8yoDhhQPq_JN8vDSSAY,13619
|
|
60
|
+
smart_tests/test_runners/raw.py,sha256=nf2PnRkVCp4Qmsl0Aw3OfqreXl8GqED4SyPgo02N6vQ,7974
|
|
61
|
+
smart_tests/test_runners/robot.py,sha256=AHk8d--Szgbj5S4VcDlxjDEW01ifFazaUAhli5Xq7XM,3907
|
|
62
|
+
smart_tests/test_runners/rspec.py,sha256=t3ucgPvgepib17iNu1eu3WqMhPHgsFCCqARQthEyT7I,252
|
|
63
|
+
smart_tests/test_runners/smart_tests.py,sha256=yOmXsj6uNR6ZiVSrK9iOvWimab1NhiezJuDbqibHTQQ,7789
|
|
64
|
+
smart_tests/test_runners/vitest.py,sha256=NasFpi7je01PXXuoT_Z6WVb52cUxc3vjqALUkKW8rAk,1565
|
|
65
|
+
smart_tests/test_runners/xctest.py,sha256=VZ6SDYPHX3HGLIpDrwC_DRgSSTiO8bTwvfzr9-hdgnA,2578
|
|
66
|
+
smart_tests/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
67
|
+
smart_tests/utils/authentication.py,sha256=8PiY4zQqD0m3_8i18-c_QT0HNy_8N3er5GJ3Ak_yZv0,2895
|
|
68
|
+
smart_tests/utils/ci_provider.py,sha256=YiXWXFqPH7uoOORgTui4M9lFxugctUYIB40gzQwNpL8,136
|
|
69
|
+
smart_tests/utils/commands.py,sha256=4z7SGGutNZFc1TME5o1YgJdJB0Og7BGB8Ib__dkmMwM,332
|
|
70
|
+
smart_tests/utils/commit_ingester.py,sha256=Qj-n5cID1ZpHB9IsGzqIpj2hXp9rqZpwPpmOU9iA3ss,1943
|
|
71
|
+
smart_tests/utils/common_tz.py,sha256=cwPKKgH7XxbnQFjxNqNub7VL7_tiy2hTx4GC1bzlb4Y,472
|
|
72
|
+
smart_tests/utils/edit_distance.py,sha256=NxzXfWxJtnzGbEFtkPOV-X1MYJqKsrCSKEj6lU-hlIY,405
|
|
73
|
+
smart_tests/utils/env_keys.py,sha256=JaY_C9xN5fiAcj8B-cQ2kjCzBbtnZJEVURpLHUVwjQw,656
|
|
74
|
+
smart_tests/utils/exceptions.py,sha256=nAUJiFrsvgWalYpj9XmFQMMTeYo7YBzquUhJNApUJEQ,994
|
|
75
|
+
smart_tests/utils/fail_fast_mode.py,sha256=BtkXAj49sMNFpnoluN2kvgv3NNNhbP1u7THiQpw7mfE,3334
|
|
76
|
+
smart_tests/utils/file_name_pattern.py,sha256=8f0jD9P0TQ7ivuJG_vJQOymVyOfdsks-NqcBnbm2UIM,169
|
|
77
|
+
smart_tests/utils/git_log_parser.py,sha256=4gIyBgyoTT3gPR_nT3rsi-AI-Da6YufjQpvyhXWZxzk,1985
|
|
78
|
+
smart_tests/utils/glob.py,sha256=smQ6_hpAop011YM2n-1Sr517xObElYnR1-9F3SZLOJ8,1156
|
|
79
|
+
smart_tests/utils/gzipgen.py,sha256=hjRwb26leFgEijnOK2JySMljZTuJCN8qxFKqWGzCNaE,1339
|
|
80
|
+
smart_tests/utils/http_client.py,sha256=g6zWp6FYPxazrBJgc_SqUNuu3cw_AyWEeJOrE6X1T3c,5806
|
|
81
|
+
smart_tests/utils/java.py,sha256=E1X7aqSwp2kb_cnS67kmKlm3XRyQOkk48qfTNogajC8,2368
|
|
82
|
+
smart_tests/utils/link.py,sha256=lTpPBIiccyig57MnYY1tGb8fiPmEnxc9UDkDPSXJTXs,5539
|
|
83
|
+
smart_tests/utils/logger.py,sha256=XRcwb26_drwuFrn8IvP-m1G383gbZk7SjB_U1ONLX84,1444
|
|
84
|
+
smart_tests/utils/no_build.py,sha256=g54DOtRsu5vj8X43vgI8-dLJ3oMnMH_HXlirVX45yCc,62
|
|
85
|
+
smart_tests/utils/sax.py,sha256=9OJp1EPyiZjE_0L3uYMlPB5SoXnOjg2h_mgrghTU6Ow,3364
|
|
86
|
+
smart_tests/utils/session.py,sha256=DDHSAXK9cFpIqi5SA2nKg1XTIGGwdrwM95-I-TngpXg,2313
|
|
87
|
+
smart_tests/utils/smart_tests_client.py,sha256=qfsx8VP059YKQRCM4ehJjTp5Xzr8-O4S_dWYrXelFIM,4854
|
|
88
|
+
smart_tests/utils/subprocess.py,sha256=McD1_IT5uGhRyF61zfmMHwYrPCGPaMum9jMrYF2TExs,428
|
|
89
|
+
smart_tests/utils/tracking.py,sha256=h-EcD4fytkzpX9oGu1zago2Wg7hRxqsxU3NwsMeqQwM,2818
|
|
90
|
+
smart_tests/utils/typer_types.py,sha256=uNI92j8XyhDem7bxxZ1LBZ5TDwbEv_3-zEgrWUoEV08,6906
|
|
91
|
+
smart_tests_cli-2.0.0.dist-info/licenses/LICENSE.txt,sha256=o7VmUVwJZRYoM7X4LVmpNdnSLzUVJ58jSo6esi7DptM,11365
|
|
92
|
+
smart_tests_cli-2.0.0.dist-info/METADATA,sha256=C2kWjWV6EZlvoLjqezj9Ytz_HhgacqeApKUjNL3rjFc,3623
|
|
93
|
+
smart_tests_cli-2.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
94
|
+
smart_tests_cli-2.0.0.dist-info/entry_points.txt,sha256=fkU5blN61P41jH8W8mhjxvQBFq-rl7bLxIMsFrvbS8c,58
|
|
95
|
+
smart_tests_cli-2.0.0.dist-info/top_level.txt,sha256=NQto8RXlGDM3OzqENbAqirHoSi5O0k7dpDPV34s1Bbg,12
|
|
96
|
+
smart_tests_cli-2.0.0.dist-info/RECORD,,
|