schemathesis 3.21.2__py3-none-any.whl → 3.22.1__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 +1 -1
- schemathesis/_compat.py +2 -18
- schemathesis/_dependency_versions.py +1 -6
- schemathesis/_hypothesis.py +15 -12
- schemathesis/_lazy_import.py +3 -2
- schemathesis/_xml.py +12 -11
- schemathesis/auths.py +88 -81
- schemathesis/checks.py +4 -4
- schemathesis/cli/__init__.py +202 -171
- schemathesis/cli/callbacks.py +29 -32
- schemathesis/cli/cassettes.py +25 -25
- schemathesis/cli/context.py +18 -12
- schemathesis/cli/junitxml.py +2 -2
- schemathesis/cli/options.py +10 -11
- schemathesis/cli/output/default.py +64 -34
- schemathesis/code_samples.py +10 -10
- schemathesis/constants.py +1 -1
- schemathesis/contrib/unique_data.py +2 -2
- schemathesis/exceptions.py +55 -42
- schemathesis/extra/_aiohttp.py +2 -2
- schemathesis/extra/_flask.py +2 -2
- schemathesis/extra/_server.py +3 -2
- schemathesis/extra/pytest_plugin.py +10 -10
- schemathesis/failures.py +16 -16
- schemathesis/filters.py +40 -41
- schemathesis/fixups/__init__.py +4 -3
- schemathesis/fixups/fast_api.py +5 -4
- schemathesis/generation/__init__.py +16 -4
- schemathesis/hooks.py +25 -25
- schemathesis/internal/jsonschema.py +4 -3
- schemathesis/internal/transformation.py +3 -2
- schemathesis/lazy.py +39 -31
- schemathesis/loaders.py +8 -8
- schemathesis/models.py +128 -126
- schemathesis/parameters.py +6 -5
- schemathesis/runner/__init__.py +107 -81
- schemathesis/runner/events.py +37 -26
- schemathesis/runner/impl/core.py +86 -81
- schemathesis/runner/impl/solo.py +19 -15
- schemathesis/runner/impl/threadpool.py +40 -22
- schemathesis/runner/serialization.py +67 -40
- schemathesis/sanitization.py +18 -20
- schemathesis/schemas.py +83 -72
- schemathesis/serializers.py +39 -30
- schemathesis/service/ci.py +20 -21
- schemathesis/service/client.py +29 -9
- schemathesis/service/constants.py +1 -0
- schemathesis/service/events.py +2 -2
- schemathesis/service/hosts.py +8 -7
- schemathesis/service/metadata.py +5 -0
- schemathesis/service/models.py +22 -4
- schemathesis/service/report.py +15 -15
- schemathesis/service/serialization.py +23 -27
- schemathesis/service/usage.py +8 -7
- schemathesis/specs/graphql/loaders.py +31 -24
- schemathesis/specs/graphql/nodes.py +3 -2
- schemathesis/specs/graphql/scalars.py +26 -2
- schemathesis/specs/graphql/schemas.py +38 -34
- schemathesis/specs/openapi/_hypothesis.py +62 -44
- schemathesis/specs/openapi/checks.py +10 -10
- schemathesis/specs/openapi/converter.py +10 -9
- schemathesis/specs/openapi/definitions.py +2 -2
- schemathesis/specs/openapi/examples.py +22 -21
- schemathesis/specs/openapi/expressions/nodes.py +5 -4
- schemathesis/specs/openapi/expressions/parser.py +7 -6
- schemathesis/specs/openapi/filters.py +6 -6
- schemathesis/specs/openapi/formats.py +2 -2
- schemathesis/specs/openapi/links.py +19 -21
- schemathesis/specs/openapi/loaders.py +133 -78
- schemathesis/specs/openapi/negative/__init__.py +16 -11
- schemathesis/specs/openapi/negative/mutations.py +11 -10
- schemathesis/specs/openapi/parameters.py +20 -19
- schemathesis/specs/openapi/references.py +21 -20
- schemathesis/specs/openapi/schemas.py +97 -84
- schemathesis/specs/openapi/security.py +25 -24
- schemathesis/specs/openapi/serialization.py +20 -23
- schemathesis/specs/openapi/stateful/__init__.py +12 -11
- schemathesis/specs/openapi/stateful/links.py +7 -7
- schemathesis/specs/openapi/utils.py +4 -3
- schemathesis/specs/openapi/validation.py +3 -2
- schemathesis/stateful/__init__.py +15 -16
- schemathesis/stateful/state_machine.py +9 -9
- schemathesis/targets.py +3 -3
- schemathesis/throttling.py +2 -2
- schemathesis/transports/auth.py +2 -2
- schemathesis/transports/content_types.py +5 -0
- schemathesis/transports/headers.py +3 -2
- schemathesis/transports/responses.py +1 -1
- schemathesis/utils.py +7 -10
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/METADATA +12 -13
- schemathesis-3.22.1.dist-info/RECORD +130 -0
- schemathesis-3.21.2.dist-info/RECORD +0 -130
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/WHEEL +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.21.2.dist-info → schemathesis-3.22.1.dist-info}/licenses/LICENSE +0 -0
schemathesis/runner/impl/core.py
CHANGED
|
@@ -8,7 +8,7 @@ import uuid
|
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from dataclasses import dataclass, field
|
|
10
10
|
from types import TracebackType
|
|
11
|
-
from typing import Any, Callable,
|
|
11
|
+
from typing import Any, Callable, Generator, Iterable, cast, TYPE_CHECKING, Literal
|
|
12
12
|
from warnings import WarningMessage, catch_warnings
|
|
13
13
|
|
|
14
14
|
import hypothesis
|
|
@@ -21,9 +21,8 @@ from requests.auth import HTTPDigestAuth, _basic_auth_str
|
|
|
21
21
|
|
|
22
22
|
from ... import failures, hooks
|
|
23
23
|
from ..._compat import MultipleFailures
|
|
24
|
-
from ..._dependency_versions import IS_HYPOTHESIS_ABOVE_6_54
|
|
25
24
|
from ...auths import unregister as unregister_auth
|
|
26
|
-
from ...generation import DataGenerationMethod
|
|
25
|
+
from ...generation import DataGenerationMethod, GenerationConfig
|
|
27
26
|
from ...constants import DEFAULT_STATEFUL_RECURSION_LIMIT, RECURSIVE_REFERENCE_ERROR_MESSAGE, USER_AGENT
|
|
28
27
|
from ...exceptions import (
|
|
29
28
|
CheckFailed,
|
|
@@ -61,25 +60,27 @@ def _should_count_towards_stop(event: events.ExecutionEvent) -> bool:
|
|
|
61
60
|
class BaseRunner:
|
|
62
61
|
schema: BaseSchema
|
|
63
62
|
checks: Iterable[CheckFunction]
|
|
64
|
-
max_response_time:
|
|
63
|
+
max_response_time: int | None
|
|
65
64
|
targets: Iterable[Target]
|
|
66
65
|
hypothesis_settings: hypothesis.settings
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
generation_config: GenerationConfig
|
|
67
|
+
auth: RawAuth | None = None
|
|
68
|
+
auth_type: str | None = None
|
|
69
|
+
headers: dict[str, Any] | None = None
|
|
70
|
+
request_timeout: int | None = None
|
|
71
71
|
store_interactions: bool = False
|
|
72
|
-
seed:
|
|
72
|
+
seed: int | None = None
|
|
73
73
|
exit_first: bool = False
|
|
74
|
-
max_failures:
|
|
74
|
+
max_failures: int | None = None
|
|
75
75
|
started_at: str = field(default_factory=current_datetime)
|
|
76
76
|
dry_run: bool = False
|
|
77
|
-
stateful:
|
|
77
|
+
stateful: Stateful | None = None
|
|
78
78
|
stateful_recursion_limit: int = DEFAULT_STATEFUL_RECURSION_LIMIT
|
|
79
79
|
count_operations: bool = True
|
|
80
|
+
count_links: bool = True
|
|
80
81
|
_failures_counter: int = 0
|
|
81
82
|
|
|
82
|
-
def execute(self) ->
|
|
83
|
+
def execute(self) -> EventStream:
|
|
83
84
|
"""Common logic for all runners."""
|
|
84
85
|
event = threading.Event()
|
|
85
86
|
return EventStream(self._generate_events(event), event)
|
|
@@ -88,9 +89,11 @@ class BaseRunner:
|
|
|
88
89
|
# If auth is explicitly provided, then the global provider is ignored
|
|
89
90
|
if self.auth is not None:
|
|
90
91
|
unregister_auth()
|
|
91
|
-
results = TestResultSet()
|
|
92
|
+
results = TestResultSet(seed=self.seed)
|
|
92
93
|
|
|
93
|
-
initialized = events.Initialized.from_schema(
|
|
94
|
+
initialized = events.Initialized.from_schema(
|
|
95
|
+
schema=self.schema, count_operations=self.count_operations, count_links=self.count_links, seed=self.seed
|
|
96
|
+
)
|
|
94
97
|
|
|
95
98
|
def _finish() -> events.Finished:
|
|
96
99
|
if has_all_not_found(results):
|
|
@@ -108,8 +111,7 @@ class BaseRunner:
|
|
|
108
111
|
return
|
|
109
112
|
|
|
110
113
|
try:
|
|
111
|
-
|
|
112
|
-
yield event
|
|
114
|
+
yield from self._execute(results, stop_event)
|
|
113
115
|
except KeyboardInterrupt:
|
|
114
116
|
yield events.Interrupted()
|
|
115
117
|
|
|
@@ -134,10 +136,11 @@ class BaseRunner:
|
|
|
134
136
|
maker: Callable,
|
|
135
137
|
template: Callable,
|
|
136
138
|
settings: hypothesis.settings,
|
|
137
|
-
|
|
139
|
+
generation_config: GenerationConfig,
|
|
140
|
+
seed: int | None,
|
|
138
141
|
results: TestResultSet,
|
|
139
142
|
recursion_level: int = 0,
|
|
140
|
-
headers:
|
|
143
|
+
headers: dict[str, Any] | None = None,
|
|
141
144
|
**kwargs: Any,
|
|
142
145
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
143
146
|
"""Run tests and recursively run additional tests."""
|
|
@@ -148,7 +151,13 @@ class BaseRunner:
|
|
|
148
151
|
as_strategy_kwargs["headers"] = {
|
|
149
152
|
key: value for key, value in headers.items() if key.lower() != "user-agent"
|
|
150
153
|
}
|
|
151
|
-
for result in maker(
|
|
154
|
+
for result in maker(
|
|
155
|
+
template,
|
|
156
|
+
settings=settings,
|
|
157
|
+
generation_config=generation_config,
|
|
158
|
+
seed=seed,
|
|
159
|
+
as_strategy_kwargs=as_strategy_kwargs,
|
|
160
|
+
):
|
|
152
161
|
if isinstance(result, Ok):
|
|
153
162
|
operation, test = result.ok()
|
|
154
163
|
feedback = Feedback(self.stateful, operation)
|
|
@@ -176,8 +185,9 @@ class BaseRunner:
|
|
|
176
185
|
yield from self._run_tests(
|
|
177
186
|
feedback.get_stateful_tests,
|
|
178
187
|
template,
|
|
179
|
-
settings,
|
|
180
|
-
|
|
188
|
+
settings=settings,
|
|
189
|
+
generation_config=generation_config,
|
|
190
|
+
seed=seed,
|
|
181
191
|
recursion_level=recursion_level + 1,
|
|
182
192
|
results=results,
|
|
183
193
|
headers=headers,
|
|
@@ -233,7 +243,7 @@ def handle_schema_error(
|
|
|
233
243
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
234
244
|
recursion_level: int,
|
|
235
245
|
*,
|
|
236
|
-
before_execution_correlation_id:
|
|
246
|
+
before_execution_correlation_id: str | None = None,
|
|
237
247
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
238
248
|
if error.method is not None:
|
|
239
249
|
assert error.path is not None
|
|
@@ -288,7 +298,7 @@ def run_test(
|
|
|
288
298
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
289
299
|
targets: Iterable[Target],
|
|
290
300
|
results: TestResultSet,
|
|
291
|
-
headers:
|
|
301
|
+
headers: dict[str, Any] | None,
|
|
292
302
|
recursion_level: int,
|
|
293
303
|
**kwargs: Any,
|
|
294
304
|
) -> Generator[events.ExecutionEvent, None, None]:
|
|
@@ -308,8 +318,8 @@ def run_test(
|
|
|
308
318
|
data_generation_method=data_generation_methods,
|
|
309
319
|
correlation_id=correlation_id,
|
|
310
320
|
)
|
|
311
|
-
hypothesis_output:
|
|
312
|
-
errors:
|
|
321
|
+
hypothesis_output: list[str] = []
|
|
322
|
+
errors: list[Exception] = []
|
|
313
323
|
test_start_time = time.monotonic()
|
|
314
324
|
setup_hypothesis_database_key(test, operation)
|
|
315
325
|
try:
|
|
@@ -326,13 +336,13 @@ def run_test(
|
|
|
326
336
|
# Test body was not executed at all - Hypothesis did not generate any tests, but there is no error
|
|
327
337
|
if not result.is_executed:
|
|
328
338
|
status = Status.skip
|
|
329
|
-
result.mark_skipped()
|
|
339
|
+
result.mark_skipped(None)
|
|
330
340
|
else:
|
|
331
341
|
status = Status.success
|
|
332
|
-
except unittest.case.SkipTest:
|
|
342
|
+
except unittest.case.SkipTest as exc:
|
|
333
343
|
# Newer Hypothesis versions raise this exception if no tests were executed
|
|
334
344
|
status = Status.skip
|
|
335
|
-
result.mark_skipped()
|
|
345
|
+
result.mark_skipped(exc)
|
|
336
346
|
except CheckFailed:
|
|
337
347
|
status = Status.failure
|
|
338
348
|
except NonCheckError:
|
|
@@ -359,9 +369,9 @@ def run_test(
|
|
|
359
369
|
except KeyboardInterrupt:
|
|
360
370
|
yield events.Interrupted()
|
|
361
371
|
return
|
|
362
|
-
except SkipTest:
|
|
372
|
+
except SkipTest as exc:
|
|
363
373
|
status = Status.skip
|
|
364
|
-
result.mark_skipped()
|
|
374
|
+
result.mark_skipped(exc)
|
|
365
375
|
except AssertionError: # comes from `hypothesis-jsonschema`
|
|
366
376
|
error = reraise(operation)
|
|
367
377
|
status = Status.error
|
|
@@ -384,6 +394,7 @@ def run_test(
|
|
|
384
394
|
status = Status.error
|
|
385
395
|
result.add_error(error)
|
|
386
396
|
test_elapsed_time = time.monotonic() - test_start_time
|
|
397
|
+
# DEPRECATED: Seed is the same per test run
|
|
387
398
|
# Fetch seed value, hypothesis generates it during test execution
|
|
388
399
|
# It may be `None` if the `derandomize` config option is set to `True`
|
|
389
400
|
result.seed = getattr(test, "_hypothesis_internal_use_seed", None) or getattr(
|
|
@@ -456,7 +467,7 @@ def setup_hypothesis_database_key(test: Callable, operation: APIOperation) -> No
|
|
|
456
467
|
test.hypothesis.inner_test._hypothesis_internal_add_digest = extra # type: ignore
|
|
457
468
|
|
|
458
469
|
|
|
459
|
-
def get_invalid_regular_expression_message(warnings:
|
|
470
|
+
def get_invalid_regular_expression_message(warnings: list[WarningMessage]) -> str | None:
|
|
460
471
|
for warning in warnings:
|
|
461
472
|
message = str(warning.message)
|
|
462
473
|
if "is not valid syntax for a Python regular expression" in message:
|
|
@@ -477,7 +488,7 @@ def reraise(operation: APIOperation) -> OperationSchemaError:
|
|
|
477
488
|
MEMORY_ADDRESS_RE = re.compile("0x[0-9a-fA-F]+")
|
|
478
489
|
|
|
479
490
|
|
|
480
|
-
def deduplicate_errors(errors:
|
|
491
|
+
def deduplicate_errors(errors: list[Exception]) -> Generator[Exception, None, None]:
|
|
481
492
|
"""Deduplicate errors by their messages + tracebacks."""
|
|
482
493
|
seen = set()
|
|
483
494
|
for error in errors:
|
|
@@ -494,11 +505,11 @@ def run_checks(
|
|
|
494
505
|
*,
|
|
495
506
|
case: Case,
|
|
496
507
|
checks: Iterable[CheckFunction],
|
|
497
|
-
check_results:
|
|
508
|
+
check_results: list[Check],
|
|
498
509
|
result: TestResult,
|
|
499
510
|
response: GenericResponse,
|
|
500
511
|
elapsed_time: float,
|
|
501
|
-
max_response_time:
|
|
512
|
+
max_response_time: int | None = None,
|
|
502
513
|
) -> None:
|
|
503
514
|
errors = []
|
|
504
515
|
|
|
@@ -523,11 +534,7 @@ def run_checks(
|
|
|
523
534
|
except AssertionError as exc:
|
|
524
535
|
add_single_failure(exc)
|
|
525
536
|
except MultipleFailures as exc:
|
|
526
|
-
|
|
527
|
-
exceptions = exc.args[1]
|
|
528
|
-
else:
|
|
529
|
-
exceptions = exc.exceptions
|
|
530
|
-
for exception in exceptions:
|
|
537
|
+
for exception in exc.exceptions:
|
|
531
538
|
add_single_failure(exception)
|
|
532
539
|
|
|
533
540
|
if max_response_time:
|
|
@@ -577,16 +584,14 @@ class ErrorCollector:
|
|
|
577
584
|
function signatures, which are used by Hypothesis.
|
|
578
585
|
"""
|
|
579
586
|
|
|
580
|
-
errors:
|
|
587
|
+
errors: list[Exception]
|
|
581
588
|
|
|
582
|
-
def __enter__(self) ->
|
|
589
|
+
def __enter__(self) -> ErrorCollector:
|
|
583
590
|
return self
|
|
584
591
|
|
|
585
|
-
# Typing: The return type suggested by mypy is `Literal[False]`, but I don't want to introduce dependency on the
|
|
586
|
-
# `typing_extensions` package for Python 3.7
|
|
587
592
|
def __exit__(
|
|
588
|
-
self, exc_type:
|
|
589
|
-
) ->
|
|
593
|
+
self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
|
|
594
|
+
) -> Literal[False]:
|
|
590
595
|
# Don't do anything special if:
|
|
591
596
|
# - Tests are successful
|
|
592
597
|
# - Checks failed
|
|
@@ -602,7 +607,7 @@ class ErrorCollector:
|
|
|
602
607
|
raise NonCheckError from None
|
|
603
608
|
|
|
604
609
|
|
|
605
|
-
def _force_data_generation_method(values:
|
|
610
|
+
def _force_data_generation_method(values: list[DataGenerationMethod], case: Case) -> None:
|
|
606
611
|
# Set data generation method to the one that actually used
|
|
607
612
|
data_generation_method = cast(DataGenerationMethod, case.data_generation_method)
|
|
608
613
|
values[:] = [data_generation_method]
|
|
@@ -614,16 +619,16 @@ def network_test(
|
|
|
614
619
|
targets: Iterable[Target],
|
|
615
620
|
result: TestResult,
|
|
616
621
|
session: requests.Session,
|
|
617
|
-
request_timeout:
|
|
622
|
+
request_timeout: int | None,
|
|
618
623
|
request_tls_verify: bool,
|
|
619
|
-
request_cert:
|
|
624
|
+
request_cert: RequestCert | None,
|
|
620
625
|
store_interactions: bool,
|
|
621
|
-
headers:
|
|
626
|
+
headers: dict[str, Any] | None,
|
|
622
627
|
feedback: Feedback,
|
|
623
|
-
max_response_time:
|
|
624
|
-
data_generation_methods:
|
|
628
|
+
max_response_time: int | None,
|
|
629
|
+
data_generation_methods: list[DataGenerationMethod],
|
|
625
630
|
dry_run: bool,
|
|
626
|
-
errors:
|
|
631
|
+
errors: list[Exception],
|
|
627
632
|
) -> None:
|
|
628
633
|
"""A single test body will be executed against the target."""
|
|
629
634
|
with ErrorCollector(errors):
|
|
@@ -657,18 +662,18 @@ def _network_test(
|
|
|
657
662
|
targets: Iterable[Target],
|
|
658
663
|
result: TestResult,
|
|
659
664
|
session: requests.Session,
|
|
660
|
-
timeout:
|
|
665
|
+
timeout: float | None,
|
|
661
666
|
store_interactions: bool,
|
|
662
|
-
headers:
|
|
667
|
+
headers: dict[str, Any] | None,
|
|
663
668
|
feedback: Feedback,
|
|
664
669
|
request_tls_verify: bool,
|
|
665
|
-
request_cert:
|
|
666
|
-
max_response_time:
|
|
670
|
+
request_cert: RequestCert | None,
|
|
671
|
+
max_response_time: int | None,
|
|
667
672
|
) -> requests.Response:
|
|
668
|
-
check_results:
|
|
673
|
+
check_results: list[Check] = []
|
|
669
674
|
try:
|
|
670
675
|
hook_context = HookContext(operation=case.operation)
|
|
671
|
-
kwargs:
|
|
676
|
+
kwargs: dict[str, Any] = {
|
|
672
677
|
"session": session,
|
|
673
678
|
"headers": headers,
|
|
674
679
|
"timeout": timeout,
|
|
@@ -711,16 +716,16 @@ def _network_test(
|
|
|
711
716
|
|
|
712
717
|
|
|
713
718
|
@contextmanager
|
|
714
|
-
def get_session(auth:
|
|
719
|
+
def get_session(auth: HTTPDigestAuth | RawAuth | None = None) -> Generator[requests.Session, None, None]:
|
|
715
720
|
with requests.Session() as session:
|
|
716
721
|
if auth is not None:
|
|
717
722
|
session.auth = auth
|
|
718
723
|
yield session
|
|
719
724
|
|
|
720
725
|
|
|
721
|
-
def prepare_timeout(timeout:
|
|
726
|
+
def prepare_timeout(timeout: int | None) -> float | None:
|
|
722
727
|
"""Request timeout is in milliseconds, but `requests` uses seconds."""
|
|
723
|
-
output:
|
|
728
|
+
output: int | float | None = timeout
|
|
724
729
|
if timeout is not None:
|
|
725
730
|
output = timeout / 1000
|
|
726
731
|
return output
|
|
@@ -731,15 +736,15 @@ def wsgi_test(
|
|
|
731
736
|
checks: Iterable[CheckFunction],
|
|
732
737
|
targets: Iterable[Target],
|
|
733
738
|
result: TestResult,
|
|
734
|
-
auth:
|
|
735
|
-
auth_type:
|
|
736
|
-
headers:
|
|
739
|
+
auth: RawAuth | None,
|
|
740
|
+
auth_type: str | None,
|
|
741
|
+
headers: dict[str, Any] | None,
|
|
737
742
|
store_interactions: bool,
|
|
738
743
|
feedback: Feedback,
|
|
739
|
-
max_response_time:
|
|
740
|
-
data_generation_methods:
|
|
744
|
+
max_response_time: int | None,
|
|
745
|
+
data_generation_methods: list[DataGenerationMethod],
|
|
741
746
|
dry_run: bool,
|
|
742
|
-
errors:
|
|
747
|
+
errors: list[Exception],
|
|
743
748
|
) -> None:
|
|
744
749
|
with ErrorCollector(errors):
|
|
745
750
|
_force_data_generation_method(data_generation_methods, case)
|
|
@@ -764,10 +769,10 @@ def _wsgi_test(
|
|
|
764
769
|
checks: Iterable[CheckFunction],
|
|
765
770
|
targets: Iterable[Target],
|
|
766
771
|
result: TestResult,
|
|
767
|
-
headers:
|
|
772
|
+
headers: dict[str, Any],
|
|
768
773
|
store_interactions: bool,
|
|
769
774
|
feedback: Feedback,
|
|
770
|
-
max_response_time:
|
|
775
|
+
max_response_time: int | None,
|
|
771
776
|
) -> WSGIResponse:
|
|
772
777
|
with catching_logs(LogCaptureHandler(), level=logging.DEBUG) as recorded:
|
|
773
778
|
start = time.monotonic()
|
|
@@ -780,7 +785,7 @@ def _wsgi_test(
|
|
|
780
785
|
run_targets(targets, context)
|
|
781
786
|
result.logs.extend(recorded.records)
|
|
782
787
|
status = Status.success
|
|
783
|
-
check_results:
|
|
788
|
+
check_results: list[Check] = []
|
|
784
789
|
try:
|
|
785
790
|
run_checks(
|
|
786
791
|
case=case,
|
|
@@ -802,8 +807,8 @@ def _wsgi_test(
|
|
|
802
807
|
|
|
803
808
|
|
|
804
809
|
def _prepare_wsgi_headers(
|
|
805
|
-
headers:
|
|
806
|
-
) ->
|
|
810
|
+
headers: dict[str, Any] | None, auth: RawAuth | None, auth_type: str | None
|
|
811
|
+
) -> dict[str, Any]:
|
|
807
812
|
headers = headers or {}
|
|
808
813
|
if "user-agent" not in {header.lower() for header in headers}:
|
|
809
814
|
headers["User-Agent"] = USER_AGENT
|
|
@@ -813,7 +818,7 @@ def _prepare_wsgi_headers(
|
|
|
813
818
|
return headers
|
|
814
819
|
|
|
815
820
|
|
|
816
|
-
def get_wsgi_auth(auth:
|
|
821
|
+
def get_wsgi_auth(auth: RawAuth | None, auth_type: str | None) -> str | None:
|
|
817
822
|
if auth:
|
|
818
823
|
if auth_type == "digest":
|
|
819
824
|
raise ValueError("Digest auth is not supported for WSGI apps")
|
|
@@ -827,12 +832,12 @@ def asgi_test(
|
|
|
827
832
|
targets: Iterable[Target],
|
|
828
833
|
result: TestResult,
|
|
829
834
|
store_interactions: bool,
|
|
830
|
-
headers:
|
|
835
|
+
headers: dict[str, Any] | None,
|
|
831
836
|
feedback: Feedback,
|
|
832
|
-
max_response_time:
|
|
833
|
-
data_generation_methods:
|
|
837
|
+
max_response_time: int | None,
|
|
838
|
+
data_generation_methods: list[DataGenerationMethod],
|
|
834
839
|
dry_run: bool,
|
|
835
|
-
errors:
|
|
840
|
+
errors: list[Exception],
|
|
836
841
|
) -> None:
|
|
837
842
|
"""A single test body will be executed against the target."""
|
|
838
843
|
with ErrorCollector(errors):
|
|
@@ -860,18 +865,18 @@ def _asgi_test(
|
|
|
860
865
|
targets: Iterable[Target],
|
|
861
866
|
result: TestResult,
|
|
862
867
|
store_interactions: bool,
|
|
863
|
-
headers:
|
|
868
|
+
headers: dict[str, Any] | None,
|
|
864
869
|
feedback: Feedback,
|
|
865
|
-
max_response_time:
|
|
870
|
+
max_response_time: int | None,
|
|
866
871
|
) -> requests.Response:
|
|
867
872
|
hook_context = HookContext(operation=case.operation)
|
|
868
|
-
kwargs:
|
|
873
|
+
kwargs: dict[str, Any] = {"headers": headers}
|
|
869
874
|
hooks.dispatch("process_call_kwargs", hook_context, case, kwargs)
|
|
870
875
|
response = case.call_asgi(**kwargs)
|
|
871
876
|
context = TargetContext(case=case, response=response, response_time=response.elapsed.total_seconds())
|
|
872
877
|
run_targets(targets, context)
|
|
873
878
|
status = Status.success
|
|
874
|
-
check_results:
|
|
879
|
+
check_results: list[Check] = []
|
|
875
880
|
try:
|
|
876
881
|
run_checks(
|
|
877
882
|
case=case,
|
schemathesis/runner/impl/solo.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
1
2
|
import threading
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from typing import Generator
|
|
4
|
+
from typing import Generator
|
|
4
5
|
|
|
5
6
|
from ...models import TestResultSet
|
|
6
7
|
from ...types import RequestCert
|
|
@@ -13,8 +14,8 @@ from .core import BaseRunner, asgi_test, get_session, network_test, wsgi_test
|
|
|
13
14
|
class SingleThreadRunner(BaseRunner):
|
|
14
15
|
"""Fast runner that runs tests sequentially in the main thread."""
|
|
15
16
|
|
|
16
|
-
request_tls_verify:
|
|
17
|
-
request_cert:
|
|
17
|
+
request_tls_verify: bool | str = True
|
|
18
|
+
request_cert: RequestCert | None = None
|
|
18
19
|
|
|
19
20
|
def _execute(
|
|
20
21
|
self, results: TestResultSet, stop_event: threading.Event
|
|
@@ -28,10 +29,11 @@ class SingleThreadRunner(BaseRunner):
|
|
|
28
29
|
auth = get_requests_auth(self.auth, self.auth_type)
|
|
29
30
|
with get_session(auth) as session:
|
|
30
31
|
yield from self._run_tests(
|
|
31
|
-
self.schema.get_all_tests,
|
|
32
|
-
network_test,
|
|
33
|
-
self.hypothesis_settings,
|
|
34
|
-
self.
|
|
32
|
+
maker=self.schema.get_all_tests,
|
|
33
|
+
template=network_test,
|
|
34
|
+
settings=self.hypothesis_settings,
|
|
35
|
+
generation_config=self.generation_config,
|
|
36
|
+
seed=self.seed,
|
|
35
37
|
checks=self.checks,
|
|
36
38
|
max_response_time=self.max_response_time,
|
|
37
39
|
targets=self.targets,
|
|
@@ -50,10 +52,11 @@ class SingleThreadRunner(BaseRunner):
|
|
|
50
52
|
class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
51
53
|
def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
|
|
52
54
|
yield from self._run_tests(
|
|
53
|
-
self.schema.get_all_tests,
|
|
54
|
-
wsgi_test,
|
|
55
|
-
self.hypothesis_settings,
|
|
56
|
-
self.
|
|
55
|
+
maker=self.schema.get_all_tests,
|
|
56
|
+
template=wsgi_test,
|
|
57
|
+
settings=self.hypothesis_settings,
|
|
58
|
+
generation_config=self.generation_config,
|
|
59
|
+
seed=self.seed,
|
|
57
60
|
checks=self.checks,
|
|
58
61
|
max_response_time=self.max_response_time,
|
|
59
62
|
targets=self.targets,
|
|
@@ -70,10 +73,11 @@ class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
|
70
73
|
class SingleThreadASGIRunner(SingleThreadRunner):
|
|
71
74
|
def _execute_impl(self, results: TestResultSet) -> Generator[events.ExecutionEvent, None, None]:
|
|
72
75
|
yield from self._run_tests(
|
|
73
|
-
self.schema.get_all_tests,
|
|
74
|
-
asgi_test,
|
|
75
|
-
self.hypothesis_settings,
|
|
76
|
-
self.
|
|
76
|
+
maker=self.schema.get_all_tests,
|
|
77
|
+
template=asgi_test,
|
|
78
|
+
settings=self.hypothesis_settings,
|
|
79
|
+
generation_config=self.generation_config,
|
|
80
|
+
seed=self.seed,
|
|
77
81
|
checks=self.checks,
|
|
78
82
|
max_response_time=self.max_response_time,
|
|
79
83
|
targets=self.targets,
|