schemathesis 4.0.0a10__py3-none-any.whl → 4.0.0a12__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.
- schemathesis/__init__.py +29 -30
- schemathesis/auths.py +65 -24
- schemathesis/checks.py +73 -39
- schemathesis/cli/commands/__init__.py +51 -3
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +163 -274
- schemathesis/cli/commands/run/context.py +8 -4
- schemathesis/cli/commands/run/events.py +11 -1
- schemathesis/cli/commands/run/executor.py +70 -78
- schemathesis/cli/commands/run/filters.py +15 -165
- schemathesis/cli/commands/run/handlers/cassettes.py +105 -104
- schemathesis/cli/commands/run/handlers/junitxml.py +5 -4
- schemathesis/cli/commands/run/handlers/output.py +195 -121
- schemathesis/cli/commands/run/loaders.py +35 -50
- schemathesis/cli/commands/run/validation.py +52 -162
- schemathesis/cli/core.py +5 -3
- schemathesis/cli/ext/fs.py +7 -5
- schemathesis/cli/ext/options.py +0 -21
- schemathesis/config/__init__.py +189 -0
- schemathesis/config/_auth.py +51 -0
- schemathesis/config/_checks.py +268 -0
- schemathesis/config/_diff_base.py +99 -0
- schemathesis/config/_env.py +21 -0
- schemathesis/config/_error.py +156 -0
- schemathesis/config/_generation.py +149 -0
- schemathesis/config/_health_check.py +24 -0
- schemathesis/config/_operations.py +327 -0
- schemathesis/config/_output.py +171 -0
- schemathesis/config/_parameters.py +19 -0
- schemathesis/config/_phases.py +187 -0
- schemathesis/config/_projects.py +523 -0
- schemathesis/config/_rate_limit.py +17 -0
- schemathesis/config/_report.py +120 -0
- schemathesis/config/_validator.py +9 -0
- schemathesis/config/_warnings.py +25 -0
- schemathesis/config/schema.json +885 -0
- schemathesis/core/__init__.py +2 -0
- schemathesis/core/compat.py +16 -9
- schemathesis/core/errors.py +24 -4
- schemathesis/core/failures.py +6 -7
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/output/__init__.py +14 -37
- schemathesis/core/output/sanitization.py +3 -146
- schemathesis/core/transport.py +36 -1
- schemathesis/core/validation.py +16 -0
- schemathesis/engine/__init__.py +2 -4
- schemathesis/engine/context.py +42 -43
- schemathesis/engine/core.py +7 -5
- schemathesis/engine/errors.py +60 -1
- schemathesis/engine/events.py +10 -2
- schemathesis/engine/phases/__init__.py +10 -0
- schemathesis/engine/phases/probes.py +11 -8
- schemathesis/engine/phases/stateful/__init__.py +2 -1
- schemathesis/engine/phases/stateful/_executor.py +104 -46
- schemathesis/engine/phases/stateful/context.py +2 -2
- schemathesis/engine/phases/unit/__init__.py +23 -15
- schemathesis/engine/phases/unit/_executor.py +110 -21
- schemathesis/engine/phases/unit/_pool.py +1 -1
- schemathesis/errors.py +2 -0
- schemathesis/filters.py +2 -3
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +6 -3
- schemathesis/generation/coverage.py +154 -124
- schemathesis/generation/hypothesis/builder.py +70 -20
- schemathesis/generation/meta.py +3 -3
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +0 -8
- schemathesis/generation/overrides.py +37 -1
- schemathesis/generation/stateful/__init__.py +4 -0
- schemathesis/generation/stateful/state_machine.py +9 -1
- schemathesis/graphql/loaders.py +159 -16
- schemathesis/hooks.py +62 -35
- schemathesis/openapi/checks.py +12 -8
- schemathesis/openapi/generation/filters.py +10 -8
- schemathesis/openapi/loaders.py +142 -17
- schemathesis/pytest/lazy.py +2 -5
- schemathesis/pytest/loaders.py +24 -0
- schemathesis/pytest/plugin.py +33 -2
- schemathesis/schemas.py +21 -66
- schemathesis/specs/graphql/scalars.py +37 -3
- schemathesis/specs/graphql/schemas.py +23 -18
- schemathesis/specs/openapi/_hypothesis.py +26 -28
- schemathesis/specs/openapi/checks.py +37 -36
- schemathesis/specs/openapi/examples.py +4 -3
- schemathesis/specs/openapi/formats.py +32 -5
- schemathesis/specs/openapi/media_types.py +44 -1
- schemathesis/specs/openapi/negative/__init__.py +2 -2
- schemathesis/specs/openapi/patterns.py +46 -16
- schemathesis/specs/openapi/references.py +2 -3
- schemathesis/specs/openapi/schemas.py +19 -22
- schemathesis/specs/openapi/stateful/__init__.py +12 -6
- schemathesis/transport/__init__.py +54 -16
- schemathesis/transport/prepare.py +38 -13
- schemathesis/transport/requests.py +12 -9
- schemathesis/transport/wsgi.py +11 -12
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/METADATA +50 -97
- schemathesis-4.0.0a12.dist-info/RECORD +164 -0
- schemathesis/cli/commands/run/checks.py +0 -79
- schemathesis/cli/commands/run/hypothesis.py +0 -78
- schemathesis/cli/commands/run/reports.py +0 -72
- schemathesis/cli/hooks.py +0 -36
- schemathesis/contrib/__init__.py +0 -9
- schemathesis/contrib/openapi/__init__.py +0 -9
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -20
- schemathesis/engine/config.py +0 -59
- schemathesis/experimental/__init__.py +0 -72
- schemathesis/generation/targets.py +0 -69
- schemathesis-4.0.0a10.dist-info/RECORD +0 -153
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.0a10.dist-info → schemathesis-4.0.0a12.dist-info}/licenses/LICENSE +0 -0
@@ -1,52 +1,48 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from pathlib import Path
|
4
|
-
from
|
5
|
-
from typing import Any, Sequence
|
4
|
+
from typing import Any, Callable
|
6
5
|
|
7
6
|
import click
|
8
7
|
from click.utils import LazyFile
|
9
8
|
|
10
|
-
from schemathesis import contrib, experimental
|
11
9
|
from schemathesis.checks import CHECKS
|
12
10
|
from schemathesis.cli.commands.run import executor, validation
|
13
|
-
from schemathesis.cli.commands.run.
|
14
|
-
from schemathesis.cli.commands.run.filters import FilterArguments, with_filters
|
15
|
-
from schemathesis.cli.commands.run.hypothesis import (
|
16
|
-
HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER,
|
17
|
-
HealthCheck,
|
18
|
-
prepare_health_checks,
|
19
|
-
prepare_phases,
|
20
|
-
prepare_settings,
|
21
|
-
)
|
22
|
-
from schemathesis.cli.commands.run.reports import DEFAULT_REPORT_DIRECTORY, ReportConfig, ReportFormat
|
11
|
+
from schemathesis.cli.commands.run.filters import with_filters
|
23
12
|
from schemathesis.cli.constants import DEFAULT_WORKERS, MAX_WORKERS, MIN_WORKERS
|
24
13
|
from schemathesis.cli.core import ensure_color
|
25
14
|
from schemathesis.cli.ext.groups import group, grouped_option
|
26
15
|
from schemathesis.cli.ext.options import (
|
27
16
|
CsvChoice,
|
28
17
|
CsvEnumChoice,
|
29
|
-
CsvListChoice,
|
30
18
|
CustomHelpMessageChoice,
|
31
19
|
RegistryChoice,
|
32
20
|
)
|
33
|
-
from schemathesis.
|
21
|
+
from schemathesis.config import (
|
22
|
+
DEFAULT_REPORT_DIRECTORY,
|
23
|
+
HealthCheck,
|
24
|
+
ReportFormat,
|
25
|
+
SchemathesisConfig,
|
26
|
+
SchemathesisWarning,
|
27
|
+
)
|
28
|
+
from schemathesis.core import HYPOTHESIS_IN_MEMORY_DATABASE_IDENTIFIER
|
34
29
|
from schemathesis.core.transport import DEFAULT_RESPONSE_TIMEOUT
|
35
|
-
from schemathesis.
|
36
|
-
from schemathesis.
|
37
|
-
from schemathesis.generation import DEFAULT_GENERATOR_MODES, GenerationConfig, GenerationMode
|
38
|
-
from schemathesis.generation.overrides import Override
|
39
|
-
from schemathesis.generation.targets import TARGETS
|
30
|
+
from schemathesis.generation import GenerationMode
|
31
|
+
from schemathesis.generation.metrics import METRICS, MetricFunction
|
40
32
|
|
41
33
|
# NOTE: Need to explicitly import all registered checks
|
42
34
|
from schemathesis.specs.openapi.checks import * # noqa: F401, F403
|
43
35
|
|
44
36
|
COLOR_OPTIONS_INVALID_USAGE_MESSAGE = "Can't use `--no-color` and `--force-color` simultaneously"
|
45
37
|
|
46
|
-
DEFAULT_PHASES =
|
38
|
+
DEFAULT_PHASES = ["examples", "coverage", "fuzzing", "stateful"]
|
47
39
|
|
48
40
|
|
49
|
-
@click.argument(
|
41
|
+
@click.argument( # type: ignore[misc]
|
42
|
+
"location",
|
43
|
+
type=str,
|
44
|
+
callback=validation.validate_schema_location,
|
45
|
+
)
|
50
46
|
@group("Options")
|
51
47
|
@grouped_option(
|
52
48
|
"--url",
|
@@ -61,7 +57,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
61
57
|
@grouped_option(
|
62
58
|
"--workers",
|
63
59
|
"-w",
|
64
|
-
"
|
60
|
+
"workers",
|
65
61
|
help="Number of concurrent workers for testing. Auto-adjusts if 'auto' is specified",
|
66
62
|
type=CustomHelpMessageChoice(
|
67
63
|
["auto", *list(map(str, range(MIN_WORKERS, MAX_WORKERS + 1)))],
|
@@ -92,6 +88,14 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
92
88
|
default=None,
|
93
89
|
envvar="SCHEMATHESIS_WAIT_FOR_SCHEMA",
|
94
90
|
)
|
91
|
+
@grouped_option(
|
92
|
+
"--warnings",
|
93
|
+
help="Control warning display: 'off' to disable all, or comma-separated list of warning types to enable",
|
94
|
+
type=str,
|
95
|
+
default=None,
|
96
|
+
callback=validation.validate_warnings,
|
97
|
+
metavar="WARNINGS",
|
98
|
+
)
|
95
99
|
@group("API validation options")
|
96
100
|
@grouped_option(
|
97
101
|
"--checks",
|
@@ -100,7 +104,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
100
104
|
multiple=True,
|
101
105
|
help="Comma-separated list of checks to run against API responses",
|
102
106
|
type=RegistryChoice(CHECKS, with_all=True),
|
103
|
-
default=
|
107
|
+
default=None,
|
104
108
|
callback=validation.reduce_list,
|
105
109
|
show_default=True,
|
106
110
|
metavar="",
|
@@ -111,7 +115,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
111
115
|
multiple=True,
|
112
116
|
help="Comma-separated list of checks to skip during testing",
|
113
117
|
type=RegistryChoice(CHECKS, with_all=True),
|
114
|
-
default=
|
118
|
+
default=None,
|
115
119
|
callback=validation.reduce_list,
|
116
120
|
show_default=True,
|
117
121
|
metavar="",
|
@@ -153,12 +157,14 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
153
157
|
"include_by",
|
154
158
|
type=str,
|
155
159
|
metavar="EXPR",
|
160
|
+
callback=validation.validate_filter_expression,
|
156
161
|
help="Include using custom expression",
|
157
162
|
)
|
158
163
|
@grouped_option(
|
159
164
|
"--exclude-by",
|
160
165
|
"exclude_by",
|
161
166
|
type=str,
|
167
|
+
callback=validation.validate_filter_expression,
|
162
168
|
metavar="EXPR",
|
163
169
|
help="Exclude using custom expression",
|
164
170
|
)
|
@@ -246,6 +252,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
246
252
|
)
|
247
253
|
@grouped_option(
|
248
254
|
"--report-dir",
|
255
|
+
"report_directory",
|
249
256
|
help="Directory to store all report files",
|
250
257
|
type=click.Path(file_okay=False, dir_okay=True),
|
251
258
|
default=DEFAULT_REPORT_DIRECTORY,
|
@@ -295,53 +302,6 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
295
302
|
metavar="BOOLEAN",
|
296
303
|
callback=validation.convert_boolean_string,
|
297
304
|
)
|
298
|
-
@group("Experimental options")
|
299
|
-
@grouped_option(
|
300
|
-
"--experimental",
|
301
|
-
"experiments",
|
302
|
-
help="Enable experimental features",
|
303
|
-
type=click.Choice(sorted([experiment.label for experiment in experimental.GLOBAL_EXPERIMENTS.available])),
|
304
|
-
callback=validation.convert_experimental,
|
305
|
-
multiple=True,
|
306
|
-
metavar="FEATURES",
|
307
|
-
)
|
308
|
-
@grouped_option(
|
309
|
-
"--experimental-coverage-unexpected-methods",
|
310
|
-
"coverage_unexpected_methods",
|
311
|
-
help="HTTP methods to use when generating test cases with methods not specified in the API during the coverage phase.",
|
312
|
-
type=CsvChoice(["get", "put", "post", "delete", "options", "head", "patch", "trace"], case_sensitive=False),
|
313
|
-
callback=validation.convert_http_methods,
|
314
|
-
metavar="",
|
315
|
-
default=None,
|
316
|
-
envvar="SCHEMATHESIS_EXPERIMENTAL_COVERAGE_UNEXPECTED_METHODS",
|
317
|
-
)
|
318
|
-
@grouped_option(
|
319
|
-
"--experimental-missing-required-header-allowed-statuses",
|
320
|
-
"missing_required_header_allowed_statuses",
|
321
|
-
help="Comma-separated list of status codes expected for test cases with a missing required header",
|
322
|
-
type=CsvListChoice(),
|
323
|
-
callback=validation.convert_status_codes,
|
324
|
-
metavar="",
|
325
|
-
envvar="SCHEMATHESIS_EXPERIMENTAL_MISSING_REQUIRED_HEADER_ALLOWED_STATUSES",
|
326
|
-
)
|
327
|
-
@grouped_option(
|
328
|
-
"--experimental-positive-data-acceptance-allowed-statuses",
|
329
|
-
"positive_data_acceptance_allowed_statuses",
|
330
|
-
help="Comma-separated list of status codes considered as successful responses",
|
331
|
-
type=CsvListChoice(),
|
332
|
-
callback=validation.convert_status_codes,
|
333
|
-
metavar="",
|
334
|
-
envvar="SCHEMATHESIS_EXPERIMENTAL_POSITIVE_DATA_ACCEPTANCE_ALLOWED_STATUSES",
|
335
|
-
)
|
336
|
-
@grouped_option(
|
337
|
-
"--experimental-negative-data-rejection-allowed-statuses",
|
338
|
-
"negative_data_rejection_allowed_statuses",
|
339
|
-
help="Comma-separated list of status codes expected for rejected negative data",
|
340
|
-
type=CsvListChoice(),
|
341
|
-
callback=validation.convert_status_codes,
|
342
|
-
metavar="",
|
343
|
-
envvar="SCHEMATHESIS_EXPERIMENTAL_NEGATIVE_DATA_REJECTION_ALLOWED_STATUSES",
|
344
|
-
)
|
345
305
|
@group("Data generation options")
|
346
306
|
@grouped_option(
|
347
307
|
"--mode",
|
@@ -349,7 +309,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
349
309
|
"generation_modes",
|
350
310
|
help="Test data generation mode",
|
351
311
|
type=click.Choice([item.value for item in GenerationMode] + ["all"]),
|
352
|
-
default=
|
312
|
+
default="all",
|
353
313
|
callback=validation.convert_generation_mode,
|
354
314
|
show_default=True,
|
355
315
|
metavar="",
|
@@ -378,7 +338,7 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
378
338
|
help="Enables deterministic mode, which eliminates random variation between tests",
|
379
339
|
is_flag=True,
|
380
340
|
is_eager=True,
|
381
|
-
default=
|
341
|
+
default=False,
|
382
342
|
show_default=True,
|
383
343
|
)
|
384
344
|
@grouped_option(
|
@@ -402,9 +362,9 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
402
362
|
"generation_maximize",
|
403
363
|
multiple=True,
|
404
364
|
help="Guide input generation to values more likely to expose bugs via targeted property-based testing",
|
405
|
-
type=RegistryChoice(
|
365
|
+
type=RegistryChoice(METRICS),
|
406
366
|
default=None,
|
407
|
-
callback=validation.
|
367
|
+
callback=validation.convert_maximize,
|
408
368
|
show_default=True,
|
409
369
|
metavar="METRIC",
|
410
370
|
)
|
@@ -443,119 +403,69 @@ DEFAULT_PHASES = ("examples", "coverage", "fuzzing", "stateful")
|
|
443
403
|
show_default=True,
|
444
404
|
metavar="BOOLEAN",
|
445
405
|
)
|
446
|
-
@grouped_option(
|
447
|
-
"--contrib-openapi-fill-missing-examples",
|
448
|
-
"contrib_openapi_fill_missing_examples",
|
449
|
-
help="Enable generation of random examples for API operations that do not have explicit examples",
|
450
|
-
is_flag=True,
|
451
|
-
default=False,
|
452
|
-
show_default=True,
|
453
|
-
metavar="BOOLEAN",
|
454
|
-
)
|
455
|
-
@group("Open API options")
|
456
|
-
@grouped_option(
|
457
|
-
"--set-query",
|
458
|
-
"set_query",
|
459
|
-
help=r"OpenAPI: Override a specific query parameter by specifying 'parameter=value'",
|
460
|
-
multiple=True,
|
461
|
-
type=str,
|
462
|
-
callback=validation.validate_set_query,
|
463
|
-
)
|
464
|
-
@grouped_option(
|
465
|
-
"--set-header",
|
466
|
-
"set_header",
|
467
|
-
help=r"OpenAPI: Override a specific header parameter by specifying 'parameter=value'",
|
468
|
-
multiple=True,
|
469
|
-
type=str,
|
470
|
-
callback=validation.validate_set_header,
|
471
|
-
)
|
472
|
-
@grouped_option(
|
473
|
-
"--set-cookie",
|
474
|
-
"set_cookie",
|
475
|
-
help=r"OpenAPI: Override a specific cookie parameter by specifying 'parameter=value'",
|
476
|
-
multiple=True,
|
477
|
-
type=str,
|
478
|
-
callback=validation.validate_set_cookie,
|
479
|
-
)
|
480
|
-
@grouped_option(
|
481
|
-
"--set-path",
|
482
|
-
"set_path",
|
483
|
-
help=r"OpenAPI: Override a specific path parameter by specifying 'parameter=value'",
|
484
|
-
multiple=True,
|
485
|
-
type=str,
|
486
|
-
callback=validation.validate_set_path,
|
487
|
-
)
|
488
406
|
@group("Global options")
|
489
407
|
@grouped_option("--no-color", help="Disable ANSI color escape codes", type=bool, is_flag=True)
|
490
408
|
@grouped_option("--force-color", help="Explicitly tells to enable ANSI color escape codes", type=bool, is_flag=True)
|
491
409
|
@click.pass_context # type: ignore[misc]
|
492
410
|
def run(
|
493
411
|
ctx: click.Context,
|
494
|
-
|
412
|
+
*,
|
413
|
+
location: str,
|
495
414
|
auth: tuple[str, str] | None,
|
496
415
|
headers: dict[str, str],
|
497
|
-
|
498
|
-
|
499
|
-
set_cookie: dict[str, str],
|
500
|
-
set_path: dict[str, str],
|
501
|
-
experiments: list,
|
502
|
-
coverage_unexpected_methods: set[str] | None,
|
503
|
-
missing_required_header_allowed_statuses: list[str],
|
504
|
-
positive_data_acceptance_allowed_statuses: list[str],
|
505
|
-
negative_data_rejection_allowed_statuses: list[str],
|
506
|
-
included_check_names: Sequence[str],
|
507
|
-
excluded_check_names: Sequence[str],
|
416
|
+
included_check_names: list[str] | None,
|
417
|
+
excluded_check_names: list[str] | None,
|
508
418
|
max_response_time: float | None = None,
|
509
|
-
phases:
|
419
|
+
phases: list[str] = DEFAULT_PHASES,
|
510
420
|
max_failures: int | None = None,
|
511
|
-
continue_on_failure: bool =
|
512
|
-
include_path:
|
513
|
-
include_path_regex: str | None
|
514
|
-
include_method:
|
515
|
-
include_method_regex: str | None
|
516
|
-
include_name:
|
517
|
-
include_name_regex: str | None
|
518
|
-
include_tag:
|
519
|
-
include_tag_regex: str | None
|
520
|
-
include_operation_id:
|
521
|
-
include_operation_id_regex: str | None
|
522
|
-
exclude_path:
|
523
|
-
exclude_path_regex: str | None
|
524
|
-
exclude_method:
|
525
|
-
exclude_method_regex: str | None
|
526
|
-
exclude_name:
|
527
|
-
exclude_name_regex: str | None
|
528
|
-
exclude_tag:
|
529
|
-
exclude_tag_regex: str | None
|
530
|
-
exclude_operation_id:
|
531
|
-
exclude_operation_id_regex: str | None
|
532
|
-
include_by:
|
533
|
-
exclude_by:
|
421
|
+
continue_on_failure: bool | None = None,
|
422
|
+
include_path: tuple[str, ...],
|
423
|
+
include_path_regex: str | None,
|
424
|
+
include_method: tuple[str, ...],
|
425
|
+
include_method_regex: str | None,
|
426
|
+
include_name: tuple[str, ...],
|
427
|
+
include_name_regex: str | None,
|
428
|
+
include_tag: tuple[str, ...],
|
429
|
+
include_tag_regex: str | None,
|
430
|
+
include_operation_id: tuple[str, ...],
|
431
|
+
include_operation_id_regex: str | None,
|
432
|
+
exclude_path: tuple[str, ...],
|
433
|
+
exclude_path_regex: str | None,
|
434
|
+
exclude_method: tuple[str, ...],
|
435
|
+
exclude_method_regex: str | None,
|
436
|
+
exclude_name: tuple[str, ...],
|
437
|
+
exclude_name_regex: str | None,
|
438
|
+
exclude_tag: tuple[str, ...],
|
439
|
+
exclude_tag_regex: str | None,
|
440
|
+
exclude_operation_id: tuple[str, ...],
|
441
|
+
exclude_operation_id_regex: str | None,
|
442
|
+
include_by: Callable | None = None,
|
443
|
+
exclude_by: Callable | None = None,
|
534
444
|
exclude_deprecated: bool = False,
|
535
|
-
|
536
|
-
base_url: str | None
|
445
|
+
workers: int = DEFAULT_WORKERS,
|
446
|
+
base_url: str | None,
|
537
447
|
wait_for_schema: float | None = None,
|
448
|
+
suppress_health_check: list[HealthCheck] | None,
|
449
|
+
warnings: bool | list[SchemathesisWarning] | None,
|
538
450
|
rate_limit: str | None = None,
|
539
|
-
suppress_health_check: list[HealthCheck] | None = None,
|
540
451
|
request_timeout: int | None = None,
|
541
452
|
request_tls_verify: bool = True,
|
542
453
|
request_cert: str | None = None,
|
543
454
|
request_cert_key: str | None = None,
|
544
455
|
request_proxy: str | None = None,
|
545
|
-
report_formats: list[ReportFormat] | None
|
546
|
-
|
456
|
+
report_formats: list[ReportFormat] | None,
|
457
|
+
report_directory: Path | str = DEFAULT_REPORT_DIRECTORY,
|
547
458
|
report_junit_path: LazyFile | None = None,
|
548
459
|
report_vcr_path: LazyFile | None = None,
|
549
460
|
report_har_path: LazyFile | None = None,
|
550
461
|
report_preserve_bytes: bool = False,
|
551
462
|
output_sanitize: bool = True,
|
552
463
|
output_truncate: bool = True,
|
553
|
-
|
554
|
-
generation_modes: tuple[GenerationMode, ...] = DEFAULT_GENERATOR_MODES,
|
464
|
+
generation_modes: list[GenerationMode],
|
555
465
|
generation_seed: int | None = None,
|
556
466
|
generation_max_examples: int | None = None,
|
557
|
-
generation_maximize:
|
558
|
-
generation_deterministic: bool
|
467
|
+
generation_maximize: list[MetricFunction] | None,
|
468
|
+
generation_deterministic: bool = False,
|
559
469
|
generation_database: str | None = None,
|
560
470
|
generation_unique_inputs: bool = False,
|
561
471
|
generation_allow_x00: bool = True,
|
@@ -573,126 +483,105 @@ def run(
|
|
573
483
|
"""
|
574
484
|
if no_color and force_color:
|
575
485
|
raise click.UsageError(COLOR_OPTIONS_INVALID_USAGE_MESSAGE)
|
576
|
-
ensure_color(ctx, no_color, force_color)
|
577
|
-
|
578
|
-
validation.validate_schema(schema, base_url)
|
579
486
|
|
580
|
-
|
581
|
-
_hypothesis_suppress_health_check = prepare_health_checks(suppress_health_check)
|
487
|
+
config: SchemathesisConfig = ctx.obj.config
|
582
488
|
|
583
|
-
|
584
|
-
|
585
|
-
if
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
489
|
+
# First, set the right color
|
490
|
+
color: bool | None
|
491
|
+
if force_color:
|
492
|
+
color = True
|
493
|
+
elif no_color:
|
494
|
+
color = False
|
495
|
+
else:
|
496
|
+
color = config.color
|
497
|
+
ensure_color(ctx, color)
|
591
498
|
|
592
|
-
|
593
|
-
include_path=include_path,
|
594
|
-
include_method=include_method,
|
595
|
-
include_name=include_name,
|
596
|
-
include_tag=include_tag,
|
597
|
-
include_operation_id=include_operation_id,
|
598
|
-
include_path_regex=include_path_regex,
|
599
|
-
include_method_regex=include_method_regex,
|
600
|
-
include_name_regex=include_name_regex,
|
601
|
-
include_tag_regex=include_tag_regex,
|
602
|
-
include_operation_id_regex=include_operation_id_regex,
|
603
|
-
exclude_path=exclude_path,
|
604
|
-
exclude_method=exclude_method,
|
605
|
-
exclude_name=exclude_name,
|
606
|
-
exclude_tag=exclude_tag,
|
607
|
-
exclude_operation_id=exclude_operation_id,
|
608
|
-
exclude_path_regex=exclude_path_regex,
|
609
|
-
exclude_method_regex=exclude_method_regex,
|
610
|
-
exclude_name_regex=exclude_name_regex,
|
611
|
-
exclude_tag_regex=exclude_tag_regex,
|
612
|
-
exclude_operation_id_regex=exclude_operation_id_regex,
|
613
|
-
include_by=include_by,
|
614
|
-
exclude_by=exclude_by,
|
615
|
-
exclude_deprecated=exclude_deprecated,
|
616
|
-
).into()
|
499
|
+
validation.validate_auth_overlap(auth, headers)
|
617
500
|
|
618
|
-
|
501
|
+
# Then override the global config from CLI options
|
502
|
+
config.update(
|
503
|
+
color=color,
|
504
|
+
suppress_health_check=suppress_health_check,
|
505
|
+
seed=generation_seed,
|
506
|
+
wait_for_schema=wait_for_schema,
|
507
|
+
max_failures=max_failures,
|
508
|
+
)
|
509
|
+
config.output.sanitization.update(enabled=output_sanitize)
|
510
|
+
config.output.truncation.update(enabled=output_truncate)
|
511
|
+
config.reports.update(
|
512
|
+
formats=report_formats,
|
513
|
+
junit_path=report_junit_path.name if report_junit_path else None,
|
514
|
+
vcr_path=report_vcr_path.name if report_vcr_path else None,
|
515
|
+
har_path=report_har_path.name if report_har_path else None,
|
516
|
+
directory=Path(report_directory),
|
517
|
+
preserve_bytes=report_preserve_bytes,
|
518
|
+
)
|
519
|
+
# Other CLI options work as an override for all defined projects
|
520
|
+
config.projects.override.update(
|
521
|
+
base_url=base_url,
|
522
|
+
headers=headers if headers else None,
|
523
|
+
basic_auth=auth,
|
524
|
+
workers=workers,
|
525
|
+
continue_on_failure=continue_on_failure,
|
526
|
+
rate_limit=rate_limit,
|
527
|
+
request_timeout=request_timeout,
|
528
|
+
tls_verify=request_tls_verify,
|
529
|
+
request_cert=request_cert,
|
530
|
+
request_cert_key=request_cert_key,
|
531
|
+
proxy=request_proxy,
|
532
|
+
warnings=warnings,
|
533
|
+
)
|
534
|
+
# These are filters for what API operations should be tested
|
535
|
+
filter_set = {
|
536
|
+
"include_path": include_path,
|
537
|
+
"include_method": include_method,
|
538
|
+
"include_name": include_name,
|
539
|
+
"include_tag": include_tag,
|
540
|
+
"include_operation_id": include_operation_id,
|
541
|
+
"include_path_regex": include_path_regex,
|
542
|
+
"include_method_regex": include_method_regex,
|
543
|
+
"include_name_regex": include_name_regex,
|
544
|
+
"include_tag_regex": include_tag_regex,
|
545
|
+
"include_operation_id_regex": include_operation_id_regex,
|
546
|
+
"exclude_path": exclude_path,
|
547
|
+
"exclude_method": exclude_method,
|
548
|
+
"exclude_name": exclude_name,
|
549
|
+
"exclude_tag": exclude_tag,
|
550
|
+
"exclude_operation_id": exclude_operation_id,
|
551
|
+
"exclude_path_regex": exclude_path_regex,
|
552
|
+
"exclude_method_regex": exclude_method_regex,
|
553
|
+
"exclude_name_regex": exclude_name_regex,
|
554
|
+
"exclude_tag_regex": exclude_tag_regex,
|
555
|
+
"exclude_operation_id_regex": exclude_operation_id_regex,
|
556
|
+
"include_by": include_by,
|
557
|
+
"exclude_by": exclude_by,
|
558
|
+
"exclude_deprecated": exclude_deprecated,
|
559
|
+
}
|
560
|
+
config.projects.override.phases.update(phases=phases)
|
561
|
+
config.projects.override.checks.update(
|
619
562
|
included_check_names=included_check_names,
|
620
563
|
excluded_check_names=excluded_check_names,
|
621
|
-
positive_data_acceptance_allowed_statuses=positive_data_acceptance_allowed_statuses,
|
622
|
-
missing_required_header_allowed_statuses=missing_required_header_allowed_statuses,
|
623
|
-
negative_data_rejection_allowed_statuses=negative_data_rejection_allowed_statuses,
|
624
564
|
max_response_time=max_response_time,
|
625
|
-
)
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
# Use the same seed for all tests unless `derandomize=True` is used
|
640
|
-
seed: int | None
|
641
|
-
if generation_seed is None and not generation_deterministic:
|
642
|
-
seed = Random().getrandbits(128)
|
643
|
-
else:
|
644
|
-
seed = generation_seed
|
645
|
-
|
646
|
-
phases_ = [PhaseName.PROBING] + [PhaseName.from_str(phase) for phase in phases]
|
565
|
+
)
|
566
|
+
config.projects.override.generation.update(
|
567
|
+
modes=generation_modes,
|
568
|
+
max_examples=generation_max_examples,
|
569
|
+
no_shrink=generation_no_shrink,
|
570
|
+
maximize=generation_maximize,
|
571
|
+
deterministic=generation_deterministic,
|
572
|
+
database=generation_database,
|
573
|
+
unique_inputs=generation_unique_inputs,
|
574
|
+
allow_x00=generation_allow_x00,
|
575
|
+
graphql_allow_null=generation_graphql_allow_null,
|
576
|
+
with_security_parameters=generation_with_security_parameters,
|
577
|
+
codec=generation_codec,
|
578
|
+
)
|
647
579
|
|
648
|
-
|
649
|
-
location=
|
650
|
-
base_url=base_url,
|
651
|
-
engine=EngineConfig(
|
652
|
-
execution=ExecutionConfig(
|
653
|
-
phases=phases_,
|
654
|
-
checks=selected_checks,
|
655
|
-
targets=TARGETS.get_by_names(generation_maximize or []),
|
656
|
-
hypothesis_settings=prepare_settings(
|
657
|
-
database=generation_database,
|
658
|
-
derandomize=generation_deterministic,
|
659
|
-
max_examples=generation_max_examples,
|
660
|
-
phases=_hypothesis_phases,
|
661
|
-
suppress_health_check=_hypothesis_suppress_health_check,
|
662
|
-
),
|
663
|
-
generation=GenerationConfig(
|
664
|
-
modes=list(generation_modes),
|
665
|
-
allow_x00=generation_allow_x00,
|
666
|
-
graphql_allow_null=generation_graphql_allow_null,
|
667
|
-
codec=generation_codec,
|
668
|
-
with_security_parameters=generation_with_security_parameters,
|
669
|
-
unexpected_methods=coverage_unexpected_methods,
|
670
|
-
),
|
671
|
-
max_failures=max_failures,
|
672
|
-
continue_on_failure=continue_on_failure,
|
673
|
-
unique_inputs=generation_unique_inputs,
|
674
|
-
seed=seed,
|
675
|
-
workers_num=workers_num,
|
676
|
-
),
|
677
|
-
network=NetworkConfig(
|
678
|
-
auth=auth,
|
679
|
-
headers=headers,
|
680
|
-
timeout=request_timeout,
|
681
|
-
tls_verify=request_tls_verify,
|
682
|
-
proxy=request_proxy,
|
683
|
-
cert=(request_cert, request_cert_key)
|
684
|
-
if request_cert is not None and request_cert_key is not None
|
685
|
-
else request_cert,
|
686
|
-
),
|
687
|
-
override=override,
|
688
|
-
checks_config=checks_config,
|
689
|
-
),
|
580
|
+
executor.execute(
|
581
|
+
location=location,
|
690
582
|
filter_set=filter_set,
|
691
|
-
|
692
|
-
|
693
|
-
output=OutputConfig(sanitize=output_sanitize, truncate=output_truncate),
|
694
|
-
report=report_config,
|
583
|
+
# We don't the project yet, so pass the default config
|
584
|
+
config=config.projects.get_default(),
|
695
585
|
args=ctx.args,
|
696
586
|
params=ctx.params,
|
697
587
|
)
|
698
|
-
executor.execute(config)
|
@@ -1,16 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
from dataclasses import dataclass, field
|
4
|
-
from typing import TYPE_CHECKING, Generator
|
4
|
+
from typing import TYPE_CHECKING, Callable, Generator
|
5
5
|
|
6
|
+
from schemathesis.cli.commands.run.events import LoadingFinished
|
7
|
+
from schemathesis.config import ProjectConfig
|
6
8
|
from schemathesis.core.failures import Failure
|
7
|
-
from schemathesis.core.output import OutputConfig
|
8
9
|
from schemathesis.core.result import Err, Ok
|
9
10
|
from schemathesis.core.transforms import UNRESOLVABLE
|
10
11
|
from schemathesis.core.transport import Response
|
11
12
|
from schemathesis.engine import Status, events
|
12
13
|
from schemathesis.engine.recorder import CaseNode, ScenarioRecorder
|
13
14
|
from schemathesis.generation.case import Case
|
15
|
+
from schemathesis.schemas import APIOperation
|
14
16
|
|
15
17
|
if TYPE_CHECKING:
|
16
18
|
from schemathesis.generation.stateful.state_machine import ExtractionFailure
|
@@ -176,12 +178,12 @@ class GroupedFailures:
|
|
176
178
|
class ExecutionContext:
|
177
179
|
"""Storage for the current context of the execution."""
|
178
180
|
|
181
|
+
config: ProjectConfig
|
182
|
+
find_operation_by_label: Callable[[str], APIOperation | None] | None = None
|
179
183
|
statistic: Statistic = field(default_factory=Statistic)
|
180
184
|
exit_code: int = 0
|
181
|
-
output_config: OutputConfig = field(default_factory=OutputConfig)
|
182
185
|
initialization_lines: list[str | Generator[str, None, None]] = field(default_factory=list)
|
183
186
|
summary_lines: list[str | Generator[str, None, None]] = field(default_factory=list)
|
184
|
-
seed: int | None = None
|
185
187
|
|
186
188
|
def add_initialization_line(self, line: str | Generator[str, None, None]) -> None:
|
187
189
|
self.initialization_lines.append(line)
|
@@ -190,6 +192,8 @@ class ExecutionContext:
|
|
190
192
|
self.summary_lines.append(line)
|
191
193
|
|
192
194
|
def on_event(self, event: events.EngineEvent) -> None:
|
195
|
+
if isinstance(event, LoadingFinished):
|
196
|
+
self.find_operation_by_label = event.find_operation_by_label
|
193
197
|
if isinstance(event, events.ScenarioFinished):
|
194
198
|
self.statistic.on_scenario_finished(event.recorder)
|
195
199
|
elif isinstance(event, events.NonFatalError) or (
|
@@ -1,9 +1,13 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
1
3
|
import time
|
2
4
|
import uuid
|
5
|
+
from typing import Callable
|
3
6
|
|
7
|
+
from schemathesis.config import ProjectConfig
|
4
8
|
from schemathesis.core import Specification
|
5
9
|
from schemathesis.engine import events
|
6
|
-
from schemathesis.schemas import ApiStatistic
|
10
|
+
from schemathesis.schemas import APIOperation, ApiStatistic
|
7
11
|
|
8
12
|
|
9
13
|
class LoadingStarted(events.EngineEvent):
|
@@ -26,6 +30,8 @@ class LoadingFinished(events.EngineEvent):
|
|
26
30
|
"specification",
|
27
31
|
"statistic",
|
28
32
|
"schema",
|
33
|
+
"config",
|
34
|
+
"find_operation_by_label",
|
29
35
|
)
|
30
36
|
|
31
37
|
def __init__(
|
@@ -38,6 +44,8 @@ class LoadingFinished(events.EngineEvent):
|
|
38
44
|
specification: Specification,
|
39
45
|
statistic: ApiStatistic,
|
40
46
|
schema: dict,
|
47
|
+
config: ProjectConfig,
|
48
|
+
find_operation_by_label: Callable[[str], APIOperation | None],
|
41
49
|
) -> None:
|
42
50
|
self.id = uuid.uuid4()
|
43
51
|
self.timestamp = time.time()
|
@@ -48,3 +56,5 @@ class LoadingFinished(events.EngineEvent):
|
|
48
56
|
self.statistic = statistic
|
49
57
|
self.schema = schema
|
50
58
|
self.base_path = base_path
|
59
|
+
self.config = config
|
60
|
+
self.find_operation_by_label = find_operation_by_label
|