schemathesis 4.0.0a8__py3-none-any.whl → 4.0.0a10__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/cli/commands/run/handlers/junitxml.py +1 -1
- schemathesis/core/__init__.py +1 -0
- schemathesis/generation/coverage.py +133 -56
- schemathesis/generation/hypothesis/__init__.py +7 -1
- schemathesis/generation/hypothesis/builder.py +7 -2
- schemathesis/specs/openapi/patterns.py +40 -9
- schemathesis/specs/openapi/schemas.py +5 -1
- {schemathesis-4.0.0a8.dist-info → schemathesis-4.0.0a10.dist-info}/METADATA +1 -1
- {schemathesis-4.0.0a8.dist-info → schemathesis-4.0.0a10.dist-info}/RECORD +12 -12
- {schemathesis-4.0.0a8.dist-info → schemathesis-4.0.0a10.dist-info}/WHEEL +0 -0
- {schemathesis-4.0.0a8.dist-info → schemathesis-4.0.0a10.dist-info}/entry_points.txt +0 -0
- {schemathesis-4.0.0a8.dist-info → schemathesis-4.0.0a10.dist-info}/licenses/LICENSE +0 -0
@@ -23,7 +23,7 @@ class JunitXMLHandler(EventHandler):
|
|
23
23
|
label = event.recorder.label
|
24
24
|
test_case = self.get_or_create_test_case(label)
|
25
25
|
test_case.elapsed_sec += event.elapsed_time
|
26
|
-
if event.status == Status.FAILURE:
|
26
|
+
if event.status == Status.FAILURE and label in ctx.statistic.failures:
|
27
27
|
add_failure(test_case, ctx.statistic.failures[label].values(), ctx)
|
28
28
|
elif event.status == Status.SKIP and event.skip_reason is not None:
|
29
29
|
test_case.add_skipped_info(output=event.skip_reason)
|
schemathesis/core/__init__.py
CHANGED
@@ -16,7 +16,7 @@ from hypothesis_jsonschema import from_schema
|
|
16
16
|
from hypothesis_jsonschema._canonicalise import canonicalish
|
17
17
|
from hypothesis_jsonschema._from_schema import STRING_FORMATS as BUILT_IN_STRING_FORMATS
|
18
18
|
|
19
|
-
from schemathesis.core import NOT_SET
|
19
|
+
from schemathesis.core import INTERNAL_BUFFER_SIZE, NOT_SET
|
20
20
|
from schemathesis.core.compat import RefResolutionError
|
21
21
|
from schemathesis.core.transforms import deepclone
|
22
22
|
from schemathesis.core.validation import has_invalid_characters, is_latin_1_encodable
|
@@ -36,7 +36,8 @@ def json_recursive_strategy(strategy: st.SearchStrategy) -> st.SearchStrategy:
|
|
36
36
|
return st.lists(strategy, max_size=3) | st.dictionaries(st.text(), strategy, max_size=3)
|
37
37
|
|
38
38
|
|
39
|
-
|
39
|
+
NEGATIVE_MODE_MAX_LENGTH_WITH_PATTERN = 100
|
40
|
+
NEGATIVE_MODE_MAX_ITEMS = 15
|
40
41
|
FLOAT_STRATEGY: st.SearchStrategy = st.floats(allow_nan=False, allow_infinity=False).map(_replace_zero_with_nonzero)
|
41
42
|
NUMERIC_STRATEGY: st.SearchStrategy = st.integers() | FLOAT_STRATEGY
|
42
43
|
JSON_STRATEGY: st.SearchStrategy = st.recursive(
|
@@ -159,7 +160,7 @@ class CoverageContext:
|
|
159
160
|
def generate_from_schema(self, schema: dict | bool) -> Any:
|
160
161
|
if isinstance(schema, bool):
|
161
162
|
return 0
|
162
|
-
keys = sorted([k for k in schema if not k.startswith("x-") and k not in ["description", "example"]])
|
163
|
+
keys = sorted([k for k in schema if not k.startswith("x-") and k not in ["description", "example", "examples"]])
|
163
164
|
if keys == ["type"] and isinstance(schema["type"], str) and schema["type"] in STRATEGIES_FOR_TYPE:
|
164
165
|
return cached_draw(STRATEGIES_FOR_TYPE[schema["type"]])
|
165
166
|
if keys == ["format", "type"]:
|
@@ -257,6 +258,9 @@ def _cover_positive_for_type(
|
|
257
258
|
if ty == "object" or ty == "array":
|
258
259
|
template_schema = _get_template_schema(schema, ty)
|
259
260
|
template = ctx.generate_from_schema(template_schema)
|
261
|
+
elif "properties" in schema or "required" in schema:
|
262
|
+
template_schema = _get_template_schema(schema, "object")
|
263
|
+
template = ctx.generate_from_schema(template_schema)
|
260
264
|
else:
|
261
265
|
template = None
|
262
266
|
if GenerationMode.POSITIVE in ctx.generation_modes:
|
@@ -295,6 +299,8 @@ def _cover_positive_for_type(
|
|
295
299
|
yield from _positive_array(ctx, schema, cast(list, template))
|
296
300
|
elif ty == "object":
|
297
301
|
yield from _positive_object(ctx, schema, cast(dict, template))
|
302
|
+
elif "properties" in schema or "required" in schema:
|
303
|
+
yield from _positive_object(ctx, schema, cast(dict, template))
|
298
304
|
|
299
305
|
|
300
306
|
@contextmanager
|
@@ -386,42 +392,59 @@ def cover_schema_iter(
|
|
386
392
|
if k not in seen:
|
387
393
|
yield value_
|
388
394
|
seen.add(k)
|
389
|
-
elif key == "minLength" and 0 < value <
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
|
396
|
-
if new_schema["pattern"] == schema["pattern"]:
|
397
|
-
# Pattern wasn't updated, try to generate a valid value then shrink the string to the required length
|
398
|
-
del new_schema["minLength"]
|
399
|
-
del new_schema["maxLength"]
|
400
|
-
value = ctx.generate_from_schema(new_schema)[:max_length]
|
401
|
-
else:
|
402
|
-
value = ctx.generate_from_schema(new_schema)
|
403
|
-
else:
|
404
|
-
value = ctx.generate_from_schema(new_schema)
|
395
|
+
elif key == "minLength" and 0 < value < INTERNAL_BUFFER_SIZE:
|
396
|
+
if value == 1:
|
397
|
+
# In this case, the only possible negative string is an empty one
|
398
|
+
# The `pattern` value may require an non-empty one and the generation will fail
|
399
|
+
# However, it is fine to violate `pattern` here as it is negative string generation anyway
|
400
|
+
value = ""
|
405
401
|
k = _to_hashable_key(value)
|
406
402
|
if k not in seen:
|
407
403
|
yield NegativeValue(
|
408
404
|
value, description="String smaller than minLength", location=ctx.current_path
|
409
405
|
)
|
410
406
|
seen.add(k)
|
411
|
-
|
407
|
+
else:
|
408
|
+
with suppress(InvalidArgument):
|
409
|
+
min_length = max_length = value - 1
|
410
|
+
new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
|
411
|
+
new_schema.setdefault("type", "string")
|
412
|
+
if "pattern" in new_schema:
|
413
|
+
new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
|
414
|
+
if new_schema["pattern"] == schema["pattern"]:
|
415
|
+
# Pattern wasn't updated, try to generate a valid value then shrink the string to the required length
|
416
|
+
del new_schema["minLength"]
|
417
|
+
del new_schema["maxLength"]
|
418
|
+
value = ctx.generate_from_schema(new_schema)[:max_length]
|
419
|
+
else:
|
420
|
+
value = ctx.generate_from_schema(new_schema)
|
421
|
+
else:
|
422
|
+
value = ctx.generate_from_schema(new_schema)
|
423
|
+
k = _to_hashable_key(value)
|
424
|
+
if k not in seen:
|
425
|
+
yield NegativeValue(
|
426
|
+
value, description="String smaller than minLength", location=ctx.current_path
|
427
|
+
)
|
428
|
+
seen.add(k)
|
429
|
+
elif key == "maxLength" and value < INTERNAL_BUFFER_SIZE:
|
412
430
|
try:
|
413
431
|
min_length = max_length = value + 1
|
414
432
|
new_schema = {**schema, "minLength": min_length, "maxLength": max_length}
|
415
433
|
new_schema.setdefault("type", "string")
|
416
434
|
if "pattern" in new_schema:
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
del new_schema["minLength"]
|
421
|
-
del new_schema["maxLength"]
|
422
|
-
value = ctx.generate_from_schema(new_schema).ljust(max_length, "0")
|
423
|
-
else:
|
435
|
+
if value > NEGATIVE_MODE_MAX_LENGTH_WITH_PATTERN:
|
436
|
+
# Large `maxLength` value can be extremely slow to generate when combined with `pattern`
|
437
|
+
del new_schema["pattern"]
|
424
438
|
value = ctx.generate_from_schema(new_schema)
|
439
|
+
else:
|
440
|
+
new_schema["pattern"] = update_quantifier(schema["pattern"], min_length, max_length)
|
441
|
+
if new_schema["pattern"] == schema["pattern"]:
|
442
|
+
# Pattern wasn't updated, try to generate a valid value then extend the string to the required length
|
443
|
+
del new_schema["minLength"]
|
444
|
+
del new_schema["maxLength"]
|
445
|
+
value = ctx.generate_from_schema(new_schema).ljust(max_length, "0")
|
446
|
+
else:
|
447
|
+
value = ctx.generate_from_schema(new_schema)
|
425
448
|
else:
|
426
449
|
value = ctx.generate_from_schema(new_schema)
|
427
450
|
k = _to_hashable_key(value)
|
@@ -437,11 +460,34 @@ def cover_schema_iter(
|
|
437
460
|
elif key == "required":
|
438
461
|
template = template or ctx.generate_from_schema(_get_template_schema(schema, "object"))
|
439
462
|
yield from _negative_required(ctx, template, value)
|
440
|
-
elif key == "maxItems" and isinstance(value, int) and value <
|
441
|
-
|
442
|
-
#
|
443
|
-
|
444
|
-
|
463
|
+
elif key == "maxItems" and isinstance(value, int) and value < INTERNAL_BUFFER_SIZE:
|
464
|
+
if value > NEGATIVE_MODE_MAX_ITEMS:
|
465
|
+
# It could be extremely slow to generate large arrays
|
466
|
+
# Generate values up to the limit and reuse them to construct the final array
|
467
|
+
new_schema = {
|
468
|
+
**schema,
|
469
|
+
"minItems": NEGATIVE_MODE_MAX_ITEMS,
|
470
|
+
"maxItems": NEGATIVE_MODE_MAX_ITEMS,
|
471
|
+
"type": "array",
|
472
|
+
}
|
473
|
+
if "items" in schema and isinstance(schema["items"], dict):
|
474
|
+
# The schema may have another large array nested, therefore generate covering cases
|
475
|
+
# and use them to build an array for the current schema
|
476
|
+
negative = [case.value for case in cover_schema_iter(ctx, schema["items"])]
|
477
|
+
positive = [case.value for case in cover_schema_iter(ctx.with_positive(), schema["items"])]
|
478
|
+
# Interleave positive & negative values
|
479
|
+
array_value = [value for pair in zip(positive, negative) for value in pair][
|
480
|
+
:NEGATIVE_MODE_MAX_ITEMS
|
481
|
+
]
|
482
|
+
else:
|
483
|
+
array_value = ctx.generate_from_schema(new_schema)
|
484
|
+
|
485
|
+
# Extend the array to be of length value + 1 by repeating its own elements
|
486
|
+
diff = value + 1 - len(array_value)
|
487
|
+
if diff > 0:
|
488
|
+
array_value += (
|
489
|
+
array_value * (diff // len(array_value)) + array_value[: diff % len(array_value)]
|
490
|
+
)
|
445
491
|
k = _to_hashable_key(array_value)
|
446
492
|
if k not in seen:
|
447
493
|
yield NegativeValue(
|
@@ -450,8 +496,21 @@ def cover_schema_iter(
|
|
450
496
|
location=ctx.current_path,
|
451
497
|
)
|
452
498
|
seen.add(k)
|
453
|
-
|
454
|
-
|
499
|
+
else:
|
500
|
+
try:
|
501
|
+
# Force the array to have one more item than allowed
|
502
|
+
new_schema = {**schema, "minItems": value + 1, "maxItems": value + 1, "type": "array"}
|
503
|
+
array_value = ctx.generate_from_schema(new_schema)
|
504
|
+
k = _to_hashable_key(array_value)
|
505
|
+
if k not in seen:
|
506
|
+
yield NegativeValue(
|
507
|
+
array_value,
|
508
|
+
description="Array with more items than allowed by maxItems",
|
509
|
+
location=ctx.current_path,
|
510
|
+
)
|
511
|
+
seen.add(k)
|
512
|
+
except (InvalidArgument, Unsatisfiable):
|
513
|
+
pass
|
455
514
|
elif key == "minItems" and isinstance(value, int) and value > 0:
|
456
515
|
try:
|
457
516
|
# Force the array to have one less item than the minimum
|
@@ -557,7 +616,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
557
616
|
|
558
617
|
seen = set()
|
559
618
|
|
560
|
-
if min_length is not None and min_length <
|
619
|
+
if min_length is not None and min_length < INTERNAL_BUFFER_SIZE:
|
561
620
|
# Exactly the minimum length
|
562
621
|
yield PositiveValue(
|
563
622
|
ctx.generate_from_schema({**schema, "maxLength": min_length}), description="Minimum length string"
|
@@ -566,7 +625,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
566
625
|
|
567
626
|
# One character more than minimum if possible
|
568
627
|
larger = min_length + 1
|
569
|
-
if larger <
|
628
|
+
if larger < INTERNAL_BUFFER_SIZE and larger not in seen and (not max_length or larger <= max_length):
|
570
629
|
yield PositiveValue(
|
571
630
|
ctx.generate_from_schema({**schema, "minLength": larger, "maxLength": larger}),
|
572
631
|
description="Near-boundary length string",
|
@@ -575,7 +634,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
575
634
|
|
576
635
|
if max_length is not None:
|
577
636
|
# Exactly the maximum length
|
578
|
-
if max_length <
|
637
|
+
if max_length < INTERNAL_BUFFER_SIZE and max_length not in seen:
|
579
638
|
yield PositiveValue(
|
580
639
|
ctx.generate_from_schema({**schema, "minLength": max_length}), description="Maximum length string"
|
581
640
|
)
|
@@ -584,7 +643,7 @@ def _positive_string(ctx: CoverageContext, schema: dict) -> Generator[GeneratedV
|
|
584
643
|
# One character less than maximum if possible
|
585
644
|
smaller = max_length - 1
|
586
645
|
if (
|
587
|
-
smaller <
|
646
|
+
smaller < INTERNAL_BUFFER_SIZE
|
588
647
|
and smaller not in seen
|
589
648
|
and (smaller > 0 and (min_length is None or smaller >= min_length))
|
590
649
|
):
|
@@ -683,19 +742,22 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
683
742
|
|
684
743
|
if example or examples or default:
|
685
744
|
if example:
|
745
|
+
seen.add(_to_hashable_key(example))
|
686
746
|
yield PositiveValue(example, description="Example value")
|
687
747
|
if examples:
|
688
748
|
for example in examples:
|
749
|
+
seen.add(_to_hashable_key(example))
|
689
750
|
yield PositiveValue(example, description="Example value")
|
690
751
|
if (
|
691
752
|
default
|
692
753
|
and not (example is not None and default == example)
|
693
754
|
and not (examples is not None and any(default == ex for ex in examples))
|
694
755
|
):
|
756
|
+
seen.add(_to_hashable_key(default))
|
695
757
|
yield PositiveValue(default, description="Default value")
|
696
758
|
else:
|
697
759
|
yield PositiveValue(template, description="Valid array")
|
698
|
-
seen.add(
|
760
|
+
seen.add(_to_hashable_key(template))
|
699
761
|
|
700
762
|
# Boundary and near-boundary sizes
|
701
763
|
min_items = schema.get("minItems")
|
@@ -706,33 +768,48 @@ def _positive_array(ctx: CoverageContext, schema: dict, template: list) -> Gener
|
|
706
768
|
# One item more than minimum if possible
|
707
769
|
larger = min_items + 1
|
708
770
|
if larger not in seen and (max_items is None or larger <= max_items):
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
|
713
|
-
|
771
|
+
value = ctx.generate_from_schema({**schema, "minItems": larger, "maxItems": larger})
|
772
|
+
key = _to_hashable_key(value)
|
773
|
+
if key not in seen:
|
774
|
+
seen.add(key)
|
775
|
+
yield PositiveValue(value, description="Near-boundary items array")
|
714
776
|
|
715
777
|
if max_items is not None:
|
716
|
-
if max_items <
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
778
|
+
if max_items < INTERNAL_BUFFER_SIZE and max_items not in seen:
|
779
|
+
value = ctx.generate_from_schema({**schema, "minItems": max_items})
|
780
|
+
key = _to_hashable_key(value)
|
781
|
+
if key not in seen:
|
782
|
+
seen.add(key)
|
783
|
+
yield PositiveValue(value, description="Maximum items array")
|
722
784
|
|
723
785
|
# One item smaller than maximum if possible
|
724
786
|
smaller = max_items - 1
|
725
787
|
if (
|
726
|
-
smaller <
|
788
|
+
smaller < INTERNAL_BUFFER_SIZE
|
727
789
|
and smaller > 0
|
728
790
|
and smaller not in seen
|
729
791
|
and (min_items is None or smaller >= min_items)
|
730
792
|
):
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
793
|
+
value = ctx.generate_from_schema({**schema, "minItems": smaller, "maxItems": smaller})
|
794
|
+
key = _to_hashable_key(value)
|
795
|
+
if key not in seen:
|
796
|
+
seen.add(key)
|
797
|
+
yield PositiveValue(value, description="Near-boundary items array")
|
798
|
+
|
799
|
+
if "items" in schema and "enum" in schema["items"] and isinstance(schema["items"]["enum"], list) and max_items != 0:
|
800
|
+
# Ensure there is enough items to pass `minItems` if it is specified
|
801
|
+
length = min_items or 1
|
802
|
+
for variant in schema["items"]["enum"]:
|
803
|
+
value = [variant] * length
|
804
|
+
key = _to_hashable_key(value)
|
805
|
+
if key not in seen:
|
806
|
+
seen.add(key)
|
807
|
+
yield PositiveValue(value, description="Enum value from available for items array")
|
808
|
+
elif min_items is None and max_items is None and "items" in schema and isinstance(schema["items"], dict):
|
809
|
+
# Otherwise only an empty array is generated
|
810
|
+
sub_schema = schema["items"]
|
811
|
+
for item in cover_schema_iter(ctx, sub_schema):
|
812
|
+
yield PositiveValue([item.value], description=f"Single-item array: {item.description}")
|
736
813
|
|
737
814
|
|
738
815
|
def _positive_object(ctx: CoverageContext, schema: dict, template: dict) -> Generator[GeneratedValue, None, None]:
|
@@ -5,11 +5,14 @@ DEFAULT_DEADLINE = 15000
|
|
5
5
|
|
6
6
|
|
7
7
|
def setup() -> None:
|
8
|
+
from hypothesis import core as root_core
|
9
|
+
from hypothesis.internal.conjecture import engine
|
8
10
|
from hypothesis.internal.entropy import deterministic_PRNG
|
9
11
|
from hypothesis.internal.reflection import is_first_param_referenced_in_function
|
10
|
-
from hypothesis.strategies._internal import core
|
12
|
+
from hypothesis.strategies._internal import collections, core
|
11
13
|
from hypothesis_jsonschema import _from_schema, _resolve
|
12
14
|
|
15
|
+
from schemathesis.core import INTERNAL_BUFFER_SIZE
|
13
16
|
from schemathesis.core.transforms import deepclone
|
14
17
|
|
15
18
|
# Forcefully initializes Hypothesis' global PRNG to avoid races that initialize it
|
@@ -28,3 +31,6 @@ def setup() -> None:
|
|
28
31
|
core.is_first_param_referenced_in_function = _is_first_param_referenced_in_function # type: ignore
|
29
32
|
_resolve.deepcopy = deepclone # type: ignore
|
30
33
|
_from_schema.deepcopy = deepclone # type: ignore
|
34
|
+
root_core.BUFFER_SIZE = INTERNAL_BUFFER_SIZE # type: ignore
|
35
|
+
engine.BUFFER_SIZE = INTERNAL_BUFFER_SIZE
|
36
|
+
collections.BUFFER_SIZE = INTERNAL_BUFFER_SIZE # type: ignore
|
@@ -1,17 +1,18 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
3
|
import asyncio
|
4
|
+
import os
|
4
5
|
from dataclasses import dataclass, field
|
5
6
|
from enum import Enum
|
6
7
|
from functools import wraps
|
7
8
|
from itertools import combinations
|
8
|
-
import os
|
9
9
|
from time import perf_counter
|
10
10
|
from typing import Any, Callable, Generator, Mapping
|
11
11
|
|
12
12
|
import hypothesis
|
13
13
|
from hypothesis import Phase
|
14
14
|
from hypothesis import strategies as st
|
15
|
+
from hypothesis._settings import all_settings
|
15
16
|
from hypothesis.errors import Unsatisfiable
|
16
17
|
from jsonschema.exceptions import SchemaError
|
17
18
|
|
@@ -98,7 +99,11 @@ def create_test(
|
|
98
99
|
# Merge the user-provided settings with the current ones
|
99
100
|
settings = hypothesis.settings(
|
100
101
|
settings,
|
101
|
-
**{
|
102
|
+
**{
|
103
|
+
item: getattr(config.settings, item)
|
104
|
+
for item in all_settings
|
105
|
+
if getattr(config.settings, item) != getattr(default, item)
|
106
|
+
},
|
102
107
|
)
|
103
108
|
|
104
109
|
if Phase.explain in settings.phases:
|
@@ -69,13 +69,18 @@ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, m
|
|
69
69
|
trailing_anchor_length = _get_anchor_length(parsed[2][1])
|
70
70
|
leading_anchor = pattern[:leading_anchor_length]
|
71
71
|
trailing_anchor = pattern[-trailing_anchor_length:]
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
)
|
72
|
+
# Special case for patterns canonicalisation. Some frameworks generate `\\w\\W` instead of `.`
|
73
|
+
# Such patterns lead to significantly slower data generation
|
74
|
+
if op == sre.IN and _matches_anything(value):
|
75
|
+
op = sre.ANY
|
76
|
+
value = None
|
77
|
+
inner_pattern = "."
|
78
|
+
elif op in REPEATS and len(value[2]) == 1 and value[2][0][0] == sre.IN and _matches_anything(value[2][0][1]):
|
79
|
+
value = (value[0], value[1], [(sre.ANY, None)], *value[3:])
|
80
|
+
inner_pattern = "."
|
81
|
+
else:
|
82
|
+
inner_pattern = pattern[leading_anchor_length:-trailing_anchor_length]
|
83
|
+
return leading_anchor + _update_quantifier(op, value, inner_pattern, min_length, max_length) + trailing_anchor
|
79
84
|
elif (
|
80
85
|
len(parsed) > 3
|
81
86
|
and parsed[0][0] == ANCHOR
|
@@ -86,6 +91,19 @@ def _handle_parsed_pattern(parsed: list, pattern: str, min_length: int | None, m
|
|
86
91
|
return pattern
|
87
92
|
|
88
93
|
|
94
|
+
def _matches_anything(value: list) -> bool:
|
95
|
+
"""Check if the given pattern is equivalent to '.' (match any character)."""
|
96
|
+
# Common forms: [\w\W], [\s\S], etc.
|
97
|
+
return value in (
|
98
|
+
[(sre.CATEGORY, sre.CATEGORY_WORD), (sre.CATEGORY, sre.CATEGORY_NOT_WORD)],
|
99
|
+
[(sre.CATEGORY, sre.CATEGORY_SPACE), (sre.CATEGORY, sre.CATEGORY_NOT_SPACE)],
|
100
|
+
[(sre.CATEGORY, sre.CATEGORY_DIGIT), (sre.CATEGORY, sre.CATEGORY_NOT_DIGIT)],
|
101
|
+
[(sre.CATEGORY, sre.CATEGORY_NOT_WORD), (sre.CATEGORY, sre.CATEGORY_WORD)],
|
102
|
+
[(sre.CATEGORY, sre.CATEGORY_NOT_SPACE), (sre.CATEGORY, sre.CATEGORY_SPACE)],
|
103
|
+
[(sre.CATEGORY, sre.CATEGORY_NOT_DIGIT), (sre.CATEGORY, sre.CATEGORY_DIGIT)],
|
104
|
+
)
|
105
|
+
|
106
|
+
|
89
107
|
def _handle_anchored_pattern(parsed: list, pattern: str, min_length: int | None, max_length: int | None) -> str:
|
90
108
|
"""Update regex pattern with multiple quantified patterns to satisfy length constraints."""
|
91
109
|
# Extract anchors
|
@@ -269,15 +287,28 @@ def _get_anchor_length(node_type: int) -> int:
|
|
269
287
|
return 1 # ^ or $ or their multiline/locale/unicode variants
|
270
288
|
|
271
289
|
|
272
|
-
def _update_quantifier(
|
290
|
+
def _update_quantifier(
|
291
|
+
op: int, value: tuple | None, pattern: str, min_length: int | None, max_length: int | None
|
292
|
+
) -> str:
|
273
293
|
"""Update the quantifier based on the operation type and given constraints."""
|
274
|
-
if op in REPEATS:
|
294
|
+
if op in REPEATS and value is not None:
|
275
295
|
return _handle_repeat_quantifier(value, pattern, min_length, max_length)
|
276
296
|
if op in (LITERAL, IN) and max_length != 0:
|
277
297
|
return _handle_literal_or_in_quantifier(pattern, min_length, max_length)
|
298
|
+
if op == sre.ANY and value is None:
|
299
|
+
# Equivalent to `.` which is in turn is the same as `.{1}`
|
300
|
+
return _handle_repeat_quantifier(
|
301
|
+
SINGLE_ANY,
|
302
|
+
pattern,
|
303
|
+
min_length,
|
304
|
+
max_length,
|
305
|
+
)
|
278
306
|
return pattern
|
279
307
|
|
280
308
|
|
309
|
+
SINGLE_ANY = sre_parse.parse(".{1}")[0][1]
|
310
|
+
|
311
|
+
|
281
312
|
def _handle_repeat_quantifier(
|
282
313
|
value: tuple[int, int, tuple], pattern: str, min_length: int | None, max_length: int | None
|
283
314
|
) -> str:
|
@@ -1195,7 +1195,11 @@ class OpenApi30(SwaggerV20):
|
|
1195
1195
|
files = []
|
1196
1196
|
definition = operation.definition.raw
|
1197
1197
|
if "$ref" in definition["requestBody"]:
|
1198
|
-
|
1198
|
+
self.resolver.push_scope(operation.definition.scope)
|
1199
|
+
try:
|
1200
|
+
body = self.resolver.resolve_all(definition["requestBody"], RECURSION_DEPTH_LIMIT)
|
1201
|
+
finally:
|
1202
|
+
self.resolver.pop_scope()
|
1199
1203
|
else:
|
1200
1204
|
body = definition["requestBody"]
|
1201
1205
|
content = body["content"]
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: schemathesis
|
3
|
-
Version: 4.0.
|
3
|
+
Version: 4.0.0a10
|
4
4
|
Summary: Property-based testing framework for Open API and GraphQL based apps
|
5
5
|
Project-URL: Documentation, https://schemathesis.readthedocs.io/en/stable/
|
6
6
|
Project-URL: Changelog, https://schemathesis.readthedocs.io/en/stable/changelog.html
|
@@ -25,7 +25,7 @@ schemathesis/cli/commands/run/validation.py,sha256=cpGG5hFc4lHVemXrQXRvrlNlqBmMq
|
|
25
25
|
schemathesis/cli/commands/run/handlers/__init__.py,sha256=TPZ3KdGi8m0fjlN0GjA31MAXXn1qI7uU4FtiDwroXZI,1915
|
26
26
|
schemathesis/cli/commands/run/handlers/base.py,sha256=yDsTtCiztLksfk7cRzg8JlaAVOfS-zwK3tsJMOXAFyc,530
|
27
27
|
schemathesis/cli/commands/run/handlers/cassettes.py,sha256=SVk13xPhsQduCpgvvBwzEMDNTju-SHQCW90xTQ6iL1U,18525
|
28
|
-
schemathesis/cli/commands/run/handlers/junitxml.py,sha256=
|
28
|
+
schemathesis/cli/commands/run/handlers/junitxml.py,sha256=OhBS8JY4DXQGsQ0JjaHp19QDBR2hye30a-XulVzs-rg,2386
|
29
29
|
schemathesis/cli/commands/run/handlers/output.py,sha256=n25QDGvYMXPKRPHBDckkDVwkkieY3Gq4HHSG0h3paTA,58800
|
30
30
|
schemathesis/cli/ext/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
31
|
schemathesis/cli/ext/fs.py,sha256=OA3mRzra4rq3NyDTcBvlRh0WJrh4ByN-QQ8loI04m88,408
|
@@ -34,7 +34,7 @@ schemathesis/cli/ext/options.py,sha256=gBjfYPoiSoxCymWq41x0oKcQ2frv1fQnweETVpYiI
|
|
34
34
|
schemathesis/contrib/__init__.py,sha256=wxpX86xrEGRAS3f7eugQfKVbnqV6ZfOqFBS_DmWxOok,120
|
35
35
|
schemathesis/contrib/openapi/__init__.py,sha256=-7mBZ9RQj0EGzzmC-HKiT5ZslwHcoWFqCVpRG0GHO_o,162
|
36
36
|
schemathesis/contrib/openapi/fill_missing_examples.py,sha256=BfBpuy3vCKbE_uILqPXnm7kxEDopAr5tNQwP5E9xX8A,585
|
37
|
-
schemathesis/core/__init__.py,sha256=
|
37
|
+
schemathesis/core/__init__.py,sha256=s1Td4mecrTr31BjBSdnxPSWPPGQERkAAAEPZBBK_9AE,1794
|
38
38
|
schemathesis/core/compat.py,sha256=Lflo6z-nQ6S4uKZINc4Fr90pd3LTN6cIG9HJJmmaHeY,754
|
39
39
|
schemathesis/core/control.py,sha256=IzwIc8HIAEMtZWW0Q0iXI7T1niBpjvcLlbuwOSmy5O8,130
|
40
40
|
schemathesis/core/curl.py,sha256=yuaCe_zHLGwUjEeloQi6W3tOA3cGdnHDNI17-5jia0o,1723
|
@@ -74,13 +74,13 @@ schemathesis/engine/phases/unit/_pool.py,sha256=9OgmFd-ov1AAvcZGquK40PXkGLp7f2qC
|
|
74
74
|
schemathesis/experimental/__init__.py,sha256=jYY3Mq6okqTRTMudPzcaT0JVjzJW5IN_ZVJdGU0stBs,2011
|
75
75
|
schemathesis/generation/__init__.py,sha256=sWTRPTh-qDNkSfpM9rYI3v8zskH8_wFKUuPRg18fZI8,1627
|
76
76
|
schemathesis/generation/case.py,sha256=Rt5MCUtPVYVQzNyjUx8magocPJpHV1svyuqQSTwUE-I,7306
|
77
|
-
schemathesis/generation/coverage.py,sha256=
|
77
|
+
schemathesis/generation/coverage.py,sha256=0iQZfm6yECy_nXatU3dCDCFI4lZW-IsiGZAiljptGUE,46362
|
78
78
|
schemathesis/generation/meta.py,sha256=36h6m4E7jzLGa8TCvl7eBl_xUWLiRul3qxzexl5cB58,2515
|
79
79
|
schemathesis/generation/modes.py,sha256=t_EvKr2aOXYMsEfdMu4lLF4KCGcX1LVVyvzTkcpJqhk,663
|
80
80
|
schemathesis/generation/overrides.py,sha256=FhqcFoliEvgW6MZyFPYemfLgzKt3Miy8Cud7OMOCb7g,3045
|
81
81
|
schemathesis/generation/targets.py,sha256=_rN2qgxTE2EfvygiN-Fy3WmDnRH0ERohdx3sKRDaYhU,2120
|
82
|
-
schemathesis/generation/hypothesis/__init__.py,sha256=
|
83
|
-
schemathesis/generation/hypothesis/builder.py,sha256=
|
82
|
+
schemathesis/generation/hypothesis/__init__.py,sha256=SVwM-rx07jPZzms0idWYACgUtWAxh49HRuTnaQ__zf0,1549
|
83
|
+
schemathesis/generation/hypothesis/builder.py,sha256=QDfZRpFjQ0KYFPgu2BVSlxop0TQL7fQc201jOMR4rSQ,30472
|
84
84
|
schemathesis/generation/hypothesis/examples.py,sha256=6eGaKUEC3elmKsaqfKj1sLvM8EHc-PWT4NRBq4NI0Rs,1409
|
85
85
|
schemathesis/generation/hypothesis/given.py,sha256=sTZR1of6XaHAPWtHx2_WLlZ50M8D5Rjux0GmWkWjDq4,2337
|
86
86
|
schemathesis/generation/hypothesis/reporting.py,sha256=uDVow6Ya8YFkqQuOqRsjbzsbyP4KKfr3jA7ZaY4FuKY,279
|
@@ -121,9 +121,9 @@ schemathesis/specs/openapi/examples.py,sha256=Xvjp60QUcLaeGsJRbi2i6XM15_4uO0ceVo
|
|
121
121
|
schemathesis/specs/openapi/formats.py,sha256=ViVF3aFeFI1ctwGQbiRDXhU3so82P0BCaF2aDDbUUm8,2816
|
122
122
|
schemathesis/specs/openapi/media_types.py,sha256=ADedOaNWjbAtAekyaKmNj9fY6zBTeqcNqBEjN0EWNhI,1014
|
123
123
|
schemathesis/specs/openapi/parameters.py,sha256=tVL61gDe9A8_jwoVKZZvpXKPerMyq7vkAvwdMsi44TI,14622
|
124
|
-
schemathesis/specs/openapi/patterns.py,sha256=
|
124
|
+
schemathesis/specs/openapi/patterns.py,sha256=EQdf4net9QtwngKv36FEr7l0-3_afIMrrBdpKUWGWGc,14382
|
125
125
|
schemathesis/specs/openapi/references.py,sha256=c8Ufa8hp6Dyf-gPn5lpmyqF_GtqXIBWoKkj3bk3WaPA,8871
|
126
|
-
schemathesis/specs/openapi/schemas.py,sha256=
|
126
|
+
schemathesis/specs/openapi/schemas.py,sha256=dL1uLz_twgJZUdYBcs2JJ3b8ZlQH3nrGUg1p78pm9Os,55169
|
127
127
|
schemathesis/specs/openapi/security.py,sha256=6UWYMhL-dPtkTineqqBFNKca1i4EuoTduw-EOLeE0aQ,7149
|
128
128
|
schemathesis/specs/openapi/serialization.py,sha256=VdDLmeHqxlWM4cxQQcCkvrU6XurivolwEEaT13ohelA,11972
|
129
129
|
schemathesis/specs/openapi/utils.py,sha256=ER4vJkdFVDIE7aKyxyYatuuHVRNutytezgE52pqZNE8,900
|
@@ -146,8 +146,8 @@ schemathesis/transport/prepare.py,sha256=qQ6zXBw5NN2AIM0bzLAc5Ryc3dmMb0R6xN14lnR
|
|
146
146
|
schemathesis/transport/requests.py,sha256=j5wI1Uo_PnVuP1eV8l6ddsXosyxAPQ1mLSyWEZmTI9I,8747
|
147
147
|
schemathesis/transport/serialization.py,sha256=jIMra1LqRGav0OX3Hx7mvORt38ll4cd2DKit2D58FN0,10531
|
148
148
|
schemathesis/transport/wsgi.py,sha256=RWSuUXPrl91GxAy8a4jyNNozOWVMRBxKx_tljlWA_Lo,5697
|
149
|
-
schemathesis-4.0.
|
150
|
-
schemathesis-4.0.
|
151
|
-
schemathesis-4.0.
|
152
|
-
schemathesis-4.0.
|
153
|
-
schemathesis-4.0.
|
149
|
+
schemathesis-4.0.0a10.dist-info/METADATA,sha256=goxBn_HTzqqh7sSkqXpopuF5lT2tMxNJNcwyso9QtEQ,10428
|
150
|
+
schemathesis-4.0.0a10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
151
|
+
schemathesis-4.0.0a10.dist-info/entry_points.txt,sha256=hiK3un-xfgPdwj9uj16YVDtTNpO128bmk0U82SMv8ZQ,152
|
152
|
+
schemathesis-4.0.0a10.dist-info/licenses/LICENSE,sha256=2Ve4J8v5jMQAWrT7r1nf3bI8Vflk3rZVQefiF2zpxwg,1121
|
153
|
+
schemathesis-4.0.0a10.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|