schemathesis 3.39.16__py3-none-any.whl → 4.0.0__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 +41 -79
- schemathesis/auths.py +111 -122
- schemathesis/checks.py +169 -60
- schemathesis/cli/__init__.py +15 -2117
- schemathesis/cli/commands/__init__.py +85 -0
- schemathesis/cli/commands/data.py +10 -0
- schemathesis/cli/commands/run/__init__.py +590 -0
- schemathesis/cli/commands/run/context.py +204 -0
- schemathesis/cli/commands/run/events.py +60 -0
- schemathesis/cli/commands/run/executor.py +157 -0
- schemathesis/cli/commands/run/filters.py +53 -0
- schemathesis/cli/commands/run/handlers/__init__.py +46 -0
- schemathesis/cli/commands/run/handlers/base.py +18 -0
- schemathesis/cli/commands/run/handlers/cassettes.py +474 -0
- schemathesis/cli/commands/run/handlers/junitxml.py +55 -0
- schemathesis/cli/commands/run/handlers/output.py +1628 -0
- schemathesis/cli/commands/run/loaders.py +114 -0
- schemathesis/cli/commands/run/validation.py +246 -0
- schemathesis/cli/constants.py +5 -58
- schemathesis/cli/core.py +19 -0
- schemathesis/cli/ext/fs.py +16 -0
- schemathesis/cli/ext/groups.py +84 -0
- schemathesis/cli/{options.py → ext/options.py} +36 -34
- 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 +527 -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 +67 -0
- schemathesis/core/compat.py +32 -0
- schemathesis/core/control.py +2 -0
- schemathesis/core/curl.py +58 -0
- schemathesis/core/deserialization.py +65 -0
- schemathesis/core/errors.py +459 -0
- schemathesis/core/failures.py +315 -0
- schemathesis/core/fs.py +19 -0
- schemathesis/core/hooks.py +20 -0
- schemathesis/core/loaders.py +104 -0
- schemathesis/core/marks.py +66 -0
- schemathesis/{transports/content_types.py → core/media_types.py} +14 -12
- schemathesis/core/output/__init__.py +46 -0
- schemathesis/core/output/sanitization.py +54 -0
- schemathesis/{throttling.py → core/rate_limit.py} +16 -17
- schemathesis/core/registries.py +31 -0
- schemathesis/core/transforms.py +113 -0
- schemathesis/core/transport.py +223 -0
- schemathesis/core/validation.py +54 -0
- schemathesis/core/version.py +7 -0
- schemathesis/engine/__init__.py +28 -0
- schemathesis/engine/context.py +118 -0
- schemathesis/engine/control.py +36 -0
- schemathesis/engine/core.py +169 -0
- schemathesis/engine/errors.py +464 -0
- schemathesis/engine/events.py +258 -0
- schemathesis/engine/phases/__init__.py +88 -0
- schemathesis/{runner → engine/phases}/probes.py +52 -68
- schemathesis/engine/phases/stateful/__init__.py +68 -0
- schemathesis/engine/phases/stateful/_executor.py +356 -0
- schemathesis/engine/phases/stateful/context.py +85 -0
- schemathesis/engine/phases/unit/__init__.py +212 -0
- schemathesis/engine/phases/unit/_executor.py +416 -0
- schemathesis/engine/phases/unit/_pool.py +82 -0
- schemathesis/engine/recorder.py +247 -0
- schemathesis/errors.py +43 -0
- schemathesis/filters.py +17 -98
- schemathesis/generation/__init__.py +5 -33
- schemathesis/generation/case.py +317 -0
- schemathesis/generation/coverage.py +282 -175
- schemathesis/generation/hypothesis/__init__.py +36 -0
- schemathesis/generation/hypothesis/builder.py +800 -0
- schemathesis/generation/{_hypothesis.py → hypothesis/examples.py} +2 -11
- schemathesis/generation/hypothesis/given.py +66 -0
- schemathesis/generation/hypothesis/reporting.py +14 -0
- schemathesis/generation/hypothesis/strategies.py +16 -0
- schemathesis/generation/meta.py +115 -0
- schemathesis/generation/metrics.py +93 -0
- schemathesis/generation/modes.py +20 -0
- schemathesis/generation/overrides.py +116 -0
- schemathesis/generation/stateful/__init__.py +37 -0
- schemathesis/generation/stateful/state_machine.py +278 -0
- schemathesis/graphql/__init__.py +15 -0
- schemathesis/graphql/checks.py +109 -0
- schemathesis/graphql/loaders.py +284 -0
- schemathesis/hooks.py +80 -101
- schemathesis/openapi/__init__.py +13 -0
- schemathesis/openapi/checks.py +455 -0
- schemathesis/openapi/generation/__init__.py +0 -0
- schemathesis/openapi/generation/filters.py +72 -0
- schemathesis/openapi/loaders.py +313 -0
- schemathesis/pytest/__init__.py +5 -0
- schemathesis/pytest/control_flow.py +7 -0
- schemathesis/pytest/lazy.py +281 -0
- schemathesis/pytest/loaders.py +36 -0
- schemathesis/{extra/pytest_plugin.py → pytest/plugin.py} +128 -108
- schemathesis/python/__init__.py +0 -0
- schemathesis/python/asgi.py +12 -0
- schemathesis/python/wsgi.py +12 -0
- schemathesis/schemas.py +537 -273
- schemathesis/specs/graphql/__init__.py +0 -1
- schemathesis/specs/graphql/_cache.py +1 -2
- schemathesis/specs/graphql/scalars.py +42 -6
- schemathesis/specs/graphql/schemas.py +141 -137
- schemathesis/specs/graphql/validation.py +11 -17
- schemathesis/specs/openapi/__init__.py +6 -1
- schemathesis/specs/openapi/_cache.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +142 -156
- schemathesis/specs/openapi/checks.py +368 -257
- schemathesis/specs/openapi/converter.py +4 -4
- schemathesis/specs/openapi/definitions.py +1 -1
- schemathesis/specs/openapi/examples.py +23 -21
- schemathesis/specs/openapi/expressions/__init__.py +31 -19
- schemathesis/specs/openapi/expressions/extractors.py +1 -4
- schemathesis/specs/openapi/expressions/lexer.py +1 -1
- schemathesis/specs/openapi/expressions/nodes.py +36 -41
- schemathesis/specs/openapi/expressions/parser.py +1 -1
- schemathesis/specs/openapi/formats.py +35 -7
- schemathesis/specs/openapi/media_types.py +53 -12
- schemathesis/specs/openapi/negative/__init__.py +7 -4
- schemathesis/specs/openapi/negative/mutations.py +6 -5
- schemathesis/specs/openapi/parameters.py +7 -10
- schemathesis/specs/openapi/patterns.py +94 -31
- schemathesis/specs/openapi/references.py +12 -53
- schemathesis/specs/openapi/schemas.py +233 -307
- schemathesis/specs/openapi/security.py +1 -1
- schemathesis/specs/openapi/serialization.py +12 -6
- schemathesis/specs/openapi/stateful/__init__.py +268 -133
- schemathesis/specs/openapi/stateful/control.py +87 -0
- schemathesis/specs/openapi/stateful/links.py +209 -0
- schemathesis/transport/__init__.py +142 -0
- schemathesis/transport/asgi.py +26 -0
- schemathesis/transport/prepare.py +124 -0
- schemathesis/transport/requests.py +244 -0
- schemathesis/{_xml.py → transport/serialization.py} +69 -11
- schemathesis/transport/wsgi.py +171 -0
- schemathesis-4.0.0.dist-info/METADATA +204 -0
- schemathesis-4.0.0.dist-info/RECORD +164 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/entry_points.txt +1 -1
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/licenses/LICENSE +1 -1
- schemathesis/_compat.py +0 -74
- schemathesis/_dependency_versions.py +0 -19
- schemathesis/_hypothesis.py +0 -717
- schemathesis/_override.py +0 -50
- schemathesis/_patches.py +0 -21
- schemathesis/_rate_limiter.py +0 -7
- schemathesis/cli/callbacks.py +0 -466
- schemathesis/cli/cassettes.py +0 -561
- schemathesis/cli/context.py +0 -75
- schemathesis/cli/debug.py +0 -27
- schemathesis/cli/handlers.py +0 -19
- schemathesis/cli/junitxml.py +0 -124
- schemathesis/cli/output/__init__.py +0 -1
- schemathesis/cli/output/default.py +0 -920
- schemathesis/cli/output/short.py +0 -59
- schemathesis/cli/reporting.py +0 -79
- schemathesis/cli/sanitization.py +0 -26
- schemathesis/code_samples.py +0 -151
- schemathesis/constants.py +0 -54
- schemathesis/contrib/__init__.py +0 -11
- schemathesis/contrib/openapi/__init__.py +0 -11
- schemathesis/contrib/openapi/fill_missing_examples.py +0 -24
- schemathesis/contrib/openapi/formats/__init__.py +0 -9
- schemathesis/contrib/openapi/formats/uuid.py +0 -16
- schemathesis/contrib/unique_data.py +0 -41
- schemathesis/exceptions.py +0 -571
- schemathesis/experimental/__init__.py +0 -109
- schemathesis/extra/_aiohttp.py +0 -28
- schemathesis/extra/_flask.py +0 -13
- schemathesis/extra/_server.py +0 -18
- schemathesis/failures.py +0 -284
- schemathesis/fixups/__init__.py +0 -37
- schemathesis/fixups/fast_api.py +0 -41
- schemathesis/fixups/utf8_bom.py +0 -28
- schemathesis/generation/_methods.py +0 -44
- schemathesis/graphql.py +0 -3
- schemathesis/internal/__init__.py +0 -7
- schemathesis/internal/checks.py +0 -86
- schemathesis/internal/copy.py +0 -32
- schemathesis/internal/datetime.py +0 -5
- schemathesis/internal/deprecation.py +0 -37
- schemathesis/internal/diff.py +0 -15
- schemathesis/internal/extensions.py +0 -27
- schemathesis/internal/jsonschema.py +0 -36
- schemathesis/internal/output.py +0 -68
- schemathesis/internal/transformation.py +0 -26
- schemathesis/internal/validation.py +0 -34
- schemathesis/lazy.py +0 -474
- schemathesis/loaders.py +0 -122
- schemathesis/models.py +0 -1341
- schemathesis/parameters.py +0 -90
- schemathesis/runner/__init__.py +0 -605
- schemathesis/runner/events.py +0 -389
- schemathesis/runner/impl/__init__.py +0 -3
- schemathesis/runner/impl/context.py +0 -88
- schemathesis/runner/impl/core.py +0 -1280
- schemathesis/runner/impl/solo.py +0 -80
- schemathesis/runner/impl/threadpool.py +0 -391
- schemathesis/runner/serialization.py +0 -544
- schemathesis/sanitization.py +0 -252
- schemathesis/serializers.py +0 -328
- schemathesis/service/__init__.py +0 -18
- schemathesis/service/auth.py +0 -11
- schemathesis/service/ci.py +0 -202
- schemathesis/service/client.py +0 -133
- schemathesis/service/constants.py +0 -38
- schemathesis/service/events.py +0 -61
- schemathesis/service/extensions.py +0 -224
- schemathesis/service/hosts.py +0 -111
- schemathesis/service/metadata.py +0 -71
- schemathesis/service/models.py +0 -258
- schemathesis/service/report.py +0 -255
- schemathesis/service/serialization.py +0 -173
- schemathesis/service/usage.py +0 -66
- schemathesis/specs/graphql/loaders.py +0 -364
- schemathesis/specs/openapi/expressions/context.py +0 -16
- schemathesis/specs/openapi/links.py +0 -389
- schemathesis/specs/openapi/loaders.py +0 -707
- schemathesis/specs/openapi/stateful/statistic.py +0 -198
- schemathesis/specs/openapi/stateful/types.py +0 -14
- schemathesis/specs/openapi/validation.py +0 -26
- schemathesis/stateful/__init__.py +0 -147
- schemathesis/stateful/config.py +0 -97
- schemathesis/stateful/context.py +0 -135
- schemathesis/stateful/events.py +0 -274
- schemathesis/stateful/runner.py +0 -309
- schemathesis/stateful/sink.py +0 -68
- schemathesis/stateful/state_machine.py +0 -328
- schemathesis/stateful/statistic.py +0 -22
- schemathesis/stateful/validation.py +0 -100
- schemathesis/targets.py +0 -77
- schemathesis/transports/__init__.py +0 -369
- schemathesis/transports/asgi.py +0 -7
- schemathesis/transports/auth.py +0 -38
- schemathesis/transports/headers.py +0 -36
- schemathesis/transports/responses.py +0 -57
- schemathesis/types.py +0 -44
- schemathesis/utils.py +0 -164
- schemathesis-3.39.16.dist-info/METADATA +0 -293
- schemathesis-3.39.16.dist-info/RECORD +0 -160
- /schemathesis/{extra → cli/ext}/__init__.py +0 -0
- /schemathesis/{_lazy_import.py → core/lazy_import.py} +0 -0
- /schemathesis/{internal → core}/result.py +0 -0
- {schemathesis-3.39.16.dist-info → schemathesis-4.0.0.dist-info}/WHEEL +0 -0
@@ -1,49 +1,61 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
import asyncio
|
3
4
|
import unittest
|
4
|
-
from contextlib import contextmanager
|
5
5
|
from functools import partial
|
6
6
|
from typing import TYPE_CHECKING, Any, Callable, Generator, Type, cast
|
7
7
|
|
8
8
|
import pytest
|
9
|
-
from _pytest import
|
9
|
+
from _pytest import nodes
|
10
10
|
from _pytest.config import hookimpl
|
11
11
|
from _pytest.python import Class, Function, FunctionDefinition, Metafunc, Module, PyCollector
|
12
|
-
from hypothesis import reporting
|
13
12
|
from hypothesis.errors import InvalidArgument, Unsatisfiable
|
14
13
|
from jsonschema.exceptions import SchemaError
|
15
14
|
|
16
|
-
from
|
17
|
-
from
|
18
|
-
from ..constants import (
|
19
|
-
GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE,
|
15
|
+
from schemathesis.core.control import SkipTest
|
16
|
+
from schemathesis.core.errors import (
|
20
17
|
RECURSIVE_REFERENCE_ERROR_MESSAGE,
|
21
18
|
SERIALIZERS_SUGGESTION_MESSAGE,
|
22
|
-
|
23
|
-
from ..exceptions import (
|
19
|
+
IncorrectUsage,
|
24
20
|
InvalidHeadersExample,
|
25
|
-
|
26
|
-
|
21
|
+
InvalidRegexPattern,
|
22
|
+
InvalidSchema,
|
27
23
|
SerializationNotPossible,
|
28
|
-
|
29
|
-
UsageError,
|
24
|
+
format_exception,
|
30
25
|
)
|
31
|
-
from
|
32
|
-
from
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
from schemathesis.core.failures import FailureGroup
|
27
|
+
from schemathesis.core.marks import Mark
|
28
|
+
from schemathesis.core.result import Ok, Result
|
29
|
+
from schemathesis.generation import overrides
|
30
|
+
from schemathesis.generation.hypothesis.given import (
|
31
|
+
GivenArgsMark,
|
32
|
+
GivenKwargsMark,
|
37
33
|
is_given_applied,
|
38
|
-
is_schemathesis_test,
|
39
34
|
merge_given_args,
|
40
35
|
validate_given_args,
|
41
36
|
)
|
37
|
+
from schemathesis.generation.hypothesis.reporting import ignore_hypothesis_output
|
38
|
+
from schemathesis.pytest.control_flow import fail_on_no_matches
|
39
|
+
from schemathesis.schemas import APIOperation
|
42
40
|
|
43
41
|
if TYPE_CHECKING:
|
44
42
|
from _pytest.fixtures import FuncFixtureInfo
|
45
43
|
|
46
|
-
from
|
44
|
+
from schemathesis.schemas import BaseSchema
|
45
|
+
|
46
|
+
GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE = (
|
47
|
+
"Unsupported test setup. Tests using `@schema.given` cannot be combined with explicit schema examples in the same "
|
48
|
+
"function. Separate these tests into distinct functions to avoid conflicts."
|
49
|
+
)
|
50
|
+
|
51
|
+
|
52
|
+
def _is_schema(value: object) -> bool:
|
53
|
+
from schemathesis.schemas import BaseSchema
|
54
|
+
|
55
|
+
return isinstance(value, BaseSchema)
|
56
|
+
|
57
|
+
|
58
|
+
SchemaHandleMark = Mark["BaseSchema"](attr_name="schema", check=_is_schema)
|
47
59
|
|
48
60
|
|
49
61
|
class SchemathesisFunction(Function):
|
@@ -58,21 +70,15 @@ class SchemathesisFunction(Function):
|
|
58
70
|
self.test_function = test_func
|
59
71
|
self.test_name = test_name
|
60
72
|
|
61
|
-
if not IS_PYTEST_ABOVE_7:
|
62
|
-
# On pytest 7, `self.obj` is already `partial`
|
63
|
-
def _getobj(self) -> partial:
|
64
|
-
"""Tests defined as methods require `self` as the first argument.
|
65
|
-
|
66
|
-
This method is called only for this case.
|
67
|
-
"""
|
68
|
-
return partial(self.obj, self.parent.obj) # type: ignore
|
69
|
-
|
70
73
|
|
71
74
|
class SchemathesisCase(PyCollector):
|
72
|
-
def __init__(self, test_function: Callable, *args: Any, **kwargs: Any) -> None:
|
73
|
-
self.given_kwargs: dict[str, Any]
|
74
|
-
given_args =
|
75
|
-
given_kwargs =
|
75
|
+
def __init__(self, test_function: Callable, schema: BaseSchema, *args: Any, **kwargs: Any) -> None:
|
76
|
+
self.given_kwargs: dict[str, Any]
|
77
|
+
given_args = GivenArgsMark.get(test_function)
|
78
|
+
given_kwargs = GivenKwargsMark.get(test_function)
|
79
|
+
|
80
|
+
assert given_args is not None
|
81
|
+
assert given_kwargs is not None
|
76
82
|
|
77
83
|
def _init_with_valid_test(_test_function: Callable, _args: tuple, _kwargs: dict[str, Any]) -> None:
|
78
84
|
self.test_function = _test_function
|
@@ -84,20 +90,18 @@ class SchemathesisCase(PyCollector):
|
|
84
90
|
if failing_test is not None:
|
85
91
|
self.test_function = failing_test
|
86
92
|
self.is_invalid_test = True
|
87
|
-
self.given_kwargs =
|
93
|
+
self.given_kwargs = {}
|
88
94
|
else:
|
89
95
|
_init_with_valid_test(test_function, given_args, given_kwargs)
|
90
96
|
else:
|
91
97
|
_init_with_valid_test(test_function, given_args, given_kwargs)
|
92
|
-
self.
|
98
|
+
self.schema = schema
|
93
99
|
super().__init__(*args, **kwargs)
|
94
100
|
|
95
101
|
def _get_test_name(self, operation: APIOperation) -> str:
|
96
|
-
return f"{self.name}[{operation.
|
102
|
+
return f"{self.name}[{operation.label}]"
|
97
103
|
|
98
|
-
def _gen_items(
|
99
|
-
self, result: Result[APIOperation, OperationSchemaError]
|
100
|
-
) -> Generator[SchemathesisFunction, None, None]:
|
104
|
+
def _gen_items(self, result: Result[APIOperation, InvalidSchema]) -> Generator[SchemathesisFunction, None, None]:
|
101
105
|
"""Generate all tests for the given API operation.
|
102
106
|
|
103
107
|
Could produce more than one test item if
|
@@ -106,7 +110,15 @@ class SchemathesisCase(PyCollector):
|
|
106
110
|
This implementation is based on the original one in pytest, but with slight adjustments
|
107
111
|
to produce tests out of hypothesis ones.
|
108
112
|
"""
|
109
|
-
from
|
113
|
+
from schemathesis.checks import load_all_checks
|
114
|
+
from schemathesis.generation.hypothesis.builder import (
|
115
|
+
HypothesisTestConfig,
|
116
|
+
HypothesisTestMode,
|
117
|
+
create_test,
|
118
|
+
make_async_test,
|
119
|
+
)
|
120
|
+
|
121
|
+
load_all_checks()
|
110
122
|
|
111
123
|
is_trio_test = False
|
112
124
|
for mark in getattr(self.test_function, "pytestmark", []):
|
@@ -119,34 +131,57 @@ class SchemathesisCase(PyCollector):
|
|
119
131
|
if self.is_invalid_test:
|
120
132
|
funcobj = self.test_function
|
121
133
|
else:
|
122
|
-
|
123
|
-
|
134
|
+
as_strategy_kwargs = {}
|
135
|
+
|
136
|
+
auth = self.schema.config.auth_for(operation=operation)
|
137
|
+
if auth is not None:
|
138
|
+
from requests.auth import _basic_auth_str
|
139
|
+
|
140
|
+
as_strategy_kwargs["headers"] = {"Authorization": _basic_auth_str(*auth)}
|
141
|
+
headers = self.schema.config.headers_for(operation=operation)
|
142
|
+
if headers:
|
143
|
+
as_strategy_kwargs["headers"] = headers
|
144
|
+
|
145
|
+
override = overrides.for_operation(operation=operation, config=self.schema.config)
|
124
146
|
if override is not None:
|
125
|
-
|
126
|
-
for location, entry in override.for_operation(operation).items():
|
147
|
+
for location, entry in override.items():
|
127
148
|
if entry:
|
128
149
|
as_strategy_kwargs[location] = entry
|
129
|
-
|
130
|
-
|
150
|
+
modes = []
|
151
|
+
phases = self.schema.config.phases_for(operation=operation)
|
152
|
+
if phases.examples.enabled:
|
153
|
+
modes.append(HypothesisTestMode.EXAMPLES)
|
154
|
+
if phases.fuzzing.enabled:
|
155
|
+
modes.append(HypothesisTestMode.FUZZING)
|
156
|
+
if phases.coverage.enabled:
|
157
|
+
modes.append(HypothesisTestMode.COVERAGE)
|
158
|
+
|
131
159
|
funcobj = create_test(
|
132
160
|
operation=operation,
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
161
|
+
test_func=self.test_function,
|
162
|
+
config=HypothesisTestConfig(
|
163
|
+
modes=modes,
|
164
|
+
settings=self.schema.config.get_hypothesis_settings(operation=operation),
|
165
|
+
given_kwargs=self.given_kwargs,
|
166
|
+
project=self.schema.config,
|
167
|
+
as_strategy_kwargs=as_strategy_kwargs,
|
168
|
+
),
|
139
169
|
)
|
170
|
+
if asyncio.iscoroutinefunction(self.test_function):
|
171
|
+
# `pytest-trio` expects a coroutine function
|
172
|
+
if is_trio_test:
|
173
|
+
funcobj.hypothesis.inner_test = self.test_function # type: ignore
|
174
|
+
else:
|
175
|
+
funcobj.hypothesis.inner_test = make_async_test(self.test_function) # type: ignore
|
140
176
|
name = self._get_test_name(operation)
|
141
177
|
else:
|
142
178
|
error = result.err()
|
143
179
|
funcobj = error.as_failing_test_function()
|
144
180
|
name = self.name
|
145
|
-
# `full_path` is always available in this case
|
146
181
|
if error.method:
|
147
|
-
name += f"[{error.method.upper()} {error.
|
182
|
+
name += f"[{error.method.upper()} {error.path}]"
|
148
183
|
else:
|
149
|
-
name += f"[{error.
|
184
|
+
name += f"[{error.path}]"
|
150
185
|
|
151
186
|
cls = self._get_class_parent()
|
152
187
|
definition: FunctionDefinition = FunctionDefinition.from_parent(
|
@@ -171,8 +206,6 @@ class SchemathesisCase(PyCollector):
|
|
171
206
|
originalname=self.name,
|
172
207
|
)
|
173
208
|
else:
|
174
|
-
if not IS_PYTEST_ABOVE_8:
|
175
|
-
fixtures.add_funcarg_pseudo_fixture_def(self.parent, metafunc, fixturemanager) # type: ignore[arg-type]
|
176
209
|
fixtureinfo.prune_dependency_tree()
|
177
210
|
for callspec in metafunc._calls:
|
178
211
|
subname = f"{name}[{callspec.id}]"
|
@@ -194,11 +227,8 @@ class SchemathesisCase(PyCollector):
|
|
194
227
|
def _parametrize(self, cls: type | None, definition: FunctionDefinition, fixtureinfo: FuncFixtureInfo) -> Metafunc:
|
195
228
|
parent = self.getparent(Module)
|
196
229
|
module = parent.obj if parent is not None else parent
|
197
|
-
|
198
|
-
|
199
|
-
# Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
|
200
|
-
kwargs["_ispytest"] = True
|
201
|
-
metafunc = Metafunc(definition, fixtureinfo, self.config, **kwargs)
|
230
|
+
# Avoiding `Metafunc` is quite problematic for now, as there are quite a lot of internals we rely on
|
231
|
+
metafunc = Metafunc(definition, fixtureinfo, self.config, cls=cls, module=module, _ispytest=True)
|
202
232
|
methods = []
|
203
233
|
if module is not None and hasattr(module, "pytest_generate_tests"):
|
204
234
|
methods.append(module.pytest_generate_tests)
|
@@ -211,13 +241,7 @@ class SchemathesisCase(PyCollector):
|
|
211
241
|
def collect(self) -> list[Function]: # type: ignore
|
212
242
|
"""Generate different test items for all API operations available in the given schema."""
|
213
243
|
try:
|
214
|
-
items = [
|
215
|
-
item
|
216
|
-
for operation in self.schemathesis_case.get_all_operations(
|
217
|
-
hooks=getattr(self.test_function, "_schemathesis_hooks", None)
|
218
|
-
)
|
219
|
-
for item in self._gen_items(operation)
|
220
|
-
]
|
244
|
+
items = [item for operation in self.schema.get_all_operations() for item in self._gen_items(operation)]
|
221
245
|
if not items:
|
222
246
|
fail_on_no_matches(self.nodeid)
|
223
247
|
return items
|
@@ -225,34 +249,34 @@ class SchemathesisCase(PyCollector):
|
|
225
249
|
pytest.fail("Error during collection")
|
226
250
|
|
227
251
|
|
228
|
-
@hookimpl(hookwrapper=True) # type:ignore
|
252
|
+
@hookimpl(hookwrapper=True) # type:ignore
|
229
253
|
def pytest_pycollect_makeitem(collector: nodes.Collector, name: str, obj: Any) -> Generator[None, Any, None]:
|
230
254
|
"""Switch to a different collector if the test is parametrized marked by schemathesis."""
|
231
255
|
outcome = yield
|
232
|
-
|
233
|
-
|
234
|
-
|
256
|
+
try:
|
257
|
+
schema = SchemaHandleMark.get(obj)
|
258
|
+
assert schema is not None
|
259
|
+
outcome.force_result(SchemathesisCase.from_parent(collector, test_function=obj, name=name, schema=schema))
|
260
|
+
except Exception:
|
235
261
|
outcome.get_result()
|
236
262
|
|
237
263
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
264
|
+
@pytest.hookimpl(tryfirst=True) # type: ignore[misc]
|
265
|
+
def pytest_exception_interact(node: Function, call: pytest.CallInfo, report: pytest.TestReport) -> None:
|
266
|
+
if call.excinfo and call.excinfo.type is FailureGroup:
|
267
|
+
tb_entries = list(call.excinfo.traceback)
|
268
|
+
total_frames = len(tb_entries)
|
243
269
|
|
270
|
+
# Keep internal Schemathesis frames + one extra one from the caller
|
271
|
+
skip_frames = 0
|
272
|
+
for i in range(total_frames - 1, -1, -1):
|
273
|
+
entry = tb_entries[i]
|
244
274
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
reporting.default(value)
|
249
|
-
|
275
|
+
if not str(entry.path).endswith("schemathesis/generation/case.py"):
|
276
|
+
skip_frames = i
|
277
|
+
break
|
250
278
|
|
251
|
-
|
252
|
-
def skip_unnecessary_hypothesis_output() -> Generator:
|
253
|
-
"""Avoid printing Hypothesis output that is not necessary in Schemathesis' pytest plugin."""
|
254
|
-
with reporting.with_reporter(hypothesis_reporter): # type: ignore
|
255
|
-
yield
|
279
|
+
report.longrepr = "".join(format_exception(call.excinfo.value, with_traceback=True, skip_frames=skip_frames))
|
256
280
|
|
257
281
|
|
258
282
|
@hookimpl(wrapper=True)
|
@@ -263,28 +287,28 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
263
287
|
"""
|
264
288
|
from hypothesis_jsonschema._canonicalise import HypothesisRefResolutionError
|
265
289
|
|
266
|
-
from
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
290
|
+
from schemathesis.generation.hypothesis.builder import (
|
291
|
+
InvalidHeadersExampleMark,
|
292
|
+
InvalidRegexMark,
|
293
|
+
NonSerializableMark,
|
294
|
+
UnsatisfiableExampleMark,
|
271
295
|
)
|
272
296
|
|
273
297
|
__tracebackhide__ = True
|
274
298
|
if isinstance(pyfuncitem, SchemathesisFunction):
|
275
299
|
try:
|
276
|
-
with
|
300
|
+
with ignore_hypothesis_output():
|
277
301
|
yield
|
278
302
|
except InvalidArgument as exc:
|
279
303
|
if "Inconsistent args" in str(exc) and "@example()" in str(exc):
|
280
|
-
raise
|
281
|
-
raise
|
304
|
+
raise IncorrectUsage(GIVEN_AND_EXPLICIT_EXAMPLES_ERROR_MESSAGE) from None
|
305
|
+
raise InvalidSchema(exc.args[0]) from None
|
282
306
|
except HypothesisRefResolutionError:
|
283
307
|
pytest.skip(RECURSIVE_REFERENCE_ERROR_MESSAGE)
|
284
308
|
except (SkipTest, unittest.SkipTest) as exc:
|
285
|
-
if
|
309
|
+
if UnsatisfiableExampleMark.is_set(pyfuncitem.obj):
|
286
310
|
raise Unsatisfiable("Failed to generate test cases from examples for this API operation") from None
|
287
|
-
non_serializable =
|
311
|
+
non_serializable = NonSerializableMark.get(pyfuncitem.obj)
|
288
312
|
if non_serializable is not None:
|
289
313
|
media_types = ", ".join(non_serializable.media_types)
|
290
314
|
raise SerializationNotPossible(
|
@@ -292,20 +316,16 @@ def pytest_pyfunc_call(pyfuncitem): # type:ignore
|
|
292
316
|
f" unsupported payload media types: {media_types}\n{SERIALIZERS_SUGGESTION_MESSAGE}",
|
293
317
|
media_types=non_serializable.media_types,
|
294
318
|
) from None
|
295
|
-
invalid_regex =
|
319
|
+
invalid_regex = InvalidRegexMark.get(pyfuncitem.obj)
|
296
320
|
if invalid_regex is not None:
|
297
|
-
raise
|
298
|
-
invalid_headers =
|
321
|
+
raise InvalidRegexPattern.from_schema_error(invalid_regex, from_examples=True) from None
|
322
|
+
invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
|
299
323
|
if invalid_headers is not None:
|
300
324
|
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
301
325
|
pytest.skip(exc.args[0])
|
302
326
|
except SchemaError as exc:
|
303
|
-
raise
|
304
|
-
|
305
|
-
if hasattr(exc, "__notes__"):
|
306
|
-
exc.__notes__ = [note for note in exc.__notes__ if not _should_ignore_entry(note)] # type: ignore
|
307
|
-
raise
|
308
|
-
invalid_headers = get_invalid_example_headers_mark(pyfuncitem.obj)
|
327
|
+
raise InvalidRegexPattern.from_schema_error(exc, from_examples=False) from exc
|
328
|
+
invalid_headers = InvalidHeadersExampleMark.get(pyfuncitem.obj)
|
309
329
|
if invalid_headers is not None:
|
310
330
|
raise InvalidHeadersExample.from_headers(invalid_headers) from None
|
311
331
|
else:
|
File without changes
|
@@ -0,0 +1,12 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
|
+
|
5
|
+
if TYPE_CHECKING:
|
6
|
+
from starlette_testclient import TestClient as ASGIClient
|
7
|
+
|
8
|
+
|
9
|
+
def get_client(app: Any) -> ASGIClient:
|
10
|
+
from starlette_testclient import TestClient as ASGIClient
|
11
|
+
|
12
|
+
return ASGIClient(app)
|