schemathesis 3.35.4__py3-none-any.whl → 3.35.5__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 +5 -5
- schemathesis/_hypothesis.py +12 -6
- schemathesis/_override.py +4 -4
- schemathesis/auths.py +1 -1
- schemathesis/cli/__init__.py +19 -13
- schemathesis/cli/callbacks.py +6 -4
- schemathesis/cli/cassettes.py +67 -41
- schemathesis/cli/context.py +7 -6
- schemathesis/cli/junitxml.py +1 -1
- schemathesis/cli/options.py +7 -4
- schemathesis/cli/output/default.py +5 -5
- schemathesis/cli/reporting.py +4 -2
- schemathesis/code_samples.py +4 -3
- schemathesis/exceptions.py +4 -3
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/pytest_plugin.py +6 -3
- schemathesis/failures.py +2 -1
- schemathesis/filters.py +2 -2
- schemathesis/generation/__init__.py +2 -2
- schemathesis/generation/_hypothesis.py +1 -1
- schemathesis/generation/coverage.py +5 -5
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +3 -3
- schemathesis/lazy.py +10 -7
- schemathesis/loaders.py +3 -3
- schemathesis/models.py +39 -15
- schemathesis/runner/__init__.py +5 -5
- schemathesis/runner/events.py +1 -1
- schemathesis/runner/impl/context.py +58 -0
- schemathesis/runner/impl/core.py +54 -61
- schemathesis/runner/impl/solo.py +17 -20
- schemathesis/runner/impl/threadpool.py +65 -71
- schemathesis/runner/serialization.py +4 -3
- schemathesis/sanitization.py +2 -1
- schemathesis/schemas.py +18 -20
- schemathesis/serializers.py +2 -0
- schemathesis/service/client.py +1 -1
- schemathesis/service/events.py +4 -1
- schemathesis/service/extensions.py +2 -2
- schemathesis/service/hosts.py +4 -2
- schemathesis/service/models.py +3 -3
- schemathesis/service/report.py +3 -3
- schemathesis/service/serialization.py +4 -2
- schemathesis/specs/graphql/loaders.py +4 -3
- schemathesis/specs/graphql/schemas.py +4 -3
- schemathesis/specs/openapi/definitions.py +1 -5
- schemathesis/specs/openapi/examples.py +92 -2
- schemathesis/specs/openapi/expressions/__init__.py +7 -0
- schemathesis/specs/openapi/expressions/extractors.py +4 -1
- schemathesis/specs/openapi/expressions/nodes.py +5 -3
- schemathesis/specs/openapi/links.py +4 -4
- schemathesis/specs/openapi/loaders.py +5 -4
- schemathesis/specs/openapi/negative/__init__.py +5 -3
- schemathesis/specs/openapi/negative/mutations.py +5 -4
- schemathesis/specs/openapi/parameters.py +4 -2
- schemathesis/specs/openapi/schemas.py +9 -10
- schemathesis/specs/openapi/security.py +6 -4
- schemathesis/specs/openapi/stateful/__init__.py +2 -2
- schemathesis/specs/openapi/stateful/statistic.py +3 -3
- schemathesis/specs/openapi/stateful/types.py +3 -2
- schemathesis/stateful/__init__.py +3 -3
- schemathesis/stateful/config.py +1 -1
- schemathesis/stateful/context.py +3 -3
- schemathesis/stateful/events.py +3 -3
- schemathesis/stateful/runner.py +5 -4
- schemathesis/stateful/sink.py +1 -1
- schemathesis/stateful/state_machine.py +5 -5
- schemathesis/stateful/statistic.py +3 -1
- schemathesis/stateful/validation.py +1 -1
- schemathesis/transports/__init__.py +2 -2
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +2 -1
- schemathesis/transports/content_types.py +1 -1
- schemathesis/transports/responses.py +2 -1
- schemathesis/utils.py +4 -2
- {schemathesis-3.35.4.dist-info → schemathesis-3.35.5.dist-info}/METADATA +1 -1
- schemathesis-3.35.5.dist-info/RECORD +156 -0
- schemathesis-3.35.4.dist-info/RECORD +0 -154
- {schemathesis-3.35.4.dist-info → schemathesis-3.35.5.dist-info}/WHEEL +0 -0
- {schemathesis-3.35.4.dist-info → schemathesis-3.35.5.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.35.4.dist-info → schemathesis-3.35.5.dist-info}/licenses/LICENSE +0 -0
schemathesis/runner/impl/core.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import functools
|
|
3
4
|
import logging
|
|
5
|
+
import operator
|
|
4
6
|
import re
|
|
5
7
|
import threading
|
|
6
8
|
import time
|
|
@@ -9,7 +11,6 @@ import uuid
|
|
|
9
11
|
import warnings
|
|
10
12
|
from contextlib import contextmanager
|
|
11
13
|
from dataclasses import dataclass, field
|
|
12
|
-
from types import TracebackType
|
|
13
14
|
from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, List, Literal, cast
|
|
14
15
|
from warnings import WarningMessage, catch_warnings
|
|
15
16
|
|
|
@@ -20,7 +21,6 @@ from hypothesis.errors import HypothesisException, InvalidArgument
|
|
|
20
21
|
from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
|
|
21
22
|
from jsonschema.exceptions import SchemaError as JsonSchemaError
|
|
22
23
|
from jsonschema.exceptions import ValidationError
|
|
23
|
-
from requests.auth import HTTPDigestAuth
|
|
24
24
|
from urllib3.exceptions import InsecureRequestWarning
|
|
25
25
|
|
|
26
26
|
from ... import experimental, failures, hooks
|
|
@@ -31,7 +31,6 @@ from ..._hypothesis import (
|
|
|
31
31
|
get_non_serializable_mark,
|
|
32
32
|
has_unsatisfied_example_mark,
|
|
33
33
|
)
|
|
34
|
-
from ..._override import CaseOverride
|
|
35
34
|
from ...auths import unregister as unregister_auth
|
|
36
35
|
from ...checks import _make_max_response_time_failure_message
|
|
37
36
|
from ...constants import (
|
|
@@ -59,9 +58,8 @@ from ...generation import DataGenerationMethod, GenerationConfig
|
|
|
59
58
|
from ...hooks import HookContext, get_all_by_name
|
|
60
59
|
from ...internal.datetime import current_datetime
|
|
61
60
|
from ...internal.result import Err, Ok, Result
|
|
62
|
-
from ...models import APIOperation, Case, Check, CheckFunction, Status, TestResult
|
|
61
|
+
from ...models import APIOperation, Case, Check, CheckFunction, Status, TestResult
|
|
63
62
|
from ...runner import events
|
|
64
|
-
from ...schemas import BaseSchema
|
|
65
63
|
from ...service import extensions
|
|
66
64
|
from ...service.models import AnalysisResult, AnalysisSuccess
|
|
67
65
|
from ...specs.openapi import formats
|
|
@@ -71,12 +69,17 @@ from ...stateful import runner as stateful_runner
|
|
|
71
69
|
from ...targets import Target, TargetContext
|
|
72
70
|
from ...transports import RequestConfig, RequestsTransport
|
|
73
71
|
from ...transports.auth import get_requests_auth, prepare_wsgi_headers
|
|
74
|
-
from ...types import RawAuth
|
|
75
72
|
from ...utils import capture_hypothesis_output
|
|
76
73
|
from .. import probes
|
|
77
74
|
from ..serialization import SerializedTestResult
|
|
75
|
+
from .context import RunnerContext
|
|
78
76
|
|
|
79
77
|
if TYPE_CHECKING:
|
|
78
|
+
from ...types import RawAuth
|
|
79
|
+
from ...schemas import BaseSchema
|
|
80
|
+
from ..._override import CaseOverride
|
|
81
|
+
from requests.auth import HTTPDigestAuth
|
|
82
|
+
from types import TracebackType
|
|
80
83
|
from ...service.client import ServiceClient
|
|
81
84
|
from ...transports.responses import GenericResponse, WSGIResponse
|
|
82
85
|
|
|
@@ -122,7 +125,7 @@ class BaseRunner:
|
|
|
122
125
|
# If auth is explicitly provided, then the global provider is ignored
|
|
123
126
|
if self.auth is not None:
|
|
124
127
|
unregister_auth()
|
|
125
|
-
|
|
128
|
+
ctx = RunnerContext(self.seed, stop_event)
|
|
126
129
|
start_time = time.monotonic()
|
|
127
130
|
initialized = None
|
|
128
131
|
__probes = None
|
|
@@ -134,15 +137,15 @@ class BaseRunner:
|
|
|
134
137
|
schema=self.schema,
|
|
135
138
|
count_operations=self.count_operations,
|
|
136
139
|
count_links=self.count_links,
|
|
137
|
-
seed=
|
|
140
|
+
seed=ctx.seed,
|
|
138
141
|
start_time=start_time,
|
|
139
142
|
)
|
|
140
143
|
return initialized
|
|
141
144
|
|
|
142
145
|
def _finish() -> events.Finished:
|
|
143
|
-
if has_all_not_found
|
|
144
|
-
|
|
145
|
-
return events.Finished.from_results(results=
|
|
146
|
+
if ctx.has_all_not_found:
|
|
147
|
+
ctx.add_warning(ALL_NOT_FOUND_WARNING_MESSAGE)
|
|
148
|
+
return events.Finished.from_results(results=ctx.data, running_time=time.monotonic() - start_time)
|
|
146
149
|
|
|
147
150
|
def _before_probes() -> events.BeforeProbing:
|
|
148
151
|
return events.BeforeProbing()
|
|
@@ -176,7 +179,7 @@ class BaseRunner:
|
|
|
176
179
|
def _after_analysis() -> events.AfterAnalysis:
|
|
177
180
|
return events.AfterAnalysis(analysis=__analysis)
|
|
178
181
|
|
|
179
|
-
if
|
|
182
|
+
if ctx.is_stopped:
|
|
180
183
|
yield _finish()
|
|
181
184
|
return
|
|
182
185
|
|
|
@@ -192,16 +195,16 @@ class BaseRunner:
|
|
|
192
195
|
event = event_factory()
|
|
193
196
|
if event is not None:
|
|
194
197
|
yield event
|
|
195
|
-
if
|
|
196
|
-
yield _finish()
|
|
198
|
+
if ctx.is_stopped:
|
|
199
|
+
yield _finish() # type: ignore[unreachable]
|
|
197
200
|
return
|
|
198
201
|
|
|
199
202
|
try:
|
|
200
203
|
warnings.simplefilter("ignore", InsecureRequestWarning)
|
|
201
204
|
if not experimental.STATEFUL_ONLY.is_enabled:
|
|
202
|
-
yield from self._execute(
|
|
205
|
+
yield from self._execute(ctx)
|
|
203
206
|
if not self._is_stopping_due_to_failure_limit:
|
|
204
|
-
yield from self._run_stateful_tests(
|
|
207
|
+
yield from self._run_stateful_tests(ctx)
|
|
205
208
|
except KeyboardInterrupt:
|
|
206
209
|
yield events.Interrupted()
|
|
207
210
|
|
|
@@ -222,19 +225,17 @@ class BaseRunner:
|
|
|
222
225
|
return self._failures_counter >= self.max_failures
|
|
223
226
|
return False
|
|
224
227
|
|
|
225
|
-
def _execute(
|
|
226
|
-
self, results: TestResultSet, stop_event: threading.Event
|
|
227
|
-
) -> Generator[events.ExecutionEvent, None, None]:
|
|
228
|
+
def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
228
229
|
raise NotImplementedError
|
|
229
230
|
|
|
230
|
-
def _run_stateful_tests(self,
|
|
231
|
+
def _run_stateful_tests(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
231
232
|
# Run new-style stateful tests
|
|
232
233
|
if self.stateful is not None and experimental.STATEFUL_TEST_RUNNER.is_enabled and self.schema.links_count > 0:
|
|
233
234
|
result = TestResult(
|
|
234
235
|
method="",
|
|
235
236
|
path="",
|
|
236
237
|
verbose_name="Stateful tests",
|
|
237
|
-
seed=
|
|
238
|
+
seed=ctx.seed,
|
|
238
239
|
data_generation_method=self.schema.data_generation_methods,
|
|
239
240
|
)
|
|
240
241
|
headers = self.headers or {}
|
|
@@ -251,7 +252,7 @@ class BaseRunner:
|
|
|
251
252
|
max_failures=None if self.max_failures is None else self.max_failures - self._failures_counter,
|
|
252
253
|
request=self.request_config,
|
|
253
254
|
auth=auth,
|
|
254
|
-
seed=
|
|
255
|
+
seed=ctx.seed,
|
|
255
256
|
override=self.override,
|
|
256
257
|
)
|
|
257
258
|
state_machine = self.schema.as_state_machine()
|
|
@@ -277,6 +278,8 @@ class BaseRunner:
|
|
|
277
278
|
case=event.case,
|
|
278
279
|
response=response,
|
|
279
280
|
checks=event.checks,
|
|
281
|
+
headers=headers,
|
|
282
|
+
session=None,
|
|
280
283
|
)
|
|
281
284
|
|
|
282
285
|
else:
|
|
@@ -317,7 +320,7 @@ class BaseRunner:
|
|
|
317
320
|
status = Status.error
|
|
318
321
|
result.add_error(stateful_event.exception)
|
|
319
322
|
yield events.StatefulEvent(data=stateful_event)
|
|
320
|
-
|
|
323
|
+
ctx.add_result(result)
|
|
321
324
|
yield events.AfterStatefulExecution(
|
|
322
325
|
status=status,
|
|
323
326
|
result=SerializedTestResult.from_test_result(result),
|
|
@@ -328,11 +331,10 @@ class BaseRunner:
|
|
|
328
331
|
def _run_tests(
|
|
329
332
|
self,
|
|
330
333
|
maker: Callable,
|
|
331
|
-
|
|
334
|
+
test_func: Callable,
|
|
332
335
|
settings: hypothesis.settings,
|
|
333
336
|
generation_config: GenerationConfig,
|
|
334
|
-
|
|
335
|
-
results: TestResultSet,
|
|
337
|
+
ctx: RunnerContext,
|
|
336
338
|
recursion_level: int = 0,
|
|
337
339
|
headers: dict[str, Any] | None = None,
|
|
338
340
|
**kwargs: Any,
|
|
@@ -352,10 +354,10 @@ class BaseRunner:
|
|
|
352
354
|
return kw
|
|
353
355
|
|
|
354
356
|
for result in maker(
|
|
355
|
-
|
|
357
|
+
test_func,
|
|
356
358
|
settings=settings,
|
|
357
359
|
generation_config=generation_config,
|
|
358
|
-
seed=seed,
|
|
360
|
+
seed=ctx.seed,
|
|
359
361
|
as_strategy_kwargs=as_strategy_kwargs,
|
|
360
362
|
):
|
|
361
363
|
if isinstance(result, Ok):
|
|
@@ -372,7 +374,7 @@ class BaseRunner:
|
|
|
372
374
|
for event in run_test(
|
|
373
375
|
operation,
|
|
374
376
|
test,
|
|
375
|
-
|
|
377
|
+
ctx=ctx,
|
|
376
378
|
feedback=feedback,
|
|
377
379
|
recursion_level=recursion_level,
|
|
378
380
|
data_generation_methods=self.schema.data_generation_methods,
|
|
@@ -388,28 +390,25 @@ class BaseRunner:
|
|
|
388
390
|
if feedback is not None:
|
|
389
391
|
yield from self._run_tests(
|
|
390
392
|
feedback.get_stateful_tests,
|
|
391
|
-
|
|
393
|
+
test_func,
|
|
392
394
|
settings=settings,
|
|
393
395
|
generation_config=generation_config,
|
|
394
|
-
seed=seed,
|
|
395
396
|
recursion_level=recursion_level + 1,
|
|
396
|
-
|
|
397
|
+
ctx=ctx,
|
|
397
398
|
headers=headers,
|
|
398
399
|
**kwargs,
|
|
399
400
|
)
|
|
400
401
|
except OperationSchemaError as exc:
|
|
401
402
|
yield from handle_schema_error(
|
|
402
403
|
exc,
|
|
403
|
-
|
|
404
|
+
ctx,
|
|
404
405
|
self.schema.data_generation_methods,
|
|
405
406
|
recursion_level,
|
|
406
407
|
before_execution_correlation_id=before_execution_correlation_id,
|
|
407
408
|
)
|
|
408
409
|
else:
|
|
409
410
|
# Schema errors
|
|
410
|
-
yield from handle_schema_error(
|
|
411
|
-
result.err(), results, self.schema.data_generation_methods, recursion_level
|
|
412
|
-
)
|
|
411
|
+
yield from handle_schema_error(result.err(), ctx, self.schema.data_generation_methods, recursion_level)
|
|
413
412
|
|
|
414
413
|
|
|
415
414
|
def run_probes(schema: BaseSchema, config: probes.ProbeConfig) -> list[probes.ProbeRun]:
|
|
@@ -454,7 +453,7 @@ class EventStream:
|
|
|
454
453
|
|
|
455
454
|
def handle_schema_error(
|
|
456
455
|
error: OperationSchemaError,
|
|
457
|
-
|
|
456
|
+
ctx: RunnerContext,
|
|
458
457
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
459
458
|
recursion_level: int,
|
|
460
459
|
*,
|
|
@@ -499,11 +498,11 @@ def handle_schema_error(
|
|
|
499
498
|
hypothesis_output=[],
|
|
500
499
|
correlation_id=correlation_id,
|
|
501
500
|
)
|
|
502
|
-
|
|
501
|
+
ctx.add_result(result)
|
|
503
502
|
else:
|
|
504
503
|
# When there is no `method`, then the schema error may cover multiple operations, and we can't display it in
|
|
505
504
|
# the progress bar
|
|
506
|
-
|
|
505
|
+
ctx.add_generic_error(error)
|
|
507
506
|
|
|
508
507
|
|
|
509
508
|
def run_test(
|
|
@@ -512,7 +511,7 @@ def run_test(
|
|
|
512
511
|
checks: Iterable[CheckFunction],
|
|
513
512
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
514
513
|
targets: Iterable[Target],
|
|
515
|
-
|
|
514
|
+
ctx: RunnerContext,
|
|
516
515
|
headers: dict[str, Any] | None,
|
|
517
516
|
recursion_level: int,
|
|
518
517
|
**kwargs: Any,
|
|
@@ -689,10 +688,10 @@ def run_test(
|
|
|
689
688
|
result.seed = getattr(test, "_hypothesis_internal_use_seed", None) or getattr(
|
|
690
689
|
test, "_hypothesis_internal_use_generated_seed", None
|
|
691
690
|
)
|
|
692
|
-
|
|
691
|
+
ctx.add_result(result)
|
|
693
692
|
for status_code in (401, 403):
|
|
694
693
|
if has_too_many_responses_with_status(result, status_code):
|
|
695
|
-
|
|
694
|
+
ctx.add_warning(TOO_MANY_RESPONSES_WARNING_TEMPLATE.format(f"`{operation.verbose_name}`", status_code))
|
|
696
695
|
yield events.AfterExecution.from_result(
|
|
697
696
|
result=result,
|
|
698
697
|
status=status,
|
|
@@ -727,22 +726,6 @@ def has_too_many_responses_with_status(result: TestResult, status_code: int) ->
|
|
|
727
726
|
ALL_NOT_FOUND_WARNING_MESSAGE = "All API responses have a 404 status code. Did you specify the proper API location?"
|
|
728
727
|
|
|
729
728
|
|
|
730
|
-
def has_all_not_found(results: TestResultSet) -> bool:
|
|
731
|
-
"""Check if all responses are 404."""
|
|
732
|
-
has_not_found = False
|
|
733
|
-
for result in results.results:
|
|
734
|
-
for check in result.checks:
|
|
735
|
-
if check.response is not None:
|
|
736
|
-
if check.response.status_code == 404:
|
|
737
|
-
has_not_found = True
|
|
738
|
-
else:
|
|
739
|
-
# There are non-404 responses, no reason to check any other response
|
|
740
|
-
return False
|
|
741
|
-
# Only happens if all responses are 404, or there are no responses at all.
|
|
742
|
-
# In the first case, it returns True, for the latter - False
|
|
743
|
-
return has_not_found
|
|
744
|
-
|
|
745
|
-
|
|
746
729
|
def setup_hypothesis_database_key(test: Callable, operation: APIOperation) -> None:
|
|
747
730
|
"""Make Hypothesis use separate database entries for every API operation.
|
|
748
731
|
|
|
@@ -779,7 +762,9 @@ def group_errors(errors: list[Exception]) -> None:
|
|
|
779
762
|
serialization_errors = [error for error in errors if isinstance(error, SerializationNotPossible)]
|
|
780
763
|
if len(serialization_errors) > 1:
|
|
781
764
|
errors[:] = [error for error in errors if not isinstance(error, SerializationNotPossible)]
|
|
782
|
-
media_types =
|
|
765
|
+
media_types: list[str] = functools.reduce(
|
|
766
|
+
operator.iadd, (entry.media_types for entry in serialization_errors), []
|
|
767
|
+
)
|
|
783
768
|
errors.append(SerializationNotPossible.from_media_types(*media_types))
|
|
784
769
|
|
|
785
770
|
|
|
@@ -948,6 +933,8 @@ def network_test(
|
|
|
948
933
|
)
|
|
949
934
|
response = _network_test(case, *args)
|
|
950
935
|
add_cases(case, response, _network_test, *args)
|
|
936
|
+
elif store_interactions:
|
|
937
|
+
result.store_requests_response(case, None, Status.skip, [], headers=headers, session=session)
|
|
951
938
|
|
|
952
939
|
|
|
953
940
|
def _network_test(
|
|
@@ -987,6 +974,8 @@ def _network_test(
|
|
|
987
974
|
check_name, case, None, elapsed, f"Response timed out after {1000 * elapsed:.2f}ms", exc.context, request
|
|
988
975
|
)
|
|
989
976
|
check_results.append(check_result)
|
|
977
|
+
if store_interactions:
|
|
978
|
+
result.store_requests_response(case, None, Status.failure, [check_result], headers=headers, session=session)
|
|
990
979
|
raise exc
|
|
991
980
|
context = TargetContext(case=case, response=response, response_time=response.elapsed.total_seconds())
|
|
992
981
|
run_targets(targets, context)
|
|
@@ -1008,7 +997,7 @@ def _network_test(
|
|
|
1008
997
|
if feedback is not None:
|
|
1009
998
|
feedback.add_test_case(case, response)
|
|
1010
999
|
if store_interactions:
|
|
1011
|
-
result.store_requests_response(case, response, status, check_results)
|
|
1000
|
+
result.store_requests_response(case, response, status, check_results, headers=headers, session=session)
|
|
1012
1001
|
return response
|
|
1013
1002
|
|
|
1014
1003
|
|
|
@@ -1051,6 +1040,8 @@ def wsgi_test(
|
|
|
1051
1040
|
)
|
|
1052
1041
|
response = _wsgi_test(case, *args)
|
|
1053
1042
|
add_cases(case, response, _wsgi_test, *args)
|
|
1043
|
+
elif store_interactions:
|
|
1044
|
+
result.store_wsgi_response(case, None, headers, None, Status.skip, [])
|
|
1054
1045
|
|
|
1055
1046
|
|
|
1056
1047
|
def _wsgi_test(
|
|
@@ -1127,6 +1118,8 @@ def asgi_test(
|
|
|
1127
1118
|
)
|
|
1128
1119
|
response = _asgi_test(case, *args)
|
|
1129
1120
|
add_cases(case, response, _asgi_test, *args)
|
|
1121
|
+
elif store_interactions:
|
|
1122
|
+
result.store_requests_response(case, None, Status.skip, [], headers=headers, session=None)
|
|
1130
1123
|
|
|
1131
1124
|
|
|
1132
1125
|
def _asgi_test(
|
|
@@ -1164,5 +1157,5 @@ def _asgi_test(
|
|
|
1164
1157
|
if feedback is not None:
|
|
1165
1158
|
feedback.add_test_case(case, response)
|
|
1166
1159
|
if store_interactions:
|
|
1167
|
-
result.store_requests_response(case, response, status, check_results)
|
|
1160
|
+
result.store_requests_response(case, response, status, check_results, headers, session=None)
|
|
1168
1161
|
return response
|
schemathesis/runner/impl/solo.py
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import threading
|
|
4
3
|
from dataclasses import dataclass
|
|
5
|
-
from typing import Generator
|
|
4
|
+
from typing import TYPE_CHECKING, Generator
|
|
6
5
|
|
|
7
|
-
from ...models import TestResultSet
|
|
8
6
|
from ...transports.auth import get_requests_auth
|
|
9
7
|
from .. import events
|
|
10
8
|
from .core import BaseRunner, asgi_test, get_session, network_test, wsgi_test
|
|
11
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from .context import RunnerContext
|
|
12
|
+
from .. import events
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
@dataclass
|
|
14
16
|
class SingleThreadRunner(BaseRunner):
|
|
15
17
|
"""Fast runner that runs tests sequentially in the main thread."""
|
|
16
18
|
|
|
17
|
-
def _execute(
|
|
18
|
-
|
|
19
|
-
) -> Generator[events.ExecutionEvent, None, None]:
|
|
20
|
-
for event in self._execute_impl(results):
|
|
19
|
+
def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
20
|
+
for event in self._execute_impl(ctx):
|
|
21
21
|
yield event
|
|
22
|
-
if
|
|
22
|
+
if ctx.is_stopped or self._should_stop(event):
|
|
23
23
|
break
|
|
24
24
|
|
|
25
|
-
def _execute_impl(self,
|
|
25
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
26
26
|
auth = get_requests_auth(self.auth, self.auth_type)
|
|
27
27
|
with get_session(auth) as session:
|
|
28
28
|
yield from self._run_tests(
|
|
29
29
|
maker=self.schema.get_all_tests,
|
|
30
|
-
|
|
30
|
+
test_func=network_test,
|
|
31
31
|
settings=self.hypothesis_settings,
|
|
32
32
|
generation_config=self.generation_config,
|
|
33
|
-
seed=self.seed,
|
|
34
33
|
checks=self.checks,
|
|
35
34
|
max_response_time=self.max_response_time,
|
|
36
35
|
targets=self.targets,
|
|
37
|
-
|
|
36
|
+
ctx=ctx,
|
|
38
37
|
session=session,
|
|
39
38
|
headers=self.headers,
|
|
40
39
|
request_config=self.request_config,
|
|
@@ -45,17 +44,16 @@ class SingleThreadRunner(BaseRunner):
|
|
|
45
44
|
|
|
46
45
|
@dataclass
|
|
47
46
|
class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
48
|
-
def _execute_impl(self,
|
|
47
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
49
48
|
yield from self._run_tests(
|
|
50
49
|
maker=self.schema.get_all_tests,
|
|
51
|
-
|
|
50
|
+
test_func=wsgi_test,
|
|
52
51
|
settings=self.hypothesis_settings,
|
|
53
52
|
generation_config=self.generation_config,
|
|
54
|
-
seed=self.seed,
|
|
55
53
|
checks=self.checks,
|
|
56
54
|
max_response_time=self.max_response_time,
|
|
57
55
|
targets=self.targets,
|
|
58
|
-
|
|
56
|
+
ctx=ctx,
|
|
59
57
|
auth=self.auth,
|
|
60
58
|
auth_type=self.auth_type,
|
|
61
59
|
headers=self.headers,
|
|
@@ -66,17 +64,16 @@ class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
|
66
64
|
|
|
67
65
|
@dataclass
|
|
68
66
|
class SingleThreadASGIRunner(SingleThreadRunner):
|
|
69
|
-
def _execute_impl(self,
|
|
67
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
70
68
|
yield from self._run_tests(
|
|
71
69
|
maker=self.schema.get_all_tests,
|
|
72
|
-
|
|
70
|
+
test_func=asgi_test,
|
|
73
71
|
settings=self.hypothesis_settings,
|
|
74
72
|
generation_config=self.generation_config,
|
|
75
|
-
seed=self.seed,
|
|
76
73
|
checks=self.checks,
|
|
77
74
|
max_response_time=self.max_response_time,
|
|
78
75
|
targets=self.targets,
|
|
79
|
-
|
|
76
|
+
ctx=ctx,
|
|
80
77
|
headers=self.headers,
|
|
81
78
|
store_interactions=self.store_interactions,
|
|
82
79
|
dry_run=self.dry_run,
|