schemathesis 3.29.2__py3-none-any.whl → 3.30.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 +3 -3
- schemathesis/_compat.py +2 -2
- schemathesis/_dependency_versions.py +1 -3
- schemathesis/_hypothesis.py +6 -0
- schemathesis/_lazy_import.py +1 -0
- schemathesis/_override.py +1 -0
- schemathesis/_rate_limiter.py +2 -1
- schemathesis/_xml.py +1 -0
- schemathesis/auths.py +4 -2
- schemathesis/checks.py +8 -5
- schemathesis/cli/__init__.py +8 -1
- schemathesis/cli/callbacks.py +3 -4
- schemathesis/cli/cassettes.py +6 -4
- schemathesis/cli/constants.py +2 -0
- schemathesis/cli/context.py +3 -0
- schemathesis/cli/debug.py +2 -1
- schemathesis/cli/handlers.py +1 -1
- schemathesis/cli/options.py +1 -0
- schemathesis/cli/output/default.py +50 -22
- schemathesis/cli/output/short.py +21 -10
- schemathesis/cli/sanitization.py +1 -0
- schemathesis/code_samples.py +1 -0
- schemathesis/constants.py +1 -0
- schemathesis/contrib/openapi/__init__.py +1 -1
- schemathesis/contrib/openapi/fill_missing_examples.py +2 -0
- schemathesis/contrib/openapi/formats/uuid.py +2 -1
- schemathesis/contrib/unique_data.py +2 -1
- schemathesis/exceptions.py +40 -26
- schemathesis/experimental/__init__.py +14 -0
- schemathesis/extra/_aiohttp.py +1 -0
- schemathesis/extra/_server.py +1 -0
- schemathesis/extra/pytest_plugin.py +13 -24
- schemathesis/failures.py +32 -3
- schemathesis/filters.py +2 -1
- schemathesis/fixups/__init__.py +1 -0
- schemathesis/fixups/fast_api.py +2 -2
- schemathesis/fixups/utf8_bom.py +1 -2
- schemathesis/generation/__init__.py +2 -1
- schemathesis/hooks.py +3 -1
- schemathesis/internal/copy.py +19 -3
- schemathesis/internal/deprecation.py +1 -1
- schemathesis/internal/jsonschema.py +2 -1
- schemathesis/internal/result.py +1 -1
- schemathesis/internal/transformation.py +1 -0
- schemathesis/lazy.py +3 -2
- schemathesis/loaders.py +4 -2
- schemathesis/models.py +20 -5
- schemathesis/parameters.py +1 -0
- schemathesis/runner/__init__.py +1 -1
- schemathesis/runner/events.py +21 -4
- schemathesis/runner/impl/core.py +61 -33
- schemathesis/runner/impl/solo.py +2 -1
- schemathesis/runner/impl/threadpool.py +4 -0
- schemathesis/runner/probes.py +1 -1
- schemathesis/runner/serialization.py +1 -1
- schemathesis/sanitization.py +2 -0
- schemathesis/schemas.py +1 -4
- schemathesis/service/ci.py +1 -0
- schemathesis/service/client.py +7 -7
- schemathesis/service/events.py +2 -1
- schemathesis/service/extensions.py +5 -5
- schemathesis/service/hosts.py +1 -0
- schemathesis/service/metadata.py +2 -1
- schemathesis/service/models.py +2 -1
- schemathesis/service/report.py +3 -3
- schemathesis/service/serialization.py +54 -23
- schemathesis/service/usage.py +1 -0
- schemathesis/specs/graphql/_cache.py +1 -1
- schemathesis/specs/graphql/loaders.py +1 -1
- schemathesis/specs/graphql/nodes.py +1 -0
- schemathesis/specs/graphql/scalars.py +2 -2
- schemathesis/specs/graphql/schemas.py +7 -7
- schemathesis/specs/graphql/validation.py +1 -2
- schemathesis/specs/openapi/_hypothesis.py +17 -11
- schemathesis/specs/openapi/checks.py +102 -9
- schemathesis/specs/openapi/converter.py +2 -1
- schemathesis/specs/openapi/definitions.py +2 -1
- schemathesis/specs/openapi/examples.py +7 -9
- schemathesis/specs/openapi/expressions/__init__.py +29 -2
- schemathesis/specs/openapi/expressions/context.py +1 -1
- schemathesis/specs/openapi/expressions/extractors.py +23 -0
- schemathesis/specs/openapi/expressions/lexer.py +19 -18
- schemathesis/specs/openapi/expressions/nodes.py +24 -4
- schemathesis/specs/openapi/expressions/parser.py +26 -5
- schemathesis/specs/openapi/filters.py +1 -0
- schemathesis/specs/openapi/links.py +35 -7
- schemathesis/specs/openapi/loaders.py +13 -11
- schemathesis/specs/openapi/negative/__init__.py +2 -1
- schemathesis/specs/openapi/negative/mutations.py +1 -0
- schemathesis/specs/openapi/parameters.py +1 -0
- schemathesis/specs/openapi/schemas.py +27 -38
- schemathesis/specs/openapi/security.py +1 -0
- schemathesis/specs/openapi/serialization.py +1 -0
- schemathesis/specs/openapi/stateful/__init__.py +159 -70
- schemathesis/specs/openapi/stateful/statistic.py +198 -0
- schemathesis/specs/openapi/stateful/types.py +13 -0
- schemathesis/specs/openapi/utils.py +1 -0
- schemathesis/specs/openapi/validation.py +1 -0
- schemathesis/stateful/__init__.py +4 -2
- schemathesis/stateful/config.py +66 -0
- schemathesis/stateful/context.py +93 -0
- schemathesis/stateful/events.py +209 -0
- schemathesis/stateful/runner.py +233 -0
- schemathesis/stateful/sink.py +68 -0
- schemathesis/stateful/state_machine.py +39 -22
- schemathesis/stateful/statistic.py +20 -0
- schemathesis/stateful/validation.py +66 -0
- schemathesis/targets.py +1 -0
- schemathesis/throttling.py +23 -3
- schemathesis/transports/__init__.py +28 -10
- schemathesis/transports/auth.py +1 -0
- schemathesis/transports/content_types.py +1 -1
- schemathesis/transports/headers.py +2 -1
- schemathesis/transports/responses.py +6 -4
- schemathesis/types.py +1 -0
- schemathesis/utils.py +1 -0
- {schemathesis-3.29.2.dist-info → schemathesis-3.30.0.dist-info}/METADATA +1 -1
- schemathesis-3.30.0.dist-info/RECORD +150 -0
- schemathesis/specs/openapi/stateful/links.py +0 -92
- schemathesis-3.29.2.dist-info/RECORD +0 -141
- {schemathesis-3.29.2.dist-info → schemathesis-3.30.0.dist-info}/WHEEL +0 -0
- {schemathesis-3.29.2.dist-info → schemathesis-3.30.0.dist-info}/entry_points.txt +0 -0
- {schemathesis-3.29.2.dist-info → schemathesis-3.30.0.dist-info}/licenses/LICENSE +0 -0
schemathesis/__init__.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from typing import Any
|
|
3
4
|
|
|
4
|
-
from . import auths, checks,
|
|
5
|
+
from . import auths, checks, contrib, experimental, fixups, graphql, hooks, runner, serializers, targets # noqa: E402
|
|
5
6
|
from ._lazy_import import lazy_import
|
|
6
|
-
from .generation import DataGenerationMethod, GenerationConfig, HeaderConfig # noqa: E402
|
|
7
7
|
from .constants import SCHEMATHESIS_VERSION # noqa: E402
|
|
8
|
+
from .generation import DataGenerationMethod, GenerationConfig, HeaderConfig # noqa: E402
|
|
8
9
|
from .models import Case # noqa: E402
|
|
9
10
|
from .specs import openapi # noqa: E402
|
|
10
11
|
|
|
11
|
-
|
|
12
12
|
__version__ = SCHEMATHESIS_VERSION
|
|
13
13
|
|
|
14
14
|
# Default loaders
|
schemathesis/_compat.py
CHANGED
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
"""Compatibility flags based on installed dependency versions."""
|
|
2
2
|
|
|
3
|
-
from packaging import version
|
|
4
|
-
|
|
5
3
|
from importlib import metadata
|
|
6
4
|
|
|
5
|
+
from packaging import version
|
|
7
6
|
|
|
8
7
|
WERKZEUG_VERSION = version.parse(metadata.version("werkzeug"))
|
|
9
8
|
IS_WERKZEUG_ABOVE_3 = WERKZEUG_VERSION >= version.parse("3.0")
|
|
10
9
|
IS_WERKZEUG_BELOW_2_1 = WERKZEUG_VERSION < version.parse("2.1.0")
|
|
11
10
|
|
|
12
11
|
PYTEST_VERSION = version.parse(metadata.version("pytest"))
|
|
13
|
-
IS_PYTEST_ABOVE_54 = PYTEST_VERSION >= version.parse("5.4.0")
|
|
14
12
|
IS_PYTEST_ABOVE_7 = PYTEST_VERSION >= version.parse("7.0.0")
|
|
15
13
|
IS_PYTEST_ABOVE_8 = PYTEST_VERSION >= version.parse("8.0.0")
|
|
16
14
|
|
schemathesis/_hypothesis.py
CHANGED
|
@@ -10,6 +10,7 @@ import hypothesis
|
|
|
10
10
|
from hypothesis import Phase
|
|
11
11
|
from hypothesis import strategies as st
|
|
12
12
|
from hypothesis.errors import HypothesisWarning, Unsatisfiable
|
|
13
|
+
from hypothesis.internal.entropy import deterministic_PRNG
|
|
13
14
|
from hypothesis.internal.reflection import proxies
|
|
14
15
|
from jsonschema.exceptions import SchemaError
|
|
15
16
|
|
|
@@ -23,6 +24,11 @@ from .transports.content_types import parse_content_type
|
|
|
23
24
|
from .transports.headers import has_invalid_characters, is_latin_1_encodable
|
|
24
25
|
from .utils import GivenInput, combine_strategies
|
|
25
26
|
|
|
27
|
+
# Forcefully initializes Hypothesis' global PRNG to avoid races that initilize it
|
|
28
|
+
# if e.g. Schemathesis CLI is used with multiple workers
|
|
29
|
+
with deterministic_PRNG():
|
|
30
|
+
pass
|
|
31
|
+
|
|
26
32
|
|
|
27
33
|
def create_test(
|
|
28
34
|
*,
|
schemathesis/_lazy_import.py
CHANGED
schemathesis/_override.py
CHANGED
schemathesis/_rate_limiter.py
CHANGED
|
@@ -3,4 +3,5 @@ from ._dependency_versions import IS_PYRATE_LIMITER_ABOVE_3
|
|
|
3
3
|
if IS_PYRATE_LIMITER_ABOVE_3:
|
|
4
4
|
from pyrate_limiter import Limiter, Rate, RateItem
|
|
5
5
|
else:
|
|
6
|
-
from pyrate_limiter import Limiter
|
|
6
|
+
from pyrate_limiter import Limiter
|
|
7
|
+
from pyrate_limiter import RequestRate as Rate
|
schemathesis/_xml.py
CHANGED
schemathesis/auths.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Support for custom API authentication mechanisms."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
+
|
|
4
5
|
import inspect
|
|
5
6
|
import threading
|
|
6
7
|
import time
|
|
@@ -11,10 +12,10 @@ from typing import (
|
|
|
11
12
|
Any,
|
|
12
13
|
Callable,
|
|
13
14
|
Generic,
|
|
15
|
+
Protocol,
|
|
14
16
|
TypeVar,
|
|
15
17
|
overload,
|
|
16
18
|
runtime_checkable,
|
|
17
|
-
Protocol,
|
|
18
19
|
)
|
|
19
20
|
|
|
20
21
|
from .exceptions import UsageError
|
|
@@ -22,9 +23,10 @@ from .filters import FilterSet, FilterValue, MatcherFunc, attach_filter_chain
|
|
|
22
23
|
from .types import GenericTest
|
|
23
24
|
|
|
24
25
|
if TYPE_CHECKING:
|
|
25
|
-
from .models import APIOperation, Case
|
|
26
26
|
import requests.auth
|
|
27
27
|
|
|
28
|
+
from .models import APIOperation, Case
|
|
29
|
+
|
|
28
30
|
DEFAULT_REFRESH_INTERVAL = 300
|
|
29
31
|
AUTH_STORAGE_ATTRIBUTE_NAME = "_schemathesis_auth"
|
|
30
32
|
Auth = TypeVar("Auth")
|
schemathesis/checks.py
CHANGED
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
|
|
2
|
+
|
|
3
3
|
import json
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
6
|
from . import failures
|
|
6
|
-
from .exceptions import
|
|
7
|
+
from .exceptions import get_response_parsing_error, get_server_error
|
|
7
8
|
from .specs.openapi.checks import (
|
|
8
9
|
content_type_conformance,
|
|
10
|
+
negative_data_rejection,
|
|
9
11
|
response_headers_conformance,
|
|
10
12
|
response_schema_conformance,
|
|
11
13
|
status_code_conformance,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
if TYPE_CHECKING:
|
|
15
|
-
from .transports.responses import GenericResponse
|
|
16
17
|
from .models import Case, CheckFunction
|
|
18
|
+
from .transports.responses import GenericResponse
|
|
17
19
|
|
|
18
20
|
|
|
19
21
|
def not_a_server_error(response: GenericResponse, case: Case) -> bool | None:
|
|
@@ -24,14 +26,14 @@ def not_a_server_error(response: GenericResponse, case: Case) -> bool | None:
|
|
|
24
26
|
|
|
25
27
|
status_code = response.status_code
|
|
26
28
|
if status_code >= 500:
|
|
27
|
-
exc_class = get_server_error(status_code)
|
|
29
|
+
exc_class = get_server_error(case.operation.verbose_name, status_code)
|
|
28
30
|
raise exc_class(failures.ServerError.title, context=failures.ServerError(status_code=status_code))
|
|
29
31
|
if isinstance(case, GraphQLCase):
|
|
30
32
|
try:
|
|
31
33
|
data = get_json(response)
|
|
32
34
|
validate_graphql_response(data)
|
|
33
35
|
except json.JSONDecodeError as exc:
|
|
34
|
-
exc_class = get_response_parsing_error(exc)
|
|
36
|
+
exc_class = get_response_parsing_error(case.operation.verbose_name, exc)
|
|
35
37
|
context = failures.JSONDecodeErrorContext.from_exception(exc)
|
|
36
38
|
raise exc_class(context.title, context=context) from exc
|
|
37
39
|
return None
|
|
@@ -43,6 +45,7 @@ OPTIONAL_CHECKS = (
|
|
|
43
45
|
content_type_conformance,
|
|
44
46
|
response_headers_conformance,
|
|
45
47
|
response_schema_conformance,
|
|
48
|
+
negative_data_rejection,
|
|
46
49
|
)
|
|
47
50
|
ALL_CHECKS: tuple[CheckFunction, ...] = DEFAULT_CHECKS + OPTIONAL_CHECKS
|
|
48
51
|
|
schemathesis/cli/__init__.py
CHANGED
|
@@ -683,7 +683,14 @@ The report data, consisting of a tar gz file with multiple JSON files, is subjec
|
|
|
683
683
|
"--experimental",
|
|
684
684
|
"experiments",
|
|
685
685
|
help="Enable experimental support for specific features.",
|
|
686
|
-
type=click.Choice(
|
|
686
|
+
type=click.Choice(
|
|
687
|
+
[
|
|
688
|
+
experimental.OPEN_API_3_1.name,
|
|
689
|
+
experimental.SCHEMA_ANALYSIS.name,
|
|
690
|
+
experimental.STATEFUL_TEST_RUNNER.name,
|
|
691
|
+
experimental.STATEFUL_ONLY.name,
|
|
692
|
+
]
|
|
693
|
+
),
|
|
687
694
|
callback=callbacks.convert_experimental,
|
|
688
695
|
multiple=True,
|
|
689
696
|
)
|
schemathesis/cli/callbacks.py
CHANGED
|
@@ -7,25 +7,24 @@ import re
|
|
|
7
7
|
import traceback
|
|
8
8
|
from contextlib import contextmanager
|
|
9
9
|
from functools import partial
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, Generator
|
|
11
11
|
from urllib.parse import urlparse
|
|
12
12
|
|
|
13
13
|
import click
|
|
14
|
-
|
|
15
14
|
from click.types import LazyFile # type: ignore
|
|
16
15
|
|
|
17
16
|
from .. import exceptions, experimental, throttling
|
|
18
17
|
from ..code_samples import CodeSampleStyle
|
|
18
|
+
from ..constants import FALSE_VALUES, TRUE_VALUES
|
|
19
19
|
from ..exceptions import extract_nth_traceback
|
|
20
20
|
from ..generation import DataGenerationMethod
|
|
21
|
-
from ..constants import TRUE_VALUES, FALSE_VALUES
|
|
22
21
|
from ..internal.validation import file_exists, is_filename, is_illegal_surrogate
|
|
23
22
|
from ..loaders import load_app
|
|
24
23
|
from ..service.hosts import get_temporary_hosts_file
|
|
24
|
+
from ..stateful import Stateful
|
|
25
25
|
from ..transports.headers import has_invalid_characters, is_latin_1_encodable
|
|
26
26
|
from ..types import PathLike
|
|
27
27
|
from .constants import DEFAULT_WORKERS
|
|
28
|
-
from ..stateful import Stateful
|
|
29
28
|
|
|
30
29
|
if TYPE_CHECKING:
|
|
31
30
|
import hypothesis
|
schemathesis/cli/cassettes.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import base64
|
|
3
4
|
import json
|
|
4
5
|
import re
|
|
@@ -6,7 +7,7 @@ import sys
|
|
|
6
7
|
import threading
|
|
7
8
|
from dataclasses import dataclass, field
|
|
8
9
|
from queue import Queue
|
|
9
|
-
from typing import IO, Any, Generator, Iterator, cast
|
|
10
|
+
from typing import IO, TYPE_CHECKING, Any, Generator, Iterator, cast
|
|
10
11
|
|
|
11
12
|
from ..constants import SCHEMATHESIS_VERSION
|
|
12
13
|
from ..runner import events
|
|
@@ -16,10 +17,11 @@ from .handlers import EventHandler
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
import click
|
|
18
19
|
import requests
|
|
20
|
+
|
|
21
|
+
from ..generation import DataGenerationMethod
|
|
19
22
|
from ..models import Request, Response
|
|
20
23
|
from ..runner.serialization import SerializedCheck, SerializedInteraction
|
|
21
24
|
from .context import ExecutionContext
|
|
22
|
-
from ..generation import DataGenerationMethod
|
|
23
25
|
|
|
24
26
|
# Wait until the worker terminates
|
|
25
27
|
WRITER_WORKER_JOIN_TIMEOUT = 1
|
|
@@ -351,9 +353,9 @@ def filter_cassette(
|
|
|
351
353
|
|
|
352
354
|
def get_prepared_request(data: dict[str, Any]) -> requests.PreparedRequest:
|
|
353
355
|
"""Create a `requests.PreparedRequest` from a serialized one."""
|
|
354
|
-
from requests.structures import CaseInsensitiveDict
|
|
355
|
-
from requests.cookies import RequestsCookieJar
|
|
356
356
|
import requests
|
|
357
|
+
from requests.cookies import RequestsCookieJar
|
|
358
|
+
from requests.structures import CaseInsensitiveDict
|
|
357
359
|
|
|
358
360
|
prepared = requests.PreparedRequest()
|
|
359
361
|
prepared.method = data["method"]
|
schemathesis/cli/constants.py
CHANGED
schemathesis/cli/context.py
CHANGED
|
@@ -16,6 +16,8 @@ from ..service.models import AnalysisResult
|
|
|
16
16
|
if TYPE_CHECKING:
|
|
17
17
|
import hypothesis
|
|
18
18
|
|
|
19
|
+
from ..stateful.sink import StateMachineSink
|
|
20
|
+
|
|
19
21
|
|
|
20
22
|
@dataclass
|
|
21
23
|
class ServiceReportContext:
|
|
@@ -57,6 +59,7 @@ class ExecutionContext:
|
|
|
57
59
|
analysis: Result[AnalysisResult, Exception] | None = None
|
|
58
60
|
# Special flag to display a warning about Windows-specific encoding issue
|
|
59
61
|
encountered_windows_encoding_issue: bool = False
|
|
62
|
+
state_machine_sink: StateMachineSink | None = None
|
|
60
63
|
|
|
61
64
|
@deprecated_property(removed_in="4.0", replacement="show_trace")
|
|
62
65
|
def show_errors_tracebacks(self) -> bool:
|
schemathesis/cli/debug.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
import json
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
|
-
|
|
7
7
|
from .handlers import EventHandler
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
10
|
from click.utils import LazyFile
|
|
11
|
+
|
|
11
12
|
from ..runner import events
|
|
12
13
|
from .context import ExecutionContext
|
|
13
14
|
|
schemathesis/cli/handlers.py
CHANGED
schemathesis/cli/options.py
CHANGED
|
@@ -7,11 +7,11 @@ import textwrap
|
|
|
7
7
|
import time
|
|
8
8
|
from importlib import metadata
|
|
9
9
|
from queue import Queue
|
|
10
|
-
from typing import Any, Generator,
|
|
10
|
+
from typing import TYPE_CHECKING, Any, Generator, Literal, cast
|
|
11
11
|
|
|
12
12
|
import click
|
|
13
13
|
|
|
14
|
-
from ... import service
|
|
14
|
+
from ... import experimental, service
|
|
15
15
|
from ...constants import (
|
|
16
16
|
DISCORD_LINK,
|
|
17
17
|
FALSE_VALUES,
|
|
@@ -24,9 +24,9 @@ from ...constants import (
|
|
|
24
24
|
)
|
|
25
25
|
from ...exceptions import (
|
|
26
26
|
RuntimeErrorType,
|
|
27
|
+
extract_requests_exception_details,
|
|
27
28
|
format_exception,
|
|
28
29
|
prepare_response_payload,
|
|
29
|
-
extract_requests_exception_details,
|
|
30
30
|
)
|
|
31
31
|
from ...experimental import GLOBAL_EXPERIMENTS
|
|
32
32
|
from ...internal.result import Ok
|
|
@@ -35,10 +35,12 @@ from ...runner import events
|
|
|
35
35
|
from ...runner.events import InternalErrorType, SchemaErrorType
|
|
36
36
|
from ...runner.probes import ProbeOutcome
|
|
37
37
|
from ...runner.serialization import SerializedError, SerializedTestResult
|
|
38
|
-
from ...service.models import AnalysisSuccess,
|
|
38
|
+
from ...service.models import AnalysisSuccess, ErrorState, UnknownExtension
|
|
39
|
+
from ...stateful import events as stateful_events
|
|
40
|
+
from ...stateful.sink import StateMachineSink
|
|
39
41
|
from ..context import ExecutionContext, FileReportContext, ServiceReportContext
|
|
40
42
|
from ..handlers import EventHandler
|
|
41
|
-
from ..reporting import
|
|
43
|
+
from ..reporting import TEST_CASE_ID_TITLE, get_runtime_error_suggestion, group_by_case, split_traceback
|
|
42
44
|
|
|
43
45
|
if TYPE_CHECKING:
|
|
44
46
|
import requests
|
|
@@ -68,14 +70,14 @@ def get_percentage(position: int, length: int) -> str:
|
|
|
68
70
|
return f"[{percentage_message}]"
|
|
69
71
|
|
|
70
72
|
|
|
71
|
-
def display_execution_result(context: ExecutionContext,
|
|
73
|
+
def display_execution_result(context: ExecutionContext, status: Literal["success", "failure", "error", "skip"]) -> None:
|
|
72
74
|
"""Display an appropriate symbol for the given event's execution result."""
|
|
73
75
|
symbol, color = {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}[
|
|
76
|
+
"success": (".", "green"),
|
|
77
|
+
"failure": ("F", "red"),
|
|
78
|
+
"error": ("E", "red"),
|
|
79
|
+
"skip": ("S", "yellow"),
|
|
80
|
+
}[status]
|
|
79
81
|
context.current_line_length += len(symbol)
|
|
80
82
|
click.secho(symbol, nl=False, fg=color)
|
|
81
83
|
|
|
@@ -433,6 +435,9 @@ def display_statistic(context: ExecutionContext, event: events.Finished) -> None
|
|
|
433
435
|
display_section_name("SUMMARY")
|
|
434
436
|
click.echo()
|
|
435
437
|
total = event.total
|
|
438
|
+
if context.state_machine_sink is not None:
|
|
439
|
+
click.echo(context.state_machine_sink.transitions.to_formatted_table(get_terminal_width()))
|
|
440
|
+
click.echo()
|
|
436
441
|
if event.is_empty or not total:
|
|
437
442
|
click.secho("No checks were performed.", bold=True)
|
|
438
443
|
|
|
@@ -551,7 +556,7 @@ def display_report_metadata(meta: service.Metadata) -> None:
|
|
|
551
556
|
if value is not None:
|
|
552
557
|
click.secho(f" -> {key}: {value}")
|
|
553
558
|
click.echo()
|
|
554
|
-
click.secho(f"Compressed report size: {meta.size / 1024
|
|
559
|
+
click.secho(f"Compressed report size: {meta.size / 1024.0:,.0f} KB", bold=True)
|
|
555
560
|
|
|
556
561
|
|
|
557
562
|
def display_service_unauthorized(hostname: str) -> None:
|
|
@@ -839,7 +844,7 @@ def handle_after_execution(context: ExecutionContext, event: events.AfterExecuti
|
|
|
839
844
|
"""Display the execution result + current progress at the same line with the method / path names."""
|
|
840
845
|
context.operations_processed += 1
|
|
841
846
|
context.results.append(event.result)
|
|
842
|
-
display_execution_result(context, event)
|
|
847
|
+
display_execution_result(context, event.status.value)
|
|
843
848
|
display_percentage(context, event)
|
|
844
849
|
|
|
845
850
|
|
|
@@ -867,27 +872,50 @@ def handle_internal_error(context: ExecutionContext, event: events.InternalError
|
|
|
867
872
|
raise click.Abort
|
|
868
873
|
|
|
869
874
|
|
|
875
|
+
def handle_stateful_event(context: ExecutionContext, event: events.StatefulEvent) -> None:
|
|
876
|
+
if isinstance(event.data, stateful_events.RunStarted):
|
|
877
|
+
context.state_machine_sink = event.data.state_machine.sink()
|
|
878
|
+
if not experimental.STATEFUL_ONLY.is_enabled:
|
|
879
|
+
click.echo()
|
|
880
|
+
click.secho("Stateful tests\n", bold=True)
|
|
881
|
+
elif isinstance(event.data, stateful_events.ScenarioFinished) and not event.data.is_final:
|
|
882
|
+
display_execution_result(context, event.data.status.value)
|
|
883
|
+
elif isinstance(event.data, stateful_events.RunFinished):
|
|
884
|
+
click.echo()
|
|
885
|
+
# It is initialized in `RunStarted`
|
|
886
|
+
sink = cast(StateMachineSink, context.state_machine_sink)
|
|
887
|
+
sink.consume(event.data)
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def handle_after_stateful_execution(context: ExecutionContext, event: events.AfterStatefulExecution) -> None:
|
|
891
|
+
context.results.append(event.result)
|
|
892
|
+
|
|
893
|
+
|
|
870
894
|
class DefaultOutputStyleHandler(EventHandler):
|
|
871
895
|
def handle_event(self, context: ExecutionContext, event: events.ExecutionEvent) -> None:
|
|
872
896
|
"""Choose and execute a proper handler for the given event."""
|
|
873
897
|
if isinstance(event, events.Initialized):
|
|
874
898
|
handle_initialized(context, event)
|
|
875
|
-
|
|
899
|
+
elif isinstance(event, events.BeforeProbing):
|
|
876
900
|
handle_before_probing(context, event)
|
|
877
|
-
|
|
901
|
+
elif isinstance(event, events.AfterProbing):
|
|
878
902
|
handle_after_probing(context, event)
|
|
879
|
-
|
|
903
|
+
elif isinstance(event, events.BeforeAnalysis):
|
|
880
904
|
handle_before_analysis(context, event)
|
|
881
|
-
|
|
905
|
+
elif isinstance(event, events.AfterAnalysis):
|
|
882
906
|
handle_after_analysis(context, event)
|
|
883
|
-
|
|
907
|
+
elif isinstance(event, events.BeforeExecution):
|
|
884
908
|
handle_before_execution(context, event)
|
|
885
|
-
|
|
909
|
+
elif isinstance(event, events.AfterExecution):
|
|
886
910
|
context.hypothesis_output.extend(event.hypothesis_output)
|
|
887
911
|
handle_after_execution(context, event)
|
|
888
|
-
|
|
912
|
+
elif isinstance(event, events.Finished):
|
|
889
913
|
handle_finished(context, event)
|
|
890
|
-
|
|
914
|
+
elif isinstance(event, events.Interrupted):
|
|
891
915
|
handle_interrupted(context, event)
|
|
892
|
-
|
|
916
|
+
elif isinstance(event, events.InternalError):
|
|
893
917
|
handle_internal_error(context, event)
|
|
918
|
+
elif isinstance(event, events.StatefulEvent):
|
|
919
|
+
handle_stateful_event(context, event)
|
|
920
|
+
elif isinstance(event, events.AfterStatefulExecution):
|
|
921
|
+
handle_after_stateful_execution(context, event)
|
schemathesis/cli/output/short.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
2
|
|
|
3
3
|
from ...runner import events
|
|
4
|
+
from ...stateful import events as stateful_events
|
|
4
5
|
from ..context import ExecutionContext
|
|
5
6
|
from ..handlers import EventHandler
|
|
6
7
|
from . import default
|
|
@@ -15,7 +16,13 @@ def handle_after_execution(context: ExecutionContext, event: events.AfterExecuti
|
|
|
15
16
|
context.operations_processed += 1
|
|
16
17
|
context.results.append(event.result)
|
|
17
18
|
context.hypothesis_output.extend(event.hypothesis_output)
|
|
18
|
-
default.display_execution_result(context, event)
|
|
19
|
+
default.display_execution_result(context, event.status.value)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def handle_stateful_event(context: ExecutionContext, event: events.StatefulEvent) -> None:
|
|
23
|
+
if isinstance(event.data, stateful_events.RunStarted):
|
|
24
|
+
click.echo()
|
|
25
|
+
default.handle_stateful_event(context, event)
|
|
19
26
|
|
|
20
27
|
|
|
21
28
|
class ShortOutputStyleHandler(EventHandler):
|
|
@@ -26,23 +33,27 @@ class ShortOutputStyleHandler(EventHandler):
|
|
|
26
33
|
"""
|
|
27
34
|
if isinstance(event, events.Initialized):
|
|
28
35
|
default.handle_initialized(context, event)
|
|
29
|
-
|
|
36
|
+
elif isinstance(event, events.BeforeProbing):
|
|
30
37
|
default.handle_before_probing(context, event)
|
|
31
|
-
|
|
38
|
+
elif isinstance(event, events.AfterProbing):
|
|
32
39
|
default.handle_after_probing(context, event)
|
|
33
|
-
|
|
40
|
+
elif isinstance(event, events.BeforeAnalysis):
|
|
34
41
|
default.handle_before_analysis(context, event)
|
|
35
|
-
|
|
42
|
+
elif isinstance(event, events.AfterAnalysis):
|
|
36
43
|
default.handle_after_analysis(context, event)
|
|
37
|
-
|
|
44
|
+
elif isinstance(event, events.BeforeExecution):
|
|
38
45
|
handle_before_execution(context, event)
|
|
39
|
-
|
|
46
|
+
elif isinstance(event, events.AfterExecution):
|
|
40
47
|
handle_after_execution(context, event)
|
|
41
|
-
|
|
48
|
+
elif isinstance(event, events.Finished):
|
|
42
49
|
if context.operations_count == context.operations_processed:
|
|
43
50
|
click.echo()
|
|
44
51
|
default.handle_finished(context, event)
|
|
45
|
-
|
|
52
|
+
elif isinstance(event, events.Interrupted):
|
|
46
53
|
default.handle_interrupted(context, event)
|
|
47
|
-
|
|
54
|
+
elif isinstance(event, events.InternalError):
|
|
48
55
|
default.handle_internal_error(context, event)
|
|
56
|
+
elif isinstance(event, events.StatefulEvent):
|
|
57
|
+
handle_stateful_event(context, event)
|
|
58
|
+
elif isinstance(event, events.AfterStatefulExecution):
|
|
59
|
+
default.handle_after_stateful_execution(context, event)
|
schemathesis/cli/sanitization.py
CHANGED
schemathesis/code_samples.py
CHANGED
schemathesis/constants.py
CHANGED
|
@@ -6,9 +6,10 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
from ..hooks import HookContext, register, unregister
|
|
7
7
|
|
|
8
8
|
if TYPE_CHECKING:
|
|
9
|
-
from ..models import Case
|
|
10
9
|
from hypothesis import strategies as st
|
|
11
10
|
|
|
11
|
+
from ..models import Case
|
|
12
|
+
|
|
12
13
|
|
|
13
14
|
def install() -> None:
|
|
14
15
|
warnings.warn(
|