-
Notifications
You must be signed in to change notification settings - Fork 53
/
run.py
285 lines (260 loc) · 7.95 KB
/
run.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
import pdb
import sys
from pathlib import Path
from timeit import default_timer
from typing import Optional, Tuple, List
import click
import click_completion
import colorama
from click_default_group import DefaultGroup
from cucumber_tag_expressions import parse as parse_tags
from cucumber_tag_expressions.model import Expression
from ward._ward_version import __version__
from ward.collect import (
get_info_for_modules,
get_tests_in_modules,
load_modules,
filter_tests,
filter_fixtures,
)
from ward.config import set_defaults_from_config
from ward.debug import init_breakpointhooks
from ward.rewrite import rewrite_assertions_in_tests
from ward.suite import Suite
from ward.fixtures import _DEFINED_FIXTURES
from ward.terminal import (
SimpleTestResultWrite,
output_fixtures,
get_exit_code,
TestProgressStyle,
TestOutputStyle,
)
colorama.init()
click_completion.init()
sys.path.append(".")
# TODO: simplify to use invoke_without_command and ctx.forward once https://github.com/pallets/click/issues/430 is resolved
@click.group(
context_settings={"max_content_width": 100},
cls=DefaultGroup,
default="test",
default_if_no_args=True,
)
@click.pass_context
def run(ctx: click.Context):
pass
config = click.option(
"--config",
type=click.Path(
exists=False, file_okay=True, dir_okay=False, readable=True, allow_dash=False
),
callback=set_defaults_from_config,
help="Read configuration from PATH.",
is_eager=True,
)
path = click.option(
"-p",
"--path",
type=click.Path(exists=True),
multiple=True,
is_eager=True,
help="Look for tests in PATH.",
)
exclude = click.option(
"--exclude",
type=click.STRING,
multiple=True,
help="Paths to ignore while searching for tests. Accepts glob patterns.",
)
@run.command()
@config
@path
@exclude
@click.option(
"--search",
help="Search test names, bodies, descriptions and module names for the search query and only keep matching tests.",
)
@click.option(
"--tags",
help="Find tests matching a tag expression (e.g. 'unit and not slow').",
metavar="EXPR",
type=parse_tags,
)
@click.option(
"--fail-limit",
type=int,
help="The maximum number of failures that are allowed to occur in a run before it is automatically cancelled.",
)
@click.option(
"--test-output-style",
type=click.Choice(list(TestOutputStyle), case_sensitive=False),
default="test-per-line",
help="The style of output for displaying individual test results during the run.",
)
@click.option(
"--progress-style",
type=click.Choice(list(TestProgressStyle), case_sensitive=False),
multiple=True,
default=["inline"],
help=f"""\
The style of progress indicator to use during the run.
Pass multiple times to enable multiple styles.
The '{TestProgressStyle.BAR}' style is not compatible with the '{TestOutputStyle.DOTS_GLOBAL}' and '{TestOutputStyle.DOTS_MODULE}' test output styles.
""",
)
@click.option(
"--order",
type=click.Choice(["standard", "random"], case_sensitive=False),
default="standard",
help="Specify the order in which tests should run.",
)
@click.option(
"--show-diff-symbols/--hide-diff-symbols",
default=False,
help="If enabled, diffs will use symbols such as '?', '-', '+' and '^' instead of colours to highlight differences.",
)
@click.option(
"--capture-output/--no-capture-output",
default=True,
help="Enable or disable output capturing.",
)
@click.option(
"--show-slowest",
type=int,
help="Record and display duration of n longest running tests",
default=0,
)
@click.option(
"--dry-run/--no-dry-run",
help="Print all tests without executing them",
default=False,
)
@click.version_option(version=__version__)
@click.pass_context
def test(
ctx: click.Context,
config: str,
config_path: Optional[Path],
path: Tuple[str],
exclude: Tuple[str],
search: Optional[str],
tags: Optional[Expression],
fail_limit: Optional[int],
test_output_style: str,
progress_style: List[str],
order: str,
capture_output: bool,
show_slowest: int,
show_diff_symbols: bool,
dry_run: bool,
):
"""Run tests."""
progress_styles = [TestProgressStyle(ps) for ps in progress_style]
if TestProgressStyle.BAR in progress_styles and test_output_style in {
"dots-global",
"dots-module",
}:
raise click.BadOptionUsage(
"progress_style",
f"The '{TestProgressStyle.BAR}' progress style cannot be used with dots-based test output styles (you asked for '{test_output_style}').",
)
init_breakpointhooks(pdb, sys)
start_run = default_timer()
paths = [Path(p) for p in path]
mod_infos = get_info_for_modules(paths, exclude)
modules = list(load_modules(mod_infos))
unfiltered_tests = get_tests_in_modules(modules, capture_output)
filtered_tests = list(filter_tests(unfiltered_tests, query=search, tag_expr=tags,))
tests = rewrite_assertions_in_tests(filtered_tests)
time_to_collect = default_timer() - start_run
suite = Suite(tests=tests)
test_results = suite.generate_test_runs(order=order, dry_run=dry_run)
writer = SimpleTestResultWrite(
suite=suite,
test_output_style=test_output_style,
progress_styles=progress_styles,
config_path=config_path,
show_diff_symbols=show_diff_symbols,
)
writer.output_header(time_to_collect=time_to_collect)
results = writer.output_all_test_results(test_results, fail_limit=fail_limit)
time_taken = default_timer() - start_run
writer.output_test_result_summary(results, time_taken, show_slowest)
exit_code = get_exit_code(results)
sys.exit(exit_code.value)
@run.command()
@config
@path
@exclude
@click.option(
"-f",
"--fixture-path",
help="Only display fixtures defined in or below the given paths.",
multiple=True,
type=Path,
)
@click.option(
"--search",
help="Search fixtures names, bodies, and module names for the search query and only keep matching fixtures.",
)
@click.option(
"--show-scopes/--no-show-scopes",
help="Display each fixture's scope.",
default=True,
)
@click.option(
"--show-docstrings/--no-show-docstrings",
help="Display each fixture's docstring.",
default=False,
)
@click.option(
"--show-dependencies/--no-show-dependencies",
help="Display the fixtures and tests that each fixture depends on and is used by. Only displays direct dependencies; use --show-dependency-trees to show all dependency information.",
default=False,
)
@click.option(
"--show-dependency-trees/--no-show-dependency-trees",
help="Display the entire dependency tree for each fixture.",
default=False,
)
@click.option(
"--full/--no-full",
help="Display all available information on each fixture.",
default=False,
)
@click.pass_context
def fixtures(
ctx: click.Context,
config: str,
config_path: Optional[Path],
path: Tuple[str],
exclude: Tuple[str],
fixture_path: Tuple[Path],
search: Optional[str],
show_scopes: bool,
show_docstrings: bool,
show_dependencies: bool,
show_dependency_trees: bool,
full: bool,
):
"""Show information on fixtures."""
paths = [Path(p) for p in path]
mod_infos = get_info_for_modules(paths, exclude)
modules = list(load_modules(mod_infos))
tests = list(get_tests_in_modules(modules, capture_output=True))
filtered_fixtures = list(
filter_fixtures(_DEFINED_FIXTURES, query=search, paths=fixture_path)
)
output_fixtures(
fixtures=filtered_fixtures,
tests=tests,
show_scopes=show_scopes or full,
show_docstrings=show_docstrings or full,
show_dependencies=show_dependencies or full,
show_dependency_trees=show_dependency_trees or full,
)
@run.command()
@click.pass_context
def completions(ctx: click.Context):
shell, path = click_completion.core.install()
click.echo(f"{shell} completion installed in {path}")
ctx.exit(0)