schemathesis 3.25.6__py3-none-any.whl → 3.39.7__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 +6 -6
- schemathesis/_compat.py +2 -2
- schemathesis/_dependency_versions.py +4 -2
- schemathesis/_hypothesis.py +369 -56
- schemathesis/_lazy_import.py +1 -0
- schemathesis/_override.py +5 -4
- schemathesis/_patches.py +21 -0
- schemathesis/_rate_limiter.py +7 -0
- schemathesis/_xml.py +75 -22
- schemathesis/auths.py +78 -16
- schemathesis/checks.py +21 -9
- schemathesis/cli/__init__.py +783 -432
- schemathesis/cli/__main__.py +4 -0
- schemathesis/cli/callbacks.py +58 -13
- schemathesis/cli/cassettes.py +233 -47
- schemathesis/cli/constants.py +8 -2
- schemathesis/cli/context.py +22 -5
- schemathesis/cli/debug.py +2 -1
- schemathesis/cli/handlers.py +4 -1
- schemathesis/cli/junitxml.py +103 -22
- schemathesis/cli/options.py +15 -4
- schemathesis/cli/output/default.py +258 -112
- schemathesis/cli/output/short.py +23 -8
- schemathesis/cli/reporting.py +79 -0
- schemathesis/cli/sanitization.py +6 -0
- schemathesis/code_samples.py +5 -3
- schemathesis/constants.py +1 -0
- schemathesis/contrib/openapi/__init__.py +1 -1
- schemathesis/contrib/openapi/fill_missing_examples.py +3 -1
- schemathesis/contrib/openapi/formats/uuid.py +2 -1
- schemathesis/contrib/unique_data.py +3 -3
- schemathesis/exceptions.py +76 -65
- schemathesis/experimental/__init__.py +35 -0
- schemathesis/extra/_aiohttp.py +1 -0
- schemathesis/extra/_flask.py +4 -1
- schemathesis/extra/_server.py +1 -0
- schemathesis/extra/pytest_plugin.py +17 -25
- schemathesis/failures.py +77 -9
- schemathesis/filters.py +185 -8
- schemathesis/fixups/__init__.py +1 -0
- schemathesis/fixups/fast_api.py +2 -2
- schemathesis/fixups/utf8_bom.py +1 -2
- schemathesis/generation/__init__.py +20 -36
- schemathesis/generation/_hypothesis.py +59 -0
- schemathesis/generation/_methods.py +44 -0
- schemathesis/generation/coverage.py +931 -0
- schemathesis/graphql.py +0 -1
- schemathesis/hooks.py +89 -12
- schemathesis/internal/checks.py +84 -0
- schemathesis/internal/copy.py +22 -3
- schemathesis/internal/deprecation.py +6 -2
- schemathesis/internal/diff.py +15 -0
- schemathesis/internal/extensions.py +27 -0
- schemathesis/internal/jsonschema.py +2 -1
- schemathesis/internal/output.py +68 -0
- schemathesis/internal/result.py +1 -1
- schemathesis/internal/transformation.py +11 -0
- schemathesis/lazy.py +138 -25
- schemathesis/loaders.py +7 -5
- schemathesis/models.py +318 -211
- schemathesis/parameters.py +4 -0
- schemathesis/runner/__init__.py +50 -15
- schemathesis/runner/events.py +65 -5
- schemathesis/runner/impl/context.py +104 -0
- schemathesis/runner/impl/core.py +388 -177
- schemathesis/runner/impl/solo.py +19 -29
- schemathesis/runner/impl/threadpool.py +70 -79
- schemathesis/runner/probes.py +11 -9
- schemathesis/runner/serialization.py +150 -17
- schemathesis/sanitization.py +5 -1
- schemathesis/schemas.py +170 -102
- schemathesis/serializers.py +7 -2
- schemathesis/service/ci.py +1 -0
- schemathesis/service/client.py +39 -6
- schemathesis/service/events.py +5 -1
- schemathesis/service/extensions.py +224 -0
- schemathesis/service/hosts.py +6 -2
- schemathesis/service/metadata.py +25 -0
- schemathesis/service/models.py +211 -2
- schemathesis/service/report.py +6 -6
- schemathesis/service/serialization.py +45 -71
- schemathesis/service/usage.py +1 -0
- schemathesis/specs/graphql/_cache.py +26 -0
- schemathesis/specs/graphql/loaders.py +25 -5
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +2 -2
- schemathesis/specs/graphql/schemas.py +130 -100
- schemathesis/specs/graphql/validation.py +1 -2
- schemathesis/specs/openapi/__init__.py +1 -0
- schemathesis/specs/openapi/_cache.py +123 -0
- schemathesis/specs/openapi/_hypothesis.py +78 -60
- schemathesis/specs/openapi/checks.py +504 -25
- schemathesis/specs/openapi/converter.py +31 -4
- schemathesis/specs/openapi/definitions.py +10 -17
- schemathesis/specs/openapi/examples.py +126 -12
- schemathesis/specs/openapi/expressions/__init__.py +37 -2
- schemathesis/specs/openapi/expressions/context.py +1 -1
- schemathesis/specs/openapi/expressions/extractors.py +26 -0
- schemathesis/specs/openapi/expressions/lexer.py +20 -18
- schemathesis/specs/openapi/expressions/nodes.py +29 -6
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/formats.py +44 -0
- schemathesis/specs/openapi/links.py +125 -42
- schemathesis/specs/openapi/loaders.py +77 -36
- schemathesis/specs/openapi/media_types.py +34 -0
- schemathesis/specs/openapi/negative/__init__.py +6 -3
- schemathesis/specs/openapi/negative/mutations.py +21 -6
- schemathesis/specs/openapi/parameters.py +39 -25
- schemathesis/specs/openapi/patterns.py +137 -0
- schemathesis/specs/openapi/references.py +37 -7
- schemathesis/specs/openapi/schemas.py +360 -241
- schemathesis/specs/openapi/security.py +25 -7
- schemathesis/specs/openapi/serialization.py +1 -0
- schemathesis/specs/openapi/stateful/__init__.py +198 -70
- schemathesis/specs/openapi/stateful/statistic.py +198 -0
- schemathesis/specs/openapi/stateful/types.py +14 -0
- schemathesis/specs/openapi/utils.py +6 -1
- schemathesis/specs/openapi/validation.py +1 -0
- schemathesis/stateful/__init__.py +35 -21
- schemathesis/stateful/config.py +97 -0
- schemathesis/stateful/context.py +135 -0
- schemathesis/stateful/events.py +274 -0
- schemathesis/stateful/runner.py +309 -0
- schemathesis/stateful/sink.py +68 -0
- schemathesis/stateful/state_machine.py +67 -38
- schemathesis/stateful/statistic.py +22 -0
- schemathesis/stateful/validation.py +100 -0
- schemathesis/targets.py +33 -1
- schemathesis/throttling.py +25 -5
- schemathesis/transports/__init__.py +354 -0
- schemathesis/transports/asgi.py +7 -0
- schemathesis/transports/auth.py +25 -2
- schemathesis/transports/content_types.py +3 -1
- schemathesis/transports/headers.py +2 -1
- schemathesis/transports/responses.py +9 -4
- schemathesis/types.py +9 -0
- schemathesis/utils.py +11 -16
- schemathesis-3.39.7.dist-info/METADATA +293 -0
- schemathesis-3.39.7.dist-info/RECORD +160 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/WHEEL +1 -1
- schemathesis/specs/openapi/filters.py +0 -49
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis-3.25.6.dist-info/METADATA +0 -356
- schemathesis-3.25.6.dist-info/RECORD +0 -134
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.25.6.dist-info → schemathesis-3.39.7.dist-info}/licenses/LICENSE +0 -0
schemathesis/runner/impl/solo.py
CHANGED
|
@@ -1,50 +1,42 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Generator
|
|
4
|
+
from typing import TYPE_CHECKING, Generator
|
|
5
5
|
|
|
6
|
-
from ...models import TestResultSet
|
|
7
|
-
from ...types import RequestCert
|
|
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 .. import events
|
|
12
|
+
from .context import RunnerContext
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
request_cert: RequestCert | None = None
|
|
20
|
-
|
|
21
|
-
def _execute(
|
|
22
|
-
self, results: TestResultSet, stop_event: threading.Event
|
|
23
|
-
) -> Generator[events.ExecutionEvent, None, None]:
|
|
24
|
-
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):
|
|
25
21
|
yield event
|
|
26
|
-
if
|
|
22
|
+
if ctx.is_stopped or self._should_stop(event):
|
|
27
23
|
break
|
|
28
24
|
|
|
29
|
-
def _execute_impl(self,
|
|
25
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
30
26
|
auth = get_requests_auth(self.auth, self.auth_type)
|
|
31
27
|
with get_session(auth) as session:
|
|
32
28
|
yield from self._run_tests(
|
|
33
29
|
maker=self.schema.get_all_tests,
|
|
34
|
-
|
|
30
|
+
test_func=network_test,
|
|
35
31
|
settings=self.hypothesis_settings,
|
|
36
32
|
generation_config=self.generation_config,
|
|
37
|
-
seed=self.seed,
|
|
38
33
|
checks=self.checks,
|
|
39
34
|
max_response_time=self.max_response_time,
|
|
40
35
|
targets=self.targets,
|
|
41
|
-
|
|
36
|
+
ctx=ctx,
|
|
42
37
|
session=session,
|
|
43
38
|
headers=self.headers,
|
|
44
|
-
|
|
45
|
-
request_tls_verify=self.request_tls_verify,
|
|
46
|
-
request_proxy=self.request_proxy,
|
|
47
|
-
request_cert=self.request_cert,
|
|
39
|
+
request_config=self.request_config,
|
|
48
40
|
store_interactions=self.store_interactions,
|
|
49
41
|
dry_run=self.dry_run,
|
|
50
42
|
)
|
|
@@ -52,17 +44,16 @@ class SingleThreadRunner(BaseRunner):
|
|
|
52
44
|
|
|
53
45
|
@dataclass
|
|
54
46
|
class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
55
|
-
def _execute_impl(self,
|
|
47
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
56
48
|
yield from self._run_tests(
|
|
57
49
|
maker=self.schema.get_all_tests,
|
|
58
|
-
|
|
50
|
+
test_func=wsgi_test,
|
|
59
51
|
settings=self.hypothesis_settings,
|
|
60
52
|
generation_config=self.generation_config,
|
|
61
|
-
seed=self.seed,
|
|
62
53
|
checks=self.checks,
|
|
63
54
|
max_response_time=self.max_response_time,
|
|
64
55
|
targets=self.targets,
|
|
65
|
-
|
|
56
|
+
ctx=ctx,
|
|
66
57
|
auth=self.auth,
|
|
67
58
|
auth_type=self.auth_type,
|
|
68
59
|
headers=self.headers,
|
|
@@ -73,17 +64,16 @@ class SingleThreadWSGIRunner(SingleThreadRunner):
|
|
|
73
64
|
|
|
74
65
|
@dataclass
|
|
75
66
|
class SingleThreadASGIRunner(SingleThreadRunner):
|
|
76
|
-
def _execute_impl(self,
|
|
67
|
+
def _execute_impl(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
77
68
|
yield from self._run_tests(
|
|
78
69
|
maker=self.schema.get_all_tests,
|
|
79
|
-
|
|
70
|
+
test_func=asgi_test,
|
|
80
71
|
settings=self.hypothesis_settings,
|
|
81
72
|
generation_config=self.generation_config,
|
|
82
|
-
seed=self.seed,
|
|
83
73
|
checks=self.checks,
|
|
84
74
|
max_response_time=self.max_response_time,
|
|
85
75
|
targets=self.targets,
|
|
86
|
-
|
|
76
|
+
ctx=ctx,
|
|
87
77
|
headers=self.headers,
|
|
88
78
|
store_interactions=self.store_interactions,
|
|
89
79
|
dry_run=self.dry_run,
|
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import ctypes
|
|
3
4
|
import queue
|
|
4
5
|
import threading
|
|
5
6
|
import time
|
|
7
|
+
import warnings
|
|
6
8
|
from dataclasses import dataclass
|
|
7
9
|
from queue import Queue
|
|
8
|
-
from typing import Any, Callable, Generator, Iterable, cast
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Callable, Generator, Iterable, cast
|
|
9
11
|
|
|
10
|
-
import
|
|
12
|
+
from hypothesis.errors import HypothesisWarning
|
|
11
13
|
|
|
12
14
|
from ..._hypothesis import create_test
|
|
13
|
-
from ...generation import DataGenerationMethod, GenerationConfig
|
|
14
15
|
from ...internal.result import Ok
|
|
15
|
-
from ...models import CheckFunction, TestResultSet
|
|
16
16
|
from ...stateful import Feedback, Stateful
|
|
17
|
-
from ...targets import Target
|
|
18
17
|
from ...transports.auth import get_requests_auth
|
|
19
|
-
from ...types import RawAuth, RequestCert
|
|
20
18
|
from ...utils import capture_hypothesis_output
|
|
21
19
|
from .. import events
|
|
22
20
|
from .core import BaseRunner, asgi_test, get_session, handle_schema_error, network_test, run_test, wsgi_test
|
|
23
21
|
|
|
22
|
+
if TYPE_CHECKING:
|
|
23
|
+
import hypothesis
|
|
24
|
+
|
|
25
|
+
from ...generation import DataGenerationMethod, GenerationConfig
|
|
26
|
+
from ...internal.checks import CheckFunction
|
|
27
|
+
from ...targets import Target
|
|
28
|
+
from ...types import RawAuth
|
|
29
|
+
from .context import RunnerContext
|
|
30
|
+
|
|
24
31
|
|
|
25
32
|
def _run_task(
|
|
26
|
-
|
|
33
|
+
*,
|
|
34
|
+
test_func: Callable,
|
|
27
35
|
tasks_queue: Queue,
|
|
28
36
|
events_queue: Queue,
|
|
29
37
|
generator_done: threading.Event,
|
|
@@ -32,13 +40,13 @@ def _run_task(
|
|
|
32
40
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
33
41
|
settings: hypothesis.settings,
|
|
34
42
|
generation_config: GenerationConfig,
|
|
35
|
-
|
|
36
|
-
results: TestResultSet,
|
|
43
|
+
ctx: RunnerContext,
|
|
37
44
|
stateful: Stateful | None,
|
|
38
45
|
stateful_recursion_limit: int,
|
|
39
46
|
headers: dict[str, Any] | None = None,
|
|
40
47
|
**kwargs: Any,
|
|
41
48
|
) -> None:
|
|
49
|
+
warnings.filterwarnings("ignore", message="The recursion limit will not be reset", category=HypothesisWarning)
|
|
42
50
|
as_strategy_kwargs = {}
|
|
43
51
|
if headers is not None:
|
|
44
52
|
as_strategy_kwargs["headers"] = {key: value for key, value in headers.items() if key.lower() != "user-agent"}
|
|
@@ -47,10 +55,10 @@ def _run_task(
|
|
|
47
55
|
if recursion_level > stateful_recursion_limit:
|
|
48
56
|
return
|
|
49
57
|
for _result in maker(
|
|
50
|
-
|
|
58
|
+
test_func,
|
|
51
59
|
settings=settings,
|
|
52
60
|
generation_config=generation_config,
|
|
53
|
-
seed=seed,
|
|
61
|
+
seed=ctx.seed,
|
|
54
62
|
as_strategy_kwargs=as_strategy_kwargs,
|
|
55
63
|
):
|
|
56
64
|
# `result` is always `Ok` here
|
|
@@ -62,7 +70,7 @@ def _run_task(
|
|
|
62
70
|
checks,
|
|
63
71
|
data_generation_methods,
|
|
64
72
|
targets,
|
|
65
|
-
|
|
73
|
+
ctx=ctx,
|
|
66
74
|
recursion_level=recursion_level,
|
|
67
75
|
feedback=feedback,
|
|
68
76
|
headers=headers,
|
|
@@ -85,9 +93,9 @@ def _run_task(
|
|
|
85
93
|
operation = result.ok()
|
|
86
94
|
test_function = create_test(
|
|
87
95
|
operation=operation,
|
|
88
|
-
test=
|
|
96
|
+
test=test_func,
|
|
89
97
|
settings=settings,
|
|
90
|
-
seed=seed,
|
|
98
|
+
seed=ctx.seed,
|
|
91
99
|
data_generation_methods=list(data_generation_methods),
|
|
92
100
|
generation_config=generation_config,
|
|
93
101
|
as_strategy_kwargs=as_strategy_kwargs,
|
|
@@ -97,7 +105,7 @@ def _run_task(
|
|
|
97
105
|
# `feedback.get_stateful_tests`
|
|
98
106
|
_run_tests(lambda *_, **__: (items,)) # noqa: B023
|
|
99
107
|
else:
|
|
100
|
-
for event in handle_schema_error(result.err(),
|
|
108
|
+
for event in handle_schema_error(result.err(), ctx, data_generation_methods, 0):
|
|
101
109
|
events_queue.put(event)
|
|
102
110
|
|
|
103
111
|
|
|
@@ -113,8 +121,7 @@ def thread_task(
|
|
|
113
121
|
auth: RawAuth | None,
|
|
114
122
|
auth_type: str | None,
|
|
115
123
|
headers: dict[str, Any] | None,
|
|
116
|
-
|
|
117
|
-
results: TestResultSet,
|
|
124
|
+
ctx: RunnerContext,
|
|
118
125
|
stateful: Stateful | None,
|
|
119
126
|
stateful_recursion_limit: int,
|
|
120
127
|
kwargs: Any,
|
|
@@ -126,17 +133,16 @@ def thread_task(
|
|
|
126
133
|
prepared_auth = get_requests_auth(auth, auth_type)
|
|
127
134
|
with get_session(prepared_auth) as session:
|
|
128
135
|
_run_task(
|
|
129
|
-
network_test,
|
|
130
|
-
tasks_queue,
|
|
131
|
-
events_queue,
|
|
132
|
-
generator_done,
|
|
133
|
-
checks,
|
|
134
|
-
targets,
|
|
135
|
-
data_generation_methods,
|
|
136
|
-
settings,
|
|
137
|
-
generation_config,
|
|
138
|
-
|
|
139
|
-
results,
|
|
136
|
+
test_func=network_test,
|
|
137
|
+
tasks_queue=tasks_queue,
|
|
138
|
+
events_queue=events_queue,
|
|
139
|
+
generator_done=generator_done,
|
|
140
|
+
checks=checks,
|
|
141
|
+
targets=targets,
|
|
142
|
+
data_generation_methods=data_generation_methods,
|
|
143
|
+
settings=settings,
|
|
144
|
+
generation_config=generation_config,
|
|
145
|
+
ctx=ctx,
|
|
140
146
|
stateful=stateful,
|
|
141
147
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
142
148
|
session=session,
|
|
@@ -154,24 +160,22 @@ def wsgi_thread_task(
|
|
|
154
160
|
data_generation_methods: Iterable[DataGenerationMethod],
|
|
155
161
|
settings: hypothesis.settings,
|
|
156
162
|
generation_config: GenerationConfig,
|
|
157
|
-
|
|
158
|
-
results: TestResultSet,
|
|
163
|
+
ctx: RunnerContext,
|
|
159
164
|
stateful: Stateful | None,
|
|
160
165
|
stateful_recursion_limit: int,
|
|
161
166
|
kwargs: Any,
|
|
162
167
|
) -> None:
|
|
163
168
|
_run_task(
|
|
164
|
-
wsgi_test,
|
|
165
|
-
tasks_queue,
|
|
166
|
-
events_queue,
|
|
167
|
-
generator_done,
|
|
168
|
-
checks,
|
|
169
|
-
targets,
|
|
170
|
-
data_generation_methods,
|
|
171
|
-
settings,
|
|
172
|
-
generation_config,
|
|
173
|
-
|
|
174
|
-
results,
|
|
169
|
+
test_func=wsgi_test,
|
|
170
|
+
tasks_queue=tasks_queue,
|
|
171
|
+
events_queue=events_queue,
|
|
172
|
+
generator_done=generator_done,
|
|
173
|
+
checks=checks,
|
|
174
|
+
targets=targets,
|
|
175
|
+
data_generation_methods=data_generation_methods,
|
|
176
|
+
settings=settings,
|
|
177
|
+
generation_config=generation_config,
|
|
178
|
+
ctx=ctx,
|
|
175
179
|
stateful=stateful,
|
|
176
180
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
177
181
|
**kwargs,
|
|
@@ -188,24 +192,22 @@ def asgi_thread_task(
|
|
|
188
192
|
settings: hypothesis.settings,
|
|
189
193
|
generation_config: GenerationConfig,
|
|
190
194
|
headers: dict[str, Any] | None,
|
|
191
|
-
|
|
192
|
-
results: TestResultSet,
|
|
195
|
+
ctx: RunnerContext,
|
|
193
196
|
stateful: Stateful | None,
|
|
194
197
|
stateful_recursion_limit: int,
|
|
195
198
|
kwargs: Any,
|
|
196
199
|
) -> None:
|
|
197
200
|
_run_task(
|
|
198
|
-
asgi_test,
|
|
199
|
-
tasks_queue,
|
|
200
|
-
events_queue,
|
|
201
|
-
generator_done,
|
|
202
|
-
checks,
|
|
203
|
-
targets,
|
|
204
|
-
data_generation_methods,
|
|
205
|
-
settings,
|
|
206
|
-
generation_config,
|
|
207
|
-
|
|
208
|
-
results,
|
|
201
|
+
test_func=asgi_test,
|
|
202
|
+
tasks_queue=tasks_queue,
|
|
203
|
+
events_queue=events_queue,
|
|
204
|
+
generator_done=generator_done,
|
|
205
|
+
checks=checks,
|
|
206
|
+
targets=targets,
|
|
207
|
+
data_generation_methods=data_generation_methods,
|
|
208
|
+
settings=settings,
|
|
209
|
+
generation_config=generation_config,
|
|
210
|
+
ctx=ctx,
|
|
209
211
|
stateful=stateful,
|
|
210
212
|
stateful_recursion_limit=stateful_recursion_limit,
|
|
211
213
|
headers=headers,
|
|
@@ -223,13 +225,8 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
223
225
|
"""Spread different tests among multiple worker threads."""
|
|
224
226
|
|
|
225
227
|
workers_num: int = 2
|
|
226
|
-
request_tls_verify: bool | str = True
|
|
227
|
-
request_proxy: str | None = None
|
|
228
|
-
request_cert: RequestCert | None = None
|
|
229
228
|
|
|
230
|
-
def _execute(
|
|
231
|
-
self, results: TestResultSet, stop_event: threading.Event
|
|
232
|
-
) -> Generator[events.ExecutionEvent, None, None]:
|
|
229
|
+
def _execute(self, ctx: RunnerContext) -> Generator[events.ExecutionEvent, None, None]:
|
|
233
230
|
"""All events come from a queue where different workers push their events."""
|
|
234
231
|
# Instead of generating all tests at once, we do it when there is a free worker to pick it up
|
|
235
232
|
# This is extremely important for memory consumption when testing large schemas
|
|
@@ -237,7 +234,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
237
234
|
# It would be better to have a separate producer thread and communicate via threading events.
|
|
238
235
|
# Though it is a bit more complex, so the current solution is suboptimal in terms of resources utilization,
|
|
239
236
|
# but good enough and easy enough to implement.
|
|
240
|
-
tasks_generator = iter(self.schema.get_all_operations())
|
|
237
|
+
tasks_generator = iter(self.schema.get_all_operations(generation_config=self.generation_config))
|
|
241
238
|
generator_done = threading.Event()
|
|
242
239
|
tasks_queue: Queue = Queue()
|
|
243
240
|
# Add at least `workers_num` tasks first, so all workers are busy
|
|
@@ -250,7 +247,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
250
247
|
break
|
|
251
248
|
# Events are pushed by workers via a separate queue
|
|
252
249
|
events_queue: Queue = Queue()
|
|
253
|
-
workers = self._init_workers(tasks_queue, events_queue,
|
|
250
|
+
workers = self._init_workers(tasks_queue, events_queue, ctx, generator_done)
|
|
254
251
|
|
|
255
252
|
def stop_workers() -> None:
|
|
256
253
|
for worker in workers:
|
|
@@ -269,12 +266,12 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
269
266
|
is_finished = all(not worker.is_alive() for worker in workers)
|
|
270
267
|
while not events_queue.empty():
|
|
271
268
|
event = events_queue.get()
|
|
272
|
-
if
|
|
269
|
+
if ctx.is_stopped or isinstance(event, events.Interrupted) or self._should_stop(event):
|
|
273
270
|
# We could still have events in the queue, but ignore them to keep the logic simple
|
|
274
271
|
# for now, could be improved in the future to show more info in such corner cases
|
|
275
272
|
stop_workers()
|
|
276
273
|
is_finished = True
|
|
277
|
-
if
|
|
274
|
+
if ctx.is_stopped:
|
|
278
275
|
# Discard the event. The invariant is: the next event after `stream.stop()` is `Finished`
|
|
279
276
|
break
|
|
280
277
|
yield event
|
|
@@ -291,13 +288,13 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
291
288
|
yield events.Interrupted()
|
|
292
289
|
|
|
293
290
|
def _init_workers(
|
|
294
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
291
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
295
292
|
) -> list[threading.Thread]:
|
|
296
293
|
"""Initialize & start workers that will execute tests."""
|
|
297
294
|
workers = [
|
|
298
295
|
threading.Thread(
|
|
299
296
|
target=self._get_task(),
|
|
300
|
-
kwargs=self._get_worker_kwargs(tasks_queue, events_queue,
|
|
297
|
+
kwargs=self._get_worker_kwargs(tasks_queue, events_queue, ctx, generator_done),
|
|
301
298
|
name=f"schemathesis_{num}",
|
|
302
299
|
)
|
|
303
300
|
for num in range(self.workers_num)
|
|
@@ -310,7 +307,7 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
310
307
|
return thread_task
|
|
311
308
|
|
|
312
309
|
def _get_worker_kwargs(
|
|
313
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
310
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
314
311
|
) -> dict[str, Any]:
|
|
315
312
|
return {
|
|
316
313
|
"tasks_queue": tasks_queue,
|
|
@@ -323,16 +320,12 @@ class ThreadPoolRunner(BaseRunner):
|
|
|
323
320
|
"auth": self.auth,
|
|
324
321
|
"auth_type": self.auth_type,
|
|
325
322
|
"headers": self.headers,
|
|
326
|
-
"
|
|
327
|
-
"results": results,
|
|
323
|
+
"ctx": ctx,
|
|
328
324
|
"stateful": self.stateful,
|
|
329
325
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
330
326
|
"data_generation_methods": self.schema.data_generation_methods,
|
|
331
327
|
"kwargs": {
|
|
332
|
-
"
|
|
333
|
-
"request_tls_verify": self.request_tls_verify,
|
|
334
|
-
"request_proxy": self.request_proxy,
|
|
335
|
-
"request_cert": self.request_cert,
|
|
328
|
+
"request_config": self.request_config,
|
|
336
329
|
"store_interactions": self.store_interactions,
|
|
337
330
|
"max_response_time": self.max_response_time,
|
|
338
331
|
"dry_run": self.dry_run,
|
|
@@ -345,7 +338,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
|
|
|
345
338
|
return wsgi_thread_task
|
|
346
339
|
|
|
347
340
|
def _get_worker_kwargs(
|
|
348
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
341
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
349
342
|
) -> dict[str, Any]:
|
|
350
343
|
return {
|
|
351
344
|
"tasks_queue": tasks_queue,
|
|
@@ -355,8 +348,7 @@ class ThreadPoolWSGIRunner(ThreadPoolRunner):
|
|
|
355
348
|
"targets": self.targets,
|
|
356
349
|
"settings": self.hypothesis_settings,
|
|
357
350
|
"generation_config": self.generation_config,
|
|
358
|
-
"
|
|
359
|
-
"results": results,
|
|
351
|
+
"ctx": ctx,
|
|
360
352
|
"stateful": self.stateful,
|
|
361
353
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
362
354
|
"data_generation_methods": self.schema.data_generation_methods,
|
|
@@ -376,7 +368,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
|
|
|
376
368
|
return asgi_thread_task
|
|
377
369
|
|
|
378
370
|
def _get_worker_kwargs(
|
|
379
|
-
self, tasks_queue: Queue, events_queue: Queue,
|
|
371
|
+
self, tasks_queue: Queue, events_queue: Queue, ctx: RunnerContext, generator_done: threading.Event
|
|
380
372
|
) -> dict[str, Any]:
|
|
381
373
|
return {
|
|
382
374
|
"tasks_queue": tasks_queue,
|
|
@@ -387,8 +379,7 @@ class ThreadPoolASGIRunner(ThreadPoolRunner):
|
|
|
387
379
|
"settings": self.hypothesis_settings,
|
|
388
380
|
"generation_config": self.generation_config,
|
|
389
381
|
"headers": self.headers,
|
|
390
|
-
"
|
|
391
|
-
"results": results,
|
|
382
|
+
"ctx": ctx,
|
|
392
383
|
"stateful": self.stateful,
|
|
393
384
|
"stateful_recursion_limit": self.stateful_recursion_limit,
|
|
394
385
|
"data_generation_methods": self.schema.data_generation_methods,
|
schemathesis/runner/probes.py
CHANGED
|
@@ -5,23 +5,24 @@ the application supports certain inputs. This is done to avoid false positives i
|
|
|
5
5
|
For example, certail web servers do not support NULL bytes in headers, in such cases, the generated test case
|
|
6
6
|
will not reach the tested application at all.
|
|
7
7
|
"""
|
|
8
|
+
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
10
11
|
import enum
|
|
11
12
|
import warnings
|
|
12
|
-
from dataclasses import asdict, dataclass
|
|
13
|
+
from dataclasses import asdict, dataclass, field
|
|
13
14
|
from typing import TYPE_CHECKING, Any
|
|
14
15
|
|
|
15
16
|
from ..constants import USER_AGENT
|
|
16
17
|
from ..exceptions import format_exception
|
|
17
18
|
from ..models import Request, Response
|
|
18
19
|
from ..sanitization import sanitize_request, sanitize_response
|
|
20
|
+
from ..transports import RequestConfig
|
|
19
21
|
from ..transports.auth import get_requests_auth
|
|
20
22
|
|
|
21
23
|
if TYPE_CHECKING:
|
|
22
24
|
import requests
|
|
23
25
|
|
|
24
|
-
from ..types import RequestCert
|
|
25
26
|
from ..schemas import BaseSchema
|
|
26
27
|
|
|
27
28
|
|
|
@@ -31,9 +32,7 @@ HEADER_NAME = "X-Schemathesis-Probe"
|
|
|
31
32
|
@dataclass
|
|
32
33
|
class ProbeConfig:
|
|
33
34
|
base_url: str | None = None
|
|
34
|
-
|
|
35
|
-
request_proxy: str | None = None
|
|
36
|
-
request_cert: RequestCert | None = None
|
|
35
|
+
request: RequestConfig = field(default_factory=RequestConfig)
|
|
37
36
|
auth: tuple[str, str] | None = None
|
|
38
37
|
auth_type: str | None = None
|
|
39
38
|
headers: dict[str, str] | None = None
|
|
@@ -136,9 +135,12 @@ def send(probe: Probe, session: requests.Session, schema: BaseSchema, config: Pr
|
|
|
136
135
|
request = probe.prepare_request(session, Request(), schema, config)
|
|
137
136
|
request.headers[HEADER_NAME] = probe.name
|
|
138
137
|
request.headers["User-Agent"] = USER_AGENT
|
|
138
|
+
kwargs: dict[str, Any] = {"timeout": config.request.prepared_timeout or 2}
|
|
139
|
+
if config.request.proxy is not None:
|
|
140
|
+
kwargs["proxies"] = {"all": config.request.proxy}
|
|
139
141
|
with warnings.catch_warnings():
|
|
140
142
|
warnings.simplefilter("ignore", InsecureRequestWarning)
|
|
141
|
-
response = session.send(request,
|
|
143
|
+
response = session.send(request, **kwargs)
|
|
142
144
|
except MissingSchema:
|
|
143
145
|
# In-process ASGI/WSGI testing will have local URLs and requires extra handling
|
|
144
146
|
# which is not currently implemented
|
|
@@ -156,9 +158,9 @@ def run(schema: BaseSchema, config: ProbeConfig) -> list[ProbeRun]:
|
|
|
156
158
|
|
|
157
159
|
session = Session()
|
|
158
160
|
session.headers.update(config.headers or {})
|
|
159
|
-
session.verify = config.
|
|
160
|
-
if config.
|
|
161
|
-
session.cert = config.
|
|
161
|
+
session.verify = config.request.tls_verify
|
|
162
|
+
if config.request.cert is not None:
|
|
163
|
+
session.cert = config.request.cert
|
|
162
164
|
if config.auth is not None:
|
|
163
165
|
session.auth = get_requests_auth(config.auth, config.auth_type)
|
|
164
166
|
|