Example #1
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
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
Example #3
0
def test_fuzz_floats_bounds(data):
    bound = none() | floats(allow_nan=False)
    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
    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")
    try:
        val = data.draw(floats(low, high, exclude_min=exmin,
                               exclude_max=exmax),
                        label="value")
        assume(val)  # positive/negative zero is an issue
    except (InvalidArgument, HypothesisDeprecationWarning):
        assert ((exmin and exmax and low == next_down(high))
                or (low == high and (exmin or exmax))
                or (low == high == 0 and copysign(1.0, low) == 1
                    and copysign(1.0, high) == -1))
        reject()  # no floats in required range
    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
Example #4
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
Example #5
0
def test_down_means_lesser(x):
    lo = next_down(x)
    if not x > lo:
        assert (
            (math.isnan(x) and math.isnan(lo))
            or (x < 0 and math.isinf(x))
            or (x == lo == 0 and is_negative(lo) and not is_negative(x))
        )
def test_down_means_lesser(x):
    lo = next_down(x)
    if not x > lo:
        assert (
            (math.isnan(x) and math.isnan(lo))
            or (x < 0 and math.isinf(x))
            or (x == lo == 0 and is_negative(lo) and not is_negative(x))
        )
Example #7
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
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
def test_updown_roundtrip(val):
    assert val == next_up(next_down(val))
    assert val == next_down(next_up(val))
def test_down_means_lesser(x):
    lo = next_down(x)
    if not x > lo:
        assert (math.isnan(x) and math.isnan(lo)) or (x < 0 and math.isinf(x))
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 (lo is not None and hi is not None
                and ((mul and not has_divisibles(lo, hi, mul, exmin, exmax)) or
                     (next_up(lo) if exmin else lo) >
                     (next_down(hi) if exmax else hi))):
            type_.remove("number")
        elif 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"]

    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 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", []), (bool, 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")
    # 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))
        propnames = schema.get("propertyNames", {})
        if len(schema["required"]) > max_:
            type_.remove("object")
        else:
            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)
    # Remove no-op requires
    if "required" in schema and not schema["required"]:
        schema.pop("required")
    # 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"] = sorted(schema["allOf"], key=encode_canonical_json)
        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
Example #12
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),
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
Example #14
0
    output = out.getvalue()
    assert f"{example_type} example:" in output
    assert_output_contains_failure(output, test, **kwargs)


@contextlib.contextmanager
def temp_registered(type_, strat_or_factory):
    """Register and un-register a type for st.from_type().

    This not too hard, but there's a subtlety in restoring the
    previously-registered strategy which we got wrong in a few places.
    """
    prev = _global_type_lookup.get(type_)
    try:
        register_type_strategy(type_, strat_or_factory)
        yield
    finally:
        del _global_type_lookup[type_]
        from_type.__clear_cache()
        if prev is not None:
            register_type_strategy(type_, prev)


# Specifies whether we can represent subnormal floating point numbers.
# IEE-754 requires subnormal support, but it's often disabled anyway by unsafe
# compiler options like `-ffast-math`.  On most hardware that's even a global
# config option, so *linking against* something built this way can break us.
# Everything is terrible
PYTHON_FTZ = next_down(sys.float_info.min) == 0.0
Example #15
0
def test_down_means_lesser(x):
    lo = next_down(x)
    if not x > lo:
        assert (math.isnan(x) and math.isnan(lo)) or (x < 0 and math.isinf(x))
Example #16
0
def test_updown_roundtrip(val):
    assert val == next_up(next_down(val))
    assert val == next_down(next_up(val))
Example #17
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
Example #18
0
def _from_dtype(
    xp: Any,
    dtype: Union[DataType, str],
    *,
    min_value: Optional[Union[int, float]] = None,
    max_value: Optional[Union[int, float]] = None,
    allow_nan: Optional[bool] = None,
    allow_infinity: Optional[bool] = None,
    allow_subnormal: Optional[bool] = None,
    exclude_min: Optional[bool] = None,
    exclude_max: Optional[bool] = None,
) -> st.SearchStrategy[Union[bool, int, float]]:
    """Return a strategy for any value of the given dtype.

    Values generated are of the Python scalar which is
    :xp-ref:`promotable <type_promotion.html>` to ``dtype``, where the values do
    not exceed its bounds.

    * ``dtype`` may be a dtype object or the string name of a
      :xp-ref:`valid dtype <data_types.html>`.

    Compatible ``**kwargs`` are passed to the inferred strategy function for
    integers and floats.  This allows you to customise the min and max values,
    and exclude non-finite numbers. This is particularly useful when kwargs are
    passed through from :func:`arrays()`, as it seamlessly handles the ``width``
    or other representable bounds for you.
    """
    check_xp_attributes(xp, ["iinfo", "finfo"])

    if isinstance(dtype, str):
        dtype = dtype_from_name(xp, dtype)
    builtin = find_castable_builtin_for_dtype(xp, dtype)

    def check_valid_minmax(prefix, val, info_obj):
        name = f"{prefix}_value"
        check_valid_bound(val, name)
        check_argument(
            val >= info_obj.min,
            f"dtype={dtype} requires {name}={val} to be at least {info_obj.min}",
        )
        check_argument(
            val <= info_obj.max,
            f"dtype={dtype} requires {name}={val} to be at most {info_obj.max}",
        )

    if builtin is bool:
        return st.booleans()
    elif builtin is int:
        iinfo = xp.iinfo(dtype)
        if min_value is None:
            min_value = iinfo.min
        if max_value is None:
            max_value = iinfo.max
        check_valid_integer(min_value, "min_value")
        check_valid_integer(max_value, "max_value")
        assert isinstance(min_value, int)
        assert isinstance(max_value, int)
        check_valid_minmax("min", min_value, iinfo)
        check_valid_minmax("max", max_value, iinfo)
        check_valid_interval(min_value, max_value, "min_value", "max_value")
        return st.integers(min_value=min_value, max_value=max_value)
    else:
        finfo = xp.finfo(dtype)
        kw = {}

        # Whilst we know the boundary values of float dtypes from finfo, we do
        # not assign them to the floats() strategy by default - passing min/max
        # values will modify test case reduction behaviour so that simple bugs
        # may become harder for users to identify. We plan to improve floats()
        # behaviour in https://github.com/HypothesisWorks/hypothesis/issues/2907.
        # Setting width should manage boundary values for us anyway.
        if min_value is not None:
            check_valid_bound(min_value, "min_value")
            assert isinstance(min_value, Real)
            check_valid_minmax("min", min_value, finfo)
            kw["min_value"] = min_value
        if max_value is not None:
            check_valid_bound(max_value, "max_value")
            assert isinstance(max_value, Real)
            check_valid_minmax("max", max_value, finfo)
            if min_value is not None:
                check_valid_interval(min_value, max_value, "min_value",
                                     "max_value")
            kw["max_value"] = max_value

        # We infer whether an array module will flush subnormals to zero, as may
        # be the case when libraries are built with compiler options that
        # violate IEEE-754 (e.g. -ffast-math and -ftz=true). Note we do this for
        # the specific dtype, as compilers may end up flushing subnormals for
        # one float but supporting subnormals for the other.
        #
        # By default, floats() will generate subnormals if they are in the
        # inferred values range. If we have detected that xp flushes to zero for
        # the passed dtype, we ensure from_dtype() will not generate subnormals
        # by default.
        if allow_subnormal is not None:
            kw["allow_subnormal"] = allow_subnormal
        else:
            subnormal = next_down(finfo.smallest_normal, width=finfo.bits)
            ftz = bool(xp.asarray(subnormal, dtype=dtype) == 0)
            if ftz:
                kw["allow_subnormal"] = False

        if allow_nan is not None:
            kw["allow_nan"] = allow_nan
        if allow_infinity is not None:
            kw["allow_infinity"] = allow_infinity
        if exclude_min is not None:
            kw["exclude_min"] = exclude_min
        if exclude_max is not None:
            kw["exclude_max"] = exclude_max

        return st.floats(width=finfo.bits, **kw)
Example #19
0
        decimals(bad).example()
    with pytest.raises(InvalidArgument) as excinfo2:
        with decimal.localcontext(decimal.Context(traps=[])):
            decimals(bad).example()
    assert str(excinfo.value) == str(excinfo2.value)


@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",
        ),
    ],