def test_floats_in_tiny_interval_within_bounds(data, center):
    assume(not (math.isinf(next_down(center)) or math.isinf(next_up(center))))
    lo = Decimal.from_float(next_down(center)).next_plus()
    hi = Decimal.from_float(next_up(center)).next_minus()
    assert float(lo) < lo < center < hi < float(hi)
    val = data.draw(st.floats(lo, hi))
    assert lo < val < hi
Exemple #2
0
def test_floats_in_tiny_interval_within_bounds(data, center):
    assume(not (math.isinf(next_down(center)) or math.isinf(next_up(center))))
    lo = Decimal.from_float(next_down(center)).next_plus()
    hi = Decimal.from_float(next_up(center)).next_minus()
    assert float(lo) < lo < center < hi < float(hi)
    val = data.draw(st.floats(lo, hi))
    assert lo < val < hi
Exemple #3
0
def test_fuzz_floats_bounds(data):
    width = data.draw(sampled_from([64, 32, 16]))
    bound = none() | floats(allow_nan=False, width=width)
    low, high = data.draw(tuples(bound, bound), label="low, high")
    if low is not None and high is not None and low > high:
        low, high = high, low
    if low is not None and high is not None and low > high:
        low, high = high, low
    exmin = low is not None and low != inf and data.draw(booleans(),
                                                         label="ex_min")
    exmax = high is not None and high != -inf and data.draw(booleans(),
                                                            label="ex_max")

    if low is not None and high is not None:
        lo = next_up(low, width) if exmin else low
        hi = next_down(high, width) if exmax else high
        # There must actually be floats between these bounds
        assume(lo <= hi)
        if lo == hi == 0:
            assume(not exmin and not exmax
                   and copysign(1.0, lo) <= copysign(1.0, hi))

    s = floats(low, high, exclude_min=exmin, exclude_max=exmax, width=width)
    val = data.draw(s, label="value")
    assume(val)  # positive/negative zero is an issue

    if low is not None:
        assert low <= val
    if high is not None:
        assert val <= high
    if exmin:
        assert low != val
    if exmax:
        assert high != val
def test_up_means_greater(x):
    hi = next_up(x)
    if not x < hi:
        assert (
            (math.isnan(x) and math.isnan(hi))
            or (x > 0 and math.isinf(x))
            or (x == hi == 0 and is_negative(x) and not is_negative(hi))
        )
Exemple #5
0
def test_up_means_greater(x):
    hi = next_up(x)
    if not x < hi:
        assert (
            (math.isnan(x) and math.isnan(hi))
            or (x > 0 and math.isinf(x))
            or (x == hi == 0 and is_negative(x) and not is_negative(hi))
        )
Exemple #6
0
def flushes_to_zero(xp, width: int) -> bool:
    """Infer whether build of array module has its float dtype of the specified
    width flush subnormals to zero

    We do this per-width because compilers might FTZ for one dtype but allow
    subnormals in the other.
    """
    if width not in [32, 64]:
        raise ValueError(f"{width=}, but should be either 32 or 64")
    dtype = getattr(xp, f"float{width}")
    return bool(xp.asarray(next_up(0.0, width=width), dtype=dtype) == 0)
Exemple #7
0
def get_number_bounds(
    schema: Schema, ) -> Tuple[Optional[float], Optional[float], bool, bool]:
    """Get the min and max allowed floats, and whether they are exclusive."""
    lower, upper, exmin, exmax = _get_numeric_bounds(schema)
    if lower is not None:
        lo = float(lower)
        if lo < lower:
            lo = next_up(lo)
            exmin = False
        lower = lo
    if upper is not None:
        hi = float(upper)
        if hi > upper:
            hi = next_down(hi)
            exmax = False
        upper = hi
    return lower, upper, exmin, exmax
Exemple #8
0
def get_number_bounds(
    schema: Schema,
    *,
    _for_integer: bool = False,
) -> Tuple[Optional[float], Optional[float], bool, bool]:
    """Get the min and max allowed floats, and whether they are exclusive."""
    assert "number" in get_type(schema) or _for_integer

    lower = schema.get("minimum")
    upper = schema.get("maximum")
    exmin = schema.get("exclusiveMinimum", False)
    exmax = schema.get("exclusiveMaximum", False)
    assert lower is None or isinstance(lower, (int, float))
    assert upper is None or isinstance(upper, (int, float))
    assert isinstance(exmin, (bool, int, float))
    assert isinstance(exmax, (bool, int, float))

    # Canonicalise to number-and-boolean representation
    if exmin is not True and exmin is not False:
        if lower is None or exmin >= lower:
            lower, exmin = exmin, True
        else:
            exmin = False
    if exmax is not True and exmax is not False:
        if upper is None or exmax <= upper:
            upper, exmax = exmax, True
        else:
            exmax = False
    assert isinstance(exmin, bool)
    assert isinstance(exmax, bool)

    # Adjust bounds and cast to float
    if lower is not None and not _for_integer:
        lo = float(lower)
        if lo < lower:
            lo = next_up(lo)
            exmin = False
        lower = lo
    if upper is not None and not _for_integer:
        hi = float(upper)
        if hi > upper:
            hi = next_down(hi)
            exmax = False
        upper = hi

    return lower, upper, exmin, exmax
Exemple #9
0
def test_up_means_greater(x):
    hi = next_up(x)
    if not x < hi:
        assert (math.isnan(x) and math.isnan(hi)) or (x > 0 and math.isinf(x))
Exemple #10
0
def canonicalish(schema: JSONType) -> Dict[str, Any]:
    """Convert a schema into a more-canonical form.

    This is obviously incomplete, but improves best-effort recognition of
    equivalent schemas and makes conversion logic simpler.
    """
    if schema is True:
        return {}
    elif schema is False:
        return {"not": {}}

    # Make a copy, so we don't mutate the existing schema in place.
    # Using the canonical encoding makes all integer-valued floats into ints.
    schema = json.loads(encode_canonical_json(schema))

    # Otherwise, we're dealing with "objects", i.e. dicts.
    if not isinstance(schema, dict):
        raise InvalidArgument(
            f"Got schema={schema} of type {type(schema).__name__}, "
            "but expected a dict.")

    if "const" in schema:
        if not make_validator(schema).is_valid(schema["const"]):
            return FALSEY
        return {"const": schema["const"]}
    if "enum" in schema:
        validator = make_validator(schema)
        enum_ = sorted((v for v in schema["enum"] if validator.is_valid(v)),
                       key=sort_key)
        if not enum_:
            return FALSEY
        elif len(enum_) == 1:
            return {"const": enum_[0]}
        return {"enum": enum_}
    # Recurse into the value of each keyword with a schema (or list of them) as a value
    for key in SCHEMA_KEYS:
        if isinstance(schema.get(key), list):
            schema[key] = [canonicalish(v) for v in schema[key]]
        elif isinstance(schema.get(key), (bool, dict)):
            schema[key] = canonicalish(schema[key])
        else:
            assert key not in schema
    for key in SCHEMA_OBJECT_KEYS:
        if key in schema:
            schema[key] = {
                k: v if isinstance(v, list) else canonicalish(v)
                for k, v in schema[key].items()
            }

    type_ = get_type(schema)
    if "number" in type_:
        if schema.get("exclusiveMinimum") is False:
            del schema["exclusiveMinimum"]
        if schema.get("exclusiveMaximum") is False:
            del schema["exclusiveMaximum"]
        lo, hi, exmin, exmax = get_number_bounds(schema)
        mul = schema.get("multipleOf")
        if isinstance(mul, int):
            # Numbers which are a multiple of an integer?  That's the integer type.
            type_.remove("number")
            type_ = [t for t in TYPE_STRINGS if t in type_ or t == "integer"]
        elif lo is not None and hi is not None:
            lobound = next_up(lo) if exmin else lo
            hibound = next_down(hi) if exmax else hi
            if (mul and not has_divisibles(lo, hi, mul, exmin, exmax)
                ) or lobound > hibound:
                type_.remove("number")
            elif type_ == ["number"] and lobound == hibound:
                return {"const": lobound}

    if "integer" in type_:
        lo, hi = get_integer_bounds(schema)
        mul = schema.get("multipleOf")
        if lo is not None and isinstance(mul, int) and mul > 1 and (lo % mul):
            lo += mul - (lo % mul)
        if hi is not None and isinstance(mul, int) and mul > 1 and (hi % mul):
            hi -= hi % mul

        if lo is not None:
            schema["minimum"] = lo
            schema.pop("exclusiveMinimum", None)
        if hi is not None:
            schema["maximum"] = hi
            schema.pop("exclusiveMaximum", None)

        if lo is not None and hi is not None and lo > hi:
            type_.remove("integer")

    if "array" in type_ and "contains" in schema:
        if isinstance(schema.get("items"), dict):
            contains_items = merged([schema["contains"], schema["items"]])
            if contains_items is not None:
                schema["contains"] = contains_items

        if schema["contains"] == FALSEY:
            type_.remove("array")
        else:
            schema["minItems"] = max(schema.get("minItems", 0), 1)
        if schema["contains"] == TRUTHY:
            schema.pop("contains")
            schema["minItems"] = max(schema.get("minItems", 1), 1)
    if "array" in type_ and schema.get("minItems", 0) > schema.get(
            "maxItems", math.inf):
        type_.remove("array")
    if ("array" in type_ and "minItems" in schema
            and isinstance(schema.get("items", []), dict)):
        count = upper_bound_instances(schema["items"])
        if (count == 0 and schema["minItems"] > 0) or (schema.get(
                "uniqueItems", False) and count < schema["minItems"]):
            type_.remove("array")
    if "array" in type_ and isinstance(schema.get("items"), list):
        schema["items"] = schema["items"][:schema.get("maxItems")]
        for idx, s in enumerate(schema["items"]):
            if s == FALSEY:
                schema["items"] = schema["items"][:idx]
                schema["maxItems"] = idx
                schema.pop("additionalItems", None)
                break
        if schema.get("minItems", 0) > min(
                len(schema["items"]) +
                upper_bound_instances(schema.get("additionalItems", TRUTHY)),
                schema.get("maxItems", math.inf),
        ):
            type_.remove("array")
    if ("array" in type_ and isinstance(schema.get("items"), list)
            and schema.get("additionalItems") == FALSEY):
        schema.pop("maxItems", None)
    if "array" in type_ and (schema.get("items") == FALSEY
                             or schema.get("maxItems", 1) == 0):
        schema["maxItems"] = 0
        schema.pop("items", None)
        schema.pop("uniqueItems", None)
        schema.pop("additionalItems", None)
    if "array" in type_ and schema.get("items", TRUTHY) == TRUTHY:
        schema.pop("items", None)
    if ("properties" in schema and not schema.get("patternProperties")
            and schema.get("additionalProperties") == FALSEY):
        schema["maxProperties"] = min(schema.get("maxProperties", math.inf),
                                      len(schema["properties"]))
    if "object" in type_ and schema.get("minProperties", 0) > schema.get(
            "maxProperties", math.inf):
        type_.remove("object")
    # Remove no-op requires
    if "required" in schema and not schema["required"]:
        schema.pop("required")
    # Canonicalise "required" schemas to remove redundancy
    if "object" in type_ and "required" in schema:
        assert isinstance(schema["required"], list)
        reqs = set(schema["required"])
        if schema.get("dependencies"):
            # When the presence of a required property requires other properties via
            # dependencies, those properties can be moved to the base required keys.
            dep_names = {
                k: sorted(v)
                for k, v in schema["dependencies"].items()
                if isinstance(v, list)
            }
            while reqs.intersection(dep_names):
                for r in reqs.intersection(dep_names):
                    reqs.update(dep_names.pop(r))
            for k, v in list(schema["dependencies"].items()):
                if isinstance(v, list) and k not in dep_names:
                    schema["dependencies"].pop(k)
        schema["required"] = sorted(reqs)
        max_ = schema.get("maxProperties", float("inf"))
        assert isinstance(max_, (int, float))
        if len(schema["required"]) > max_:
            type_.remove("object")
        else:
            propnames = schema.get("propertyNames", {})
            validator = make_validator(propnames)
            if not all(
                    validator.is_valid(name) for name in schema["required"]):
                type_.remove("object")

    for t, kw in TYPE_SPECIFIC_KEYS:
        numeric = {"number", "integer"}
        if t in type_ or (t in numeric and numeric.intersection(type_)):
            continue
        for k in kw.split():
            schema.pop(k, None)

    # Canonicalise "not" subschemas
    if "not" in schema:
        not_ = schema.pop("not")
        if not_ == TRUTHY or not_ == schema:
            # If everything is rejected, discard all other (irrelevant) keys
            # TODO: more sensitive detection of cases where the not-clause
            # excludes everything in the schema.
            return FALSEY
        type_keys = {k: set(v.split()) for k, v in TYPE_SPECIFIC_KEYS}
        type_constraints = {"type"}
        for v in type_keys.values():
            type_constraints |= v
        if set(not_).issubset(type_constraints):
            not_["type"] = get_type(not_)
            for t in set(type_).intersection(not_["type"]):
                if not type_keys.get(t, set()).intersection(not_):
                    type_.remove(t)
                    if t not in ("integer", "number"):
                        not_["type"].remove(t)
            not_ = canonicalish(not_)
        if not_ != FALSEY:
            # If the "not" key rejects nothing, discard it
            schema["not"] = not_
    assert isinstance(type_, list), type_
    if not type_:
        assert type_ == []
        return FALSEY
    if type_ == ["null"]:
        return {"const": None}
    if type_ == ["boolean"]:
        return {"enum": [False, True]}
    if type_ == ["null", "boolean"]:
        return {"enum": [None, False, True]}
    if len(type_) == 1:
        schema["type"] = type_[0]
    elif type_ == get_type({}):
        schema.pop("type", None)
    else:
        schema["type"] = type_
    # Canonicalise "xxxOf" lists; in each case canonicalising and sorting the
    # sub-schemas then handling any key-specific logic.
    if TRUTHY in schema.get("anyOf", ()):
        schema.pop("anyOf", None)
    if "anyOf" in schema:
        schema["anyOf"] = sorted(schema["anyOf"], key=encode_canonical_json)
        schema["anyOf"] = [s for s in schema["anyOf"] if s != FALSEY]
        if not schema["anyOf"]:
            return FALSEY
        if len(schema) == len(schema["anyOf"]) == 1:
            return schema["anyOf"][0]  # type: ignore
    if "allOf" in schema:
        schema["allOf"] = [
            json.loads(enc)
            for enc in sorted(set(map(encode_canonical_json, schema["allOf"])))
        ]
        if any(s == FALSEY for s in schema["allOf"]):
            return FALSEY
        if all(s == TRUTHY for s in schema["allOf"]):
            schema.pop("allOf")
        elif len(schema) == len(schema["allOf"]) == 1:
            return schema["allOf"][0]  # type: ignore
        else:
            tmp = schema.copy()
            ao = tmp.pop("allOf")
            out = merged([tmp] + ao)
            if isinstance(out, dict):  # pragma: no branch
                schema = out
                # TODO: this assertion is soley because mypy 0.750 doesn't know
                # that `schema` is a dict otherwise. Needs minimal report upstream.
                assert isinstance(schema, dict)
    if "oneOf" in schema:
        one_of = schema.pop("oneOf")
        assert isinstance(one_of, list)
        one_of = sorted(one_of, key=encode_canonical_json)
        one_of = [s for s in one_of if s != FALSEY]
        if len(one_of) == 1:
            m = merged([schema, one_of[0]])
            if m is not None:  # pragma: no branch
                return m
        if (not one_of) or one_of.count(TRUTHY) > 1:
            return FALSEY
        schema["oneOf"] = one_of
    # if/then/else schemas are ignored unless if and another are present
    if "if" not in schema:
        schema.pop("then", None)
        schema.pop("else", None)
    if "then" not in schema and "else" not in schema:
        schema.pop("if", None)
    if schema.get("uniqueItems") is False:
        del schema["uniqueItems"]
    return schema
Exemple #11
0
    except TypeError:
        # The select interface for entry_points was introduced in py3.10,
        # supplanting its dict interface. We fallback to the dict interface so
        # we can still find entry points in py3.8 and py3.9.
        eps = entry_points().get("array_api", [])
    return {ep.name: ep for ep in eps}


# We try importing the Array API namespace from NumPy first, which modern
# versions should include. If not available we default to our own mocked module,
# which should allow our test suite to still work. A constant is set accordingly
# to inform our test suite of whether the array module here is a mock or not.
modules = installed_array_modules()
try:
    with catch_warnings():  # NumPy currently warns on import
        xp = modules["numpy"].load()
except KeyError:
    xp = mock_xp
    with pytest.warns(HypothesisWarning):
        xps = make_strategies_namespace(xp)
    COMPLIANT_XP = False
else:
    xps = make_strategies_namespace(xp)
    COMPLIANT_XP = True

# Infer whether build of array module has its float flush subnormals to zero
WIDTHS_FTZ = {
    32: bool(xp.asarray(next_up(0.0, width=32), dtype=xp.float32) == 0),
    64: bool(xp.asarray(next_up(0.0, width=64), dtype=xp.float64) == 0),
}
def test_updown_roundtrip(val):
    assert val == next_up(next_down(val))
    assert val == next_down(next_up(val))
def test_up_means_greater(x):
    hi = next_up(x)
    if not x < hi:
        assert (math.isnan(x) and math.isnan(hi)) or (x > 0 and math.isinf(x))
Exemple #14
0
def canonicalish(schema: JSONType) -> Dict[str, Any]:
    """Convert a schema into a more-canonical form.

    This is obviously incomplete, but improves best-effort recognition of
    equivalent schemas and makes conversion logic simpler.
    """
    if schema is True:
        return {}
    elif schema is False:
        return {"not": {}}

    # Make a copy, so we don't mutate the existing schema in place.
    # Using the canonical encoding makes all integer-valued floats into ints.
    schema = json.loads(encode_canonical_json(schema))

    # Otherwise, we're dealing with "objects", i.e. dicts.
    if not isinstance(schema, dict):
        raise InvalidArgument(
            f"Got schema={schema!r} of type {type(schema).__name__}, "
            "but expected a dict.")

    if "const" in schema:
        if not make_validator(schema).is_valid(schema["const"]):
            return FALSEY
        return {"const": schema["const"]}
    if "enum" in schema:
        validator = make_validator(schema)
        enum_ = sorted((v for v in schema["enum"] if validator.is_valid(v)),
                       key=sort_key)
        if not enum_:
            return FALSEY
        elif len(enum_) == 1:
            return {"const": enum_[0]}
        return {"enum": enum_}
    # if/then/else schemas are ignored unless if and another are present
    if_ = schema.pop("if", None)
    then = schema.pop("then", schema)
    else_ = schema.pop("else", schema)
    if if_ is not None and (then is not schema or else_ is not schema):
        if then not in (if_, TRUTHY) or else_ != TRUTHY:
            alternatives = [
                {
                    "allOf": [if_, then, schema]
                },
                {
                    "allOf": [{
                        "not": if_
                    }, else_, schema]
                },
            ]
            schema = canonicalish({"anyOf": alternatives})
    assert isinstance(schema, dict)
    # Recurse into the value of each keyword with a schema (or list of them) as a value
    for key in SCHEMA_KEYS:
        if isinstance(schema.get(key), list):
            schema[key] = [canonicalish(v) for v in schema[key]]
        elif isinstance(schema.get(key), (bool, dict)):
            schema[key] = canonicalish(schema[key])
        else:
            assert key not in schema, (key, schema[key])
    for key in SCHEMA_OBJECT_KEYS:
        if key in schema:
            schema[key] = {
                k: v if isinstance(v, list) else canonicalish(v)
                for k, v in schema[key].items()
            }

    type_ = get_type(schema)
    if "number" in type_:
        if schema.get("exclusiveMinimum") is False:
            del schema["exclusiveMinimum"]
        if schema.get("exclusiveMaximum") is False:
            del schema["exclusiveMaximum"]
        lo, hi, exmin, exmax = get_number_bounds(schema)
        mul = schema.get("multipleOf")
        if isinstance(mul, int):
            # Numbers which are a multiple of an integer?  That's the integer type.
            type_.remove("number")
            type_ = [t for t in TYPE_STRINGS if t in type_ or t == "integer"]
        elif lo is not None and hi is not None:
            lobound = next_up(lo) if exmin else lo
            hibound = next_down(hi) if exmax else hi
            if (mul and not has_divisibles(lo, hi, mul, exmin, exmax)
                ) or lobound > hibound:
                type_.remove("number")
            elif type_ == ["number"] and lobound == hibound:
                return {"const": lobound}

    if "integer" in type_:
        lo, hi = get_integer_bounds(schema)
        mul = schema.get("multipleOf")
        if lo is not None and isinstance(mul, int) and mul > 1 and (lo % mul):
            lo += mul - (lo % mul)
        if hi is not None and isinstance(mul, int) and mul > 1 and (hi % mul):
            hi -= hi % mul

        if lo is not None:
            schema["minimum"] = lo
            schema.pop("exclusiveMinimum", None)
        if hi is not None:
            schema["maximum"] = hi
            schema.pop("exclusiveMaximum", None)

        if lo is not None and hi is not None and lo > hi:
            type_.remove("integer")

    if "array" in type_ and "contains" in schema:
        if isinstance(schema.get("items"), dict):
            contains_items = merged([schema["contains"], schema["items"]])
            if contains_items is not None:
                schema["contains"] = contains_items

        if schema["contains"] == FALSEY:
            type_.remove("array")
        else:
            schema["minItems"] = max(schema.get("minItems", 0), 1)
        if schema["contains"] == TRUTHY:
            schema.pop("contains")
            schema["minItems"] = max(schema.get("minItems", 1), 1)
    if ("array" in type_ and "uniqueItems" in schema
            and isinstance(schema.get("items", []), dict)):
        item_count = upper_bound_instances(schema["items"])
        if math.isfinite(item_count):
            schema["maxItems"] = min(item_count,
                                     schema.get("maxItems", math.inf))
    if "array" in type_ and schema.get("minItems", 0) > schema.get(
            "maxItems", math.inf):
        type_.remove("array")
    if ("array" in type_ and "minItems" in schema
            and isinstance(schema.get("items", []), dict)):
        count = upper_bound_instances(schema["items"])
        if (count == 0 and schema["minItems"] > 0) or (schema.get(
                "uniqueItems", False) and count < schema["minItems"]):
            type_.remove("array")
    if "array" in type_ and isinstance(schema.get("items"), list):
        schema["items"] = schema["items"][:schema.get("maxItems")]
        for idx, s in enumerate(schema["items"]):
            if s == FALSEY:
                schema["items"] = schema["items"][:idx]
                schema["maxItems"] = idx
                schema.pop("additionalItems", None)
                break
        if schema.get("minItems", 0) > min(
                len(schema["items"]) +
                upper_bound_instances(schema.get("additionalItems", TRUTHY)),
                schema.get("maxItems", math.inf),
        ):
            type_.remove("array")
    if ("array" in type_ and isinstance(schema.get("items"), list)
            and schema.get("additionalItems") == FALSEY):
        schema.pop("maxItems", None)
    if "array" in type_ and (schema.get("items") == FALSEY
                             or schema.get("maxItems", 1) == 0):
        schema["maxItems"] = 0
        schema.pop("items", None)
        schema.pop("uniqueItems", None)
        schema.pop("additionalItems", None)
    if "array" in type_ and schema.get("items", TRUTHY) == TRUTHY:
        schema.pop("items", None)
    if ("properties" in schema and not schema.get("patternProperties")
            and schema.get("additionalProperties") == FALSEY):
        max_props = schema.get("maxProperties", math.inf)
        assert isinstance(max_props, (int, float))
        schema["maxProperties"] = min(max_props, len(schema["properties"]))
    if "object" in type_ and schema.get("minProperties", 0) > schema.get(
            "maxProperties", math.inf):
        type_.remove("object")
    # Discard dependencies values that don't restrict anything
    for k, v in schema.get("dependencies", {}).copy().items():
        if v == [] or v == TRUTHY:
            schema["dependencies"].pop(k)
    # Remove no-op keywords
    for kw, identity in {
            "minItems": 0,
            "items": {},
            "additionalItems": {},
            "dependencies": {},
            "minProperties": 0,
            "properties": {},
            "propertyNames": {},
            "patternProperties": {},
            "additionalProperties": {},
            "required": [],
    }.items():
        if kw in schema and schema[kw] == identity:
            schema.pop(kw)
    # Canonicalise "required" schemas to remove redundancy
    if "object" in type_ and "required" in schema:
        assert isinstance(schema["required"], list)
        reqs = set(schema["required"])
        if schema.get("dependencies"):
            # When the presence of a required property requires other properties via
            # dependencies, those properties can be moved to the base required keys.
            dep_names = {
                k: sorted(set(v))
                for k, v in schema["dependencies"].items()
                if isinstance(v, list)
            }
            schema["dependencies"].update(dep_names)
            while reqs.intersection(dep_names):
                for r in reqs.intersection(dep_names):
                    reqs.update(dep_names.pop(r))
                    schema["dependencies"].pop(r)
                    # TODO: else merge schema-dependencies of required properties
                    # into the base schema after adding required back in and being
                    # careful to avoid an infinite loop...
            if not schema["dependencies"]:
                schema.pop("dependencies")
        schema["required"] = sorted(reqs)
        max_ = schema.get("maxProperties", float("inf"))
        assert isinstance(max_, (int, float))
        properties = schema.get("properties", {})
        if len(schema["required"]) > max_:
            type_.remove("object")
        elif any(
                properties.get(name, {}) == FALSEY
                for name in schema["required"]):
            type_.remove("object")
        else:
            propnames = schema.get("propertyNames", {})
            validator = make_validator(propnames)
            if not all(
                    validator.is_valid(name) for name in schema["required"]):
                type_.remove("object")

    for t, kw in TYPE_SPECIFIC_KEYS:
        numeric = {"number", "integer"}
        if t in type_ or (t in numeric and numeric.intersection(type_)):
            continue
        for k in kw.split():
            schema.pop(k, None)

    # Canonicalise "not" subschemas
    if "not" in schema:
        not_ = schema.pop("not")

        negated = []
        to_negate = not_["anyOf"] if set(not_) == {"anyOf"} else [not_]
        for not_ in to_negate:
            type_keys = {k: set(v.split()) for k, v in TYPE_SPECIFIC_KEYS}
            type_constraints = {"type"}
            for v in type_keys.values():
                type_constraints |= v
            if set(not_).issubset(type_constraints):
                not_["type"] = get_type(not_)
                for t in set(type_).intersection(not_["type"]):
                    if not type_keys.get(t, set()).intersection(not_):
                        type_.remove(t)
                        if t not in ("integer", "number"):
                            not_["type"].remove(t)
                not_ = canonicalish(not_)

            m = merged([not_, {**schema, "type": type_}])
            if m is not None:
                not_ = m
            if not_ != FALSEY:
                negated.append(not_)
        if len(negated) > 1:
            schema["not"] = {"anyOf": negated}
        elif negated:
            schema["not"] = negated[0]

    assert isinstance(type_, list), type_
    if not type_:
        assert type_ == []
        return FALSEY
    if type_ == ["null"]:
        return {"const": None}
    if type_ == ["boolean"]:
        return {"enum": [False, True]}
    if type_ == ["null", "boolean"]:
        return {"enum": [None, False, True]}
    if len(type_) == 1:
        schema["type"] = type_[0]
    elif type_ == get_type({}):
        schema.pop("type", None)
    else:
        schema["type"] = type_
    # Canonicalise "xxxOf" lists; in each case canonicalising and sorting the
    # sub-schemas then handling any key-specific logic.
    if TRUTHY in schema.get("anyOf", ()):
        schema.pop("anyOf", None)
    if "anyOf" in schema:
        i = 0
        while i < len(schema["anyOf"]):
            s = schema["anyOf"][i]
            if set(s) == {"anyOf"}:
                schema["anyOf"][i:i + 1] = s["anyOf"]
                continue
            i += 1
        schema["anyOf"] = [
            json.loads(s) for s in sorted({
                encode_canonical_json(a)
                for a in schema["anyOf"] if a != FALSEY
            })
        ]
        if not schema["anyOf"]:
            return FALSEY
        if len(schema) == len(schema["anyOf"]) == 1:
            return schema["anyOf"][0]  # type: ignore
        types = []
        # Turn
        #   {"anyOf": [{"type": "string"}, {"type": "null"}]}
        # into
        #   {"type": ["string", "null"]}
        for subschema in schema["anyOf"]:
            if "type" in subschema and len(subschema) == 1:
                types.extend(get_type(subschema))
            else:
                break
        else:
            # All subschemas have only the "type" keyword, then we merge all types
            # into the parent schema
            del schema["anyOf"]
            new_types = canonicalish({"type": types})
            schema = merged([schema, new_types])
            assert isinstance(schema, dict)  # merging was certainly valid
    if "allOf" in schema:
        schema["allOf"] = [
            json.loads(enc)
            for enc in sorted(set(map(encode_canonical_json, schema["allOf"])))
        ]
        if any(s == FALSEY for s in schema["allOf"]):
            return FALSEY
        if all(s == TRUTHY for s in schema["allOf"]):
            schema.pop("allOf")
        elif len(schema) == len(schema["allOf"]) == 1:
            return schema["allOf"][0]  # type: ignore
        else:
            tmp = schema.copy()
            ao = tmp.pop("allOf")
            out = merged([tmp] + ao)
            if isinstance(out, dict):  # pragma: no branch
                schema = out
                # TODO: this assertion is soley because mypy 0.750 doesn't know
                # that `schema` is a dict otherwise. Needs minimal report upstream.
                assert isinstance(schema, dict)
    if "oneOf" in schema:
        one_of = schema.pop("oneOf")
        assert isinstance(one_of, list)
        one_of = sorted(one_of, key=encode_canonical_json)
        one_of = [s for s in one_of if s != FALSEY]
        if len(one_of) == 1:
            m = merged([schema, one_of[0]])
            if m is not None:  # pragma: no branch
                return m
        if (not one_of) or one_of.count(TRUTHY) > 1:
            return FALSEY
        schema["oneOf"] = one_of
    if schema.get("uniqueItems") is False:
        del schema["uniqueItems"]
    return schema
Exemple #15
0
        """Reject the last example (i.e. don't count it towards our budget of
        elements because it's not going to go in the final collection)."""
        assert self.count > 0
        self.count -= 1
        self.rejections += 1
        self.rejected = True
        # We set a minimum number of rejections before we give up to avoid
        # failing too fast when we reject the first draw.
        if self.rejections > max(3, 2 * self.count):
            if self.count < self.min_size:
                self.data.mark_invalid()
            else:
                self.force_stop = True


SMALLEST_POSITIVE_FLOAT = next_up(0.0) or sys.float_info.min


@lru_cache()
def _calc_p_continue(desired_avg, max_size):
    """Return the p_continue which will generate the desired average size."""
    assert desired_avg <= max_size, (desired_avg, max_size)
    if desired_avg == max_size:
        return 1.0
    p_continue = 1 - 1.0 / (1 + desired_avg)
    if p_continue == 0 or max_size == float("inf"):
        assert 0 <= p_continue < 1, p_continue
        return p_continue
    assert 0 < p_continue < 1, p_continue
    # For small max_size, the infinite-series p_continue is a poor approximation,
    # and while we can't solve the polynomial a few rounds of iteration quickly
Exemple #16
0
def test_updown_roundtrip(val):
    assert val == next_up(next_down(val))
    assert val == next_down(next_up(val))
Exemple #17
0
def kw(marks=(), **kwargs):
    id_ = ", ".join(f"{k}={v!r}" for k, v in kwargs.items())
    return pytest.param(kwargs, id=id_, marks=marks)


@pytest.mark.parametrize(
    "kwargs",
    [
        kw(min_value=1),
        kw(min_value=1),
        kw(max_value=-1),
        kw(min_value=float_info.min),
        kw(min_value=next_down(float_info.min), exclude_min=True),
        kw(max_value=-float_info.min),
        kw(min_value=next_up(-float_info.min), exclude_max=True),
    ],
)
def test_subnormal_validation(kwargs):
    strat = floats(**kwargs, allow_subnormal=True)
    with pytest.raises(InvalidArgument):
        strat.example()


@pytest.mark.parametrize(
    "kwargs",
    [
        # min value
        kw(allow_subnormal=False, min_value=1),
        kw(allow_subnormal=False, min_value=float_info.min),
        kw(allow_subnormal=True, min_value=-1),
def numeric_schema(schema: dict) -> st.SearchStrategy[float]:
    """Handle numeric schemata."""
    lower = schema.get("minimum")
    upper = schema.get("maximum")
    type_ = get_type(schema) or ["integer", "number"]

    exmin = schema.get("exclusiveMinimum")
    if exmin is True and "integer" in type_:
        assert lower is not None, "boolean exclusiveMinimum implies numeric minimum"
        lower += 1
        exmin = False
    elif exmin is not False and exmin is not None:
        lo = exmin + 1 if int(exmin) == exmin else math.ceil(exmin)
        if lower is None:
            lower = lo if "integer" in type_ else exmin
        else:
            lower = max(lower, lo if "integer" in type_ else exmin)
        exmin = False

    exmax = schema.get("exclusiveMaximum")
    if exmax is True and "integer" in type_:
        assert upper is not None, "boolean exclusiveMaximum implies numeric maximum"
        upper -= 1
        exmax = False
    elif exmax is not False and exmax is not None:
        hi = exmax - 1 if int(exmax) == exmax else math.floor(exmax)
        if upper is None:
            upper = hi if "integer" in type_ else exmax
        else:
            upper = min(upper, hi if "integer" in type_ else exmax)
        exmax = False

    if "multipleOf" in schema:
        multiple_of = schema["multipleOf"]
        assert isinstance(multiple_of, (int, float))
        if lower is not None:
            lo = math.ceil(lower / multiple_of)
            assert lo * multiple_of >= lower, (lower, lo)
            lower = lo
        if upper is not None:
            hi = math.floor(upper / multiple_of)
            assert hi * multiple_of <= upper, (upper, hi)
            upper = hi
        strat = st.integers(lower,
                            upper).map(partial(operator.mul, multiple_of))
        # check for and filter out float bounds, inexact multiplication, etc.
        return strat.filter(partial(is_valid, schema=schema))

    strat = st.nothing()
    if "integer" in type_:
        lo = lower if lower is None else math.ceil(lower)
        hi = upper if upper is None else math.floor(upper)
        if lo is None or hi is None or lo <= hi:
            strat = st.integers(lo, hi)
    if "number" in type_:
        # Filter out negative-zero as it does not exist in JSON
        lo = exmin if lower is None else lower
        if lo is not None:
            lower = float(lo)
            if lower < lo:
                lower = next_up(lower)  # type: ignore  # scary floats magic
            assert lower >= lo
        hi = exmax if upper is None else upper
        if hi is not None:
            upper = float(hi)
            if upper > hi:
                upper = next_down(upper)  # type: ignore  # scary floats magic
            assert upper <= hi
        strat |= st.floats(
            min_value=lower,
            max_value=upper,
            allow_nan=False,
            allow_infinity=False,
            exclude_min=exmin is not None,
            exclude_max=exmax is not None,
        ).filter(lambda n: n != 0 or math.copysign(1, n) == 1)
    return strat
Exemple #19
0
def floats(
    min_value: Optional[Real] = None,
    max_value: Optional[Real] = None,
    *,
    allow_nan: Optional[bool] = None,
    allow_infinity: Optional[bool] = None,
    allow_subnormal: Optional[bool] = None,
    width: int = 64,
    exclude_min: bool = False,
    exclude_max: bool = False,
) -> SearchStrategy[float]:
    """Returns a strategy which generates floats.

    - If min_value is not None, all values will be ``>= min_value``
      (or ``> min_value`` if ``exclude_min``).
    - If max_value is not None, all values will be ``<= max_value``
      (or ``< max_value`` if ``exclude_max``).
    - If min_value or max_value is not None, it is an error to enable
      allow_nan.
    - If both min_value and max_value are not None, it is an error to enable
      allow_infinity.
    - If inferred values range does not include subnormal values, it is an error
      to enable allow_subnormal.

    Where not explicitly ruled out by the bounds,
    :wikipedia:`subnormals <Subnormal_number>`, infinities, and NaNs are possible
    values generated by this strategy.

    The width argument specifies the maximum number of bits of precision
    required to represent the generated float. Valid values are 16, 32, or 64.
    Passing ``width=32`` will still use the builtin 64-bit ``float`` class,
    but always for values which can be exactly represented as a 32-bit float.

    The exclude_min and exclude_max argument can be used to generate numbers
    from open or half-open intervals, by excluding the respective endpoints.
    Excluding either signed zero will also exclude the other.
    Attempting to exclude an endpoint which is None will raise an error;
    use ``allow_infinity=False`` to generate finite floats.  You can however
    use e.g. ``min_value=-math.inf, exclude_min=True`` to exclude only
    one infinite endpoint.

    Examples from this strategy have a complicated and hard to explain
    shrinking behaviour, but it tries to improve "human readability". Finite
    numbers will be preferred to infinity and infinity will be preferred to
    NaN.
    """
    check_type(bool, exclude_min, "exclude_min")
    check_type(bool, exclude_max, "exclude_max")

    if allow_nan is None:
        allow_nan = bool(min_value is None and max_value is None)
    elif allow_nan and (min_value is not None or max_value is not None):
        raise InvalidArgument(
            f"Cannot have allow_nan={allow_nan!r}, with min_value or max_value"
        )

    if width not in (16, 32, 64):
        raise InvalidArgument(
            f"Got width={width!r}, but the only valid values "
            "are the integers 16, 32, and 64.")

    check_valid_bound(min_value, "min_value")
    check_valid_bound(max_value, "max_value")

    if math.copysign(1.0, -0.0) == 1.0:  # pragma: no cover
        raise FloatingPointError(
            "You Python install can't represent -0.0, which is required by the "
            "IEEE-754 floating-point specification.  This is probably because it was "
            "compiled with an unsafe option like -ffast-math; for a more detailed "
            "explanation see https://simonbyrne.github.io/notes/fastmath/")
    if allow_subnormal and next_up(0.0, width=width) == 0:  # pragma: no cover
        # Not worth having separate CI envs and dependencies just to cover this branch;
        # discussion in https://github.com/HypothesisWorks/hypothesis/issues/3092
        #
        # Erroring out here ensures that the database contents are interpreted
        # consistently - which matters for such a foundational strategy, even if it's
        # not always true for all user-composed strategies further up the stack.
        raise FloatingPointError(
            f"Got allow_subnormal={allow_subnormal!r}, but we can't represent "
            f"subnormal floats right now, in violation of the IEEE-754 floating-point "
            f"specification.  This is usually because something was compiled with "
            f"-ffast-math or a similar option, which sets global processor state.  "
            f"See https://simonbyrne.github.io/notes/fastmath/ for a more detailed "
            f"writeup - and good luck!")

    min_arg, max_arg = min_value, max_value
    if min_value is not None:
        min_value = float_of(min_value, width)
        assert isinstance(min_value, float)
    if max_value is not None:
        max_value = float_of(max_value, width)
        assert isinstance(max_value, float)

    if min_value != min_arg:
        raise InvalidArgument(
            f"min_value={min_arg!r} cannot be exactly represented as a float "
            f"of width {width} - use min_value={min_value!r} instead.")
    if max_value != max_arg:
        raise InvalidArgument(
            f"max_value={max_arg!r} cannot be exactly represented as a float "
            f"of width {width} - use max_value={max_value!r} instead.")

    if exclude_min and (min_value is None or min_value == math.inf):
        raise InvalidArgument(f"Cannot exclude min_value={min_value!r}")
    if exclude_max and (max_value is None or max_value == -math.inf):
        raise InvalidArgument(f"Cannot exclude max_value={max_value!r}")

    assumed_allow_subnormal = allow_subnormal is None or allow_subnormal
    if min_value is not None and (exclude_min or (min_arg is not None
                                                  and min_value < min_arg)):
        min_value = next_up_normal(min_value, width, assumed_allow_subnormal)
        if min_value == min_arg:
            assert min_value == min_arg == 0
            assert is_negative(min_arg) and not is_negative(min_value)
            min_value = next_up_normal(min_value, width,
                                       assumed_allow_subnormal)
        assert min_value > min_arg  # type: ignore
    if max_value is not None and (exclude_max or (max_arg is not None
                                                  and max_value > max_arg)):
        max_value = next_down_normal(max_value, width, assumed_allow_subnormal)
        if max_value == max_arg:
            assert max_value == max_arg == 0
            assert is_negative(max_value) and not is_negative(max_arg)
            max_value = next_down_normal(max_value, width,
                                         assumed_allow_subnormal)
        assert max_value < max_arg  # type: ignore

    if min_value == -math.inf:
        min_value = None
    if max_value == math.inf:
        max_value = None

    bad_zero_bounds = (min_value == max_value == 0 and is_negative(max_value)
                       and not is_negative(min_value))
    if (min_value is not None and max_value is not None
            and (min_value > max_value or bad_zero_bounds)):
        # This is a custom alternative to check_valid_interval, because we want
        # to include the bit-width and exclusion information in the message.
        msg = (
            "There are no %s-bit floating-point values between min_value=%r "
            "and max_value=%r" % (width, min_arg, max_arg))
        if exclude_min or exclude_max:
            msg += f", exclude_min={exclude_min!r} and exclude_max={exclude_max!r}"
        raise InvalidArgument(msg)

    if allow_infinity is None:
        allow_infinity = bool(min_value is None or max_value is None)
    elif allow_infinity:
        if min_value is not None and max_value is not None:
            raise InvalidArgument(
                f"Cannot have allow_infinity={allow_infinity!r}, "
                "with both min_value and max_value")
    elif min_value == math.inf:
        if min_arg == math.inf:
            raise InvalidArgument(
                "allow_infinity=False excludes min_value=inf")
        raise InvalidArgument(
            f"exclude_min=True turns min_value={min_arg!r} into inf, "
            "but allow_infinity=False")
    elif max_value == -math.inf:
        if max_arg == -math.inf:
            raise InvalidArgument(
                "allow_infinity=False excludes max_value=-inf")
        raise InvalidArgument(
            f"exclude_max=True turns max_value={max_arg!r} into -inf, "
            "but allow_infinity=False")

    smallest_normal = width_smallest_normals[width]
    if allow_subnormal is None:
        if min_value is not None and max_value is not None:
            if min_value == max_value:
                allow_subnormal = -smallest_normal < min_value < smallest_normal
            else:
                allow_subnormal = (min_value < smallest_normal
                                   and max_value > -smallest_normal)
        elif min_value is not None:
            allow_subnormal = min_value < smallest_normal
        elif max_value is not None:
            allow_subnormal = max_value > -smallest_normal
        else:
            allow_subnormal = True
    if allow_subnormal:
        if min_value is not None and min_value >= smallest_normal:
            raise InvalidArgument(
                f"allow_subnormal=True, but minimum value {min_value} "
                f"excludes values below float{width}'s "
                f"smallest positive normal {smallest_normal}")
        if max_value is not None and max_value <= -smallest_normal:
            raise InvalidArgument(
                f"allow_subnormal=True, but maximum value {max_value} "
                f"excludes values above float{width}'s "
                f"smallest negative normal {-smallest_normal}")

    # Any type hint silences mypy when we unpack these parameters
    kw: Any = {"allow_subnormal": allow_subnormal, "width": width}
    unbounded_floats = FloatStrategy(allow_infinity=allow_infinity,
                                     allow_nan=allow_nan,
                                     **kw)
    if min_value is None and max_value is None:
        return unbounded_floats
    elif min_value is not None and max_value is not None:
        if min_value == max_value:
            assert isinstance(min_value, float)
            result = just(min_value)
        elif is_negative(min_value):
            if is_negative(max_value):
                return floats(min_value=-max_value, max_value=-min_value,
                              **kw).map(operator.neg)
            else:
                return floats(
                    min_value=0.0, max_value=max_value, **kw) | floats(
                        min_value=0.0, max_value=-min_value, **kw).map(
                            operator.neg  # type: ignore
                        )
        elif (count_between_floats(min_value, max_value, width) > 1000
              or not allow_subnormal):
            return FixedBoundedFloatStrategy(lower_bound=min_value,
                                             upper_bound=max_value,
                                             **kw)
        else:
            ub_int = float_to_int(max_value, width)
            lb_int = float_to_int(min_value, width)
            assert lb_int <= ub_int
            result = integers(
                min_value=lb_int,
                max_value=ub_int).map(lambda x: int_to_float(x, width))
    elif min_value is not None:
        assert isinstance(min_value, float)
        if is_negative(min_value):
            # Ignore known bug https://github.com/python/mypy/issues/6697
            return unbounded_floats.map(abs) | floats(  # type: ignore
                min_value=min_value, max_value=-0.0, **kw)
        else:
            result = unbounded_floats.map(lambda x: min_value + abs(x))
    else:
        assert isinstance(max_value, float)
        if not is_negative(max_value):
            return floats(min_value=0.0, max_value=max_value, **
                          kw) | unbounded_floats.map(lambda x: -abs(x))
        else:
            result = unbounded_floats.map(lambda x: max_value - abs(x))

    if width < 64:

        def downcast(x):
            try:
                return float_of(x, width)
            except OverflowError:  # pragma: no cover
                reject()

        result = result.map(downcast)
    if not allow_infinity:
        result = result.filter(lambda x: not math.isinf(x))
    return result
Exemple #20
0
SIGNALING_NAN = int_to_float(0x7FF8_0000_0000_0001)  # nonzero mantissa
assert math.isnan(SIGNALING_NAN) and math.copysign(1, SIGNALING_NAN) == 1

NASTY_FLOATS = sorted(
    [
        0.0,
        0.5,
        1.1,
        1.5,
        1.9,
        1.0 / 3,
        10e6,
        10e-6,
        1.175494351e-38,
        next_up(0.0),
        float_info.min,
        float_info.max,
        3.402823466e38,
        9007199254740992,
        1 - 10e-6,
        2 + 10e-6,
        1.192092896e-07,
        2.2204460492503131e-016,
    ] + [2.0**-n
         for n in (24, 14, 149, 126)]  # minimum (sub)normals for float16,32
    + [float_info.min / n
       for n in (2, 10, 1000, 100_000)]  # subnormal in float64
    + [math.inf, math.nan] * 5 + [SIGNALING_NAN],
    key=flt.float_to_lex,
)
Exemple #21
0
def floats(
    min_value: Optional[Real] = None,
    max_value: Optional[Real] = None,
    *,
    allow_nan: Optional[bool] = None,
    allow_infinity: Optional[bool] = None,
    width: int = 64,
    exclude_min: bool = False,
    exclude_max: bool = False,
) -> SearchStrategy[float]:
    """Returns a strategy which generates floats.

    - If min_value is not None, all values will be ``>= min_value``
      (or ``> min_value`` if ``exclude_min``).
    - If max_value is not None, all values will be ``<= max_value``
      (or ``< max_value`` if ``exclude_max``).
    - If min_value or max_value is not None, it is an error to enable
      allow_nan.
    - If both min_value and max_value are not None, it is an error to enable
      allow_infinity.

    Where not explicitly ruled out by the bounds, all of infinity, -infinity
    and NaN are possible values generated by this strategy.

    The width argument specifies the maximum number of bits of precision
    required to represent the generated float. Valid values are 16, 32, or 64.
    Passing ``width=32`` will still use the builtin 64-bit ``float`` class,
    but always for values which can be exactly represented as a 32-bit float.

    The exclude_min and exclude_max argument can be used to generate numbers
    from open or half-open intervals, by excluding the respective endpoints.
    Excluding either signed zero will also exclude the other.
    Attempting to exclude an endpoint which is None will raise an error;
    use ``allow_infinity=False`` to generate finite floats.  You can however
    use e.g. ``min_value=-math.inf, exclude_min=True`` to exclude only
    one infinite endpoint.

    Examples from this strategy have a complicated and hard to explain
    shrinking behaviour, but it tries to improve "human readability". Finite
    numbers will be preferred to infinity and infinity will be preferred to
    NaN.
    """
    check_type(bool, exclude_min, "exclude_min")
    check_type(bool, exclude_max, "exclude_max")

    if allow_nan is None:
        allow_nan = bool(min_value is None and max_value is None)
    elif allow_nan and (min_value is not None or max_value is not None):
        raise InvalidArgument(
            f"Cannot have allow_nan={allow_nan!r}, with min_value or max_value"
        )

    if width not in (16, 32, 64):
        raise InvalidArgument(
            f"Got width={width!r}, but the only valid values "
            "are the integers 16, 32, and 64.")

    check_valid_bound(min_value, "min_value")
    check_valid_bound(max_value, "max_value")

    min_arg, max_arg = min_value, max_value
    if min_value is not None:
        min_value = float_of(min_value, width)
        assert isinstance(min_value, float)
    if max_value is not None:
        max_value = float_of(max_value, width)
        assert isinstance(max_value, float)

    if min_value != min_arg:
        raise InvalidArgument(
            f"min_value={min_arg!r} cannot be exactly represented as a float "
            f"of width {width} - use min_value={min_value!r} instead.")
    if max_value != max_arg:
        raise InvalidArgument(
            f"max_value={max_arg!r} cannot be exactly represented as a float "
            f"of width {width} - use max_value={max_value!r} instead.")

    if exclude_min and (min_value is None or min_value == math.inf):
        raise InvalidArgument(f"Cannot exclude min_value={min_value!r}")
    if exclude_max and (max_value is None or max_value == -math.inf):
        raise InvalidArgument(f"Cannot exclude max_value={max_value!r}")

    if min_value is not None and (exclude_min or (min_arg is not None
                                                  and min_value < min_arg)):
        min_value = next_up(min_value, width)
        if min_value == min_arg:
            assert min_value == min_arg == 0
            assert is_negative(min_arg) and not is_negative(min_value)
            min_value = next_up(min_value, width)
        assert min_value > min_arg  # type: ignore
    if max_value is not None and (exclude_max or (max_arg is not None
                                                  and max_value > max_arg)):
        max_value = next_down(max_value, width)
        if max_value == max_arg:
            assert max_value == max_arg == 0
            assert is_negative(max_value) and not is_negative(max_arg)
            max_value = next_down(max_value, width)
        assert max_value < max_arg  # type: ignore

    if min_value == -math.inf:
        min_value = None
    if max_value == math.inf:
        max_value = None

    bad_zero_bounds = (min_value == max_value == 0 and is_negative(max_value)
                       and not is_negative(min_value))
    if (min_value is not None and max_value is not None
            and (min_value > max_value or bad_zero_bounds)):
        # This is a custom alternative to check_valid_interval, because we want
        # to include the bit-width and exclusion information in the message.
        msg = (
            "There are no %s-bit floating-point values between min_value=%r "
            "and max_value=%r" % (width, min_arg, max_arg))
        if exclude_min or exclude_max:
            msg += f", exclude_min={exclude_min!r} and exclude_max={exclude_max!r}"
        raise InvalidArgument(msg)

    if allow_infinity is None:
        allow_infinity = bool(min_value is None or max_value is None)
    elif allow_infinity:
        if min_value is not None and max_value is not None:
            raise InvalidArgument(
                f"Cannot have allow_infinity={allow_infinity!r}, "
                "with both min_value and max_value")
    elif min_value == math.inf:
        raise InvalidArgument("allow_infinity=False excludes min_value=inf")
    elif max_value == -math.inf:
        raise InvalidArgument("allow_infinity=False excludes max_value=-inf")

    unbounded_floats = FloatStrategy(allow_infinity=allow_infinity,
                                     allow_nan=allow_nan,
                                     width=width)

    if min_value is None and max_value is None:
        return unbounded_floats
    elif min_value is not None and max_value is not None:
        if min_value == max_value:
            assert isinstance(min_value, float)
            result = just(min_value)
        elif is_negative(min_value):
            if is_negative(max_value):
                return floats(min_value=-max_value,
                              max_value=-min_value,
                              width=width).map(operator.neg)
            else:
                return floats(
                    min_value=0.0, max_value=max_value, width=width) | floats(
                        min_value=0.0, max_value=-min_value, width=width).map(
                            operator.neg)
        elif count_between_floats(min_value, max_value) > 1000:
            return FixedBoundedFloatStrategy(lower_bound=min_value,
                                             upper_bound=max_value,
                                             width=width)
        else:
            ub_int = float_to_int(max_value, width)
            lb_int = float_to_int(min_value, width)
            assert lb_int <= ub_int
            result = integers(
                min_value=lb_int,
                max_value=ub_int).map(lambda x: int_to_float(x, width))
    elif min_value is not None:
        assert isinstance(min_value, float)
        if is_negative(min_value):
            # Ignore known bug https://github.com/python/mypy/issues/6697
            return unbounded_floats.map(abs) | floats(  # type: ignore
                min_value=min_value,
                max_value=-0.0,
                width=width)
        else:
            result = unbounded_floats.map(lambda x: min_value + abs(x))
    else:
        assert isinstance(max_value, float)
        if not is_negative(max_value):
            return floats(
                min_value=0.0, max_value=max_value,
                width=width) | unbounded_floats.map(lambda x: -abs(x))
        else:
            result = unbounded_floats.map(lambda x: max_value - abs(x))

    if width < 64:

        def downcast(x):
            try:
                return float_of(x, width)
            except OverflowError:  # pragma: no cover
                reject()

        result = result.map(downcast)
    if not allow_infinity:
        result = result.filter(lambda x: not math.isinf(x))
    return result
Exemple #22
0
@pytest.mark.parametrize(
    "s, msg",
    [
        (
            floats(min_value=inf, allow_infinity=False),
            "allow_infinity=False excludes min_value=inf",
        ),
        (
            floats(min_value=next_down(inf),
                   exclude_min=True,
                   allow_infinity=False),
            "exclude_min=True turns min_value=.+? into inf, but allow_infinity=False",
        ),
        (
            floats(max_value=-inf, allow_infinity=False),
            "allow_infinity=False excludes max_value=-inf",
        ),
        (
            floats(max_value=next_up(-inf),
                   exclude_max=True,
                   allow_infinity=False),
            "exclude_max=True turns max_value=.+? into -inf, but allow_infinity=False",
        ),
    ],
)
def test_floats_message(s, msg):
    # https://github.com/HypothesisWorks/hypothesis/issues/3207
    with pytest.raises(InvalidArgument, match=msg):
        s.validate()