Beispiel #1
0
    def test_collect_short_circuits(self) -> None:
        """Ensure collect does not iterate after an err is reached."""
        until_err: t.List[Result[int, str]] = [Ok(1), Ok(2), Err("no")]

        def _iterable() -> t.Iterable[Result[int, str]]:
            yield from until_err
            # If we continue iterating after the err, we will raise a
            # runtime Error.
            assert False, "Result.collect() did not short circuit on err!"

        assert Result.collect(_iterable()) == Err("no")
Beispiel #2
0
 def insert(self,
            key: str,
            val: T,
            overwrite: bool = False) -> Result[T, str]:
     """Insert the value and return it."""
     if key in self._values and not overwrite:
         return Err("Key already exists")
     self._values[key] = val
     return Ok(val)
Beispiel #3
0
    def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test expecting a value to be Ok()."""
        exp_exc = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Err(2).expect(msg, **kwargs)

        assert msg in str(exc_info.value)
Beispiel #4
0
class TestImplementationDetails:
    """Some implementation details need to be tested."""
    def test_nothing_singleton(self) -> None:
        """Ensure Nothing() is a singleton."""
        assert Nothing() is Nothing() is Nothing()

    @pytest.mark.parametrize("obj", (Some(1), Nothing(), Ok(1), Err(1)))
    def test_all_slotted(self, obj: t.Any) -> None:
        """All implementations use __slots__."""
        assert not hasattr(obj, "__dict__")
Beispiel #5
0
    def test_raise_if_err_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test raise_if_err for Err() values."""
        exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        input_val = 2
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Err(input_val).raise_if_err(msg, **kwargs)

        assert msg in str(exc_info.value)
        assert str(input_val) in str(exc_info.value)
Beispiel #6
0
 def test_unwrap_is_err(self) -> None:
     """.unwrap() raises for an error value."""
     with pytest.raises(RuntimeError):
         Err(5).unwrap()
Beispiel #7
0
 def test_or_multichain(self) -> None:
     """.or_() calls can be chained indefinitely."""
     err: Result[int, int] = Err(5)
     assert err.or_(Err(6)).or_(Err(7)).or_(Ok(8)) == Ok(8)
Beispiel #8
0
class TestResultConstructors:
    """Test Result constructors."""
    @pytest.mark.parametrize(
        "fn, exp",
        (
            (lambda: 5, Ok(5)),
            (lambda: _raises(TypeError), Err(TypeError)),
            (Nothing, Ok(Nothing())),
        ),
    )
    def test_of(self, fn: t.Callable, exp: Result) -> None:
        """Test getting a result from a callable."""
        if exp.is_err():
            assert isinstance(Result.of(fn).unwrap_err(), exp.unwrap_err())
        else:
            assert Result.of(fn) == exp

    def test_of_with_args(self) -> None:
        """Test getting a result from a callable with args."""
        assert Result.of(lambda x: bool(x > 0), 1).unwrap() is True

    def test_of_with_kwargs(self) -> None:
        """Test getting a result from a callable with args."""
        def foo(a: int, b: str = None) -> t.Optional[str]:
            return b

        assert Result.of(foo, 1, b="a").unwrap() == "a"

    @pytest.mark.parametrize(
        "iterable, exp",
        (
            ((Ok(1), Ok(2), Ok(3)), Ok((1, 2, 3))),
            ((Ok(1), Err("no"), Ok(3)), Err("no")),
            (iter([Ok(1), Ok(2)]), Ok((1, 2))),
            ([Err("no")], Err("no")),
            ([Ok(1)], Ok((1, ))),
            ([], Ok(())),
        ),
    )
    def test_collect(self, iterable: t.Iterable[Result[int, str]],
                     exp: Result[int, str]) -> None:
        """Test collecting an iterable of results into a single result."""
        assert Result.collect(iterable) == exp

    def test_collect_short_circuits(self) -> None:
        """Ensure collect does not iterate after an err is reached."""
        until_err: t.List[Result[int, str]] = [Ok(1), Ok(2), Err("no")]

        def _iterable() -> t.Iterable[Result[int, str]]:
            yield from until_err
            # If we continue iterating after the err, we will raise a
            # runtime Error.
            assert False, "Result.collect() did not short circuit on err!"

        assert Result.collect(_iterable()) == Err("no")

    @pytest.mark.parametrize(
        "predicate, val, exp",
        (
            (lambda x: x is True, True, Ok(True)),
            (lambda x: x is True, False, Err(False)),
            (lambda x: x > 0, 1, Ok(1)),
            (lambda x: x > 0, -2, Err(-2)),
        ),
    )
    def test_ok_if(self, predicate: t.Callable, val: t.Any,
                   exp: Result) -> None:
        """Test constructing based on some predicate."""
        assert Result.ok_if(predicate, val) == exp

    @pytest.mark.parametrize(
        "predicate, val, exp",
        (
            (lambda x: x is True, True, Err(True)),
            (lambda x: x is True, False, Ok(False)),
            (lambda x: x > 0, 1, Err(1)),
            (lambda x: x > 0, -2, Ok(-2)),
        ),
    )
    def test_err_if(self, predicate: t.Callable, val: t.Any,
                    exp: Result) -> None:
        """Test constructing based on some predicate."""
        assert Result.err_if(predicate, val) == exp
Beispiel #9
0
 def test_is_ok(self) -> None:
     """Ok() returns true for is_ok()."""
     assert Ok(1).is_ok()
     assert not Err(1).is_ok()
Beispiel #10
0
 def _validate_length(self, string: str) -> Result[str, str]:
     """Check that all the strings are of the proper length."""
     if len(string) < self.MIN_LEN:
         return Err("String is too short")
     return Ok(string)
Beispiel #11
0
class TestOption:
    """Test the option type."""
    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Some(2), Nothing(), Nothing()),
            (Nothing(), Some(2), Nothing()),
            (Some(1), Some(2), Some(2)),
            (Nothing(), Nothing(), Nothing()),
        ),
    )
    def test_and(self, left: Option[int], right: Option[int],
                 exp: Option[int]) -> None:
        """Returns Some() if both options are Some()."""
        assert left.and_(right) == exp

    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Some(2), Nothing(), Some(2)),
            (Nothing(), Some(2), Some(2)),
            (Some(1), Some(2), Some(1)),
            (Nothing(), Nothing(), Nothing()),
        ),
    )
    def test_or(self, left: Option[int], right: Option[int],
                exp: Option[int]) -> None:
        """Returns Some() if either or both is Some()."""
        assert left.or_(right) == exp

    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Some(2), Nothing(), Some(2)),
            (Nothing(), Some(2), Some(2)),
            (Some(1), Some(2), Nothing()),
            (Nothing(), Nothing(), Nothing()),
        ),
    )
    def test_xor(self, left: Option[int], right: Option[int],
                 exp: Option[int]) -> None:
        """Returns Some() IFF only one option is Some()."""
        assert left.xor(right) == exp

    @pytest.mark.parametrize(
        "start, first, second, exp",
        (
            (Some(2), _sq, _sq, Some(16)),
            (Some(2), _sq, _nothing, Nothing()),
            (Some(2), _nothing, _sq, Nothing()),
            (Nothing(), _sq, _sq, Nothing()),
        ),
    )
    def test_and_then(
        self,
        start: Option[int],
        first: t.Callable[[int], Option[int]],
        second: t.Callable[[int], Option[int]],
        exp: Option[int],
    ) -> None:
        """Chains option-generating functions if results are `Some`."""
        assert start.and_then(first).and_then(second) == exp

    @pytest.mark.parametrize(
        "start, fn, exp",
        (
            (Some("one"), lambda: Some("one else"), Some("one")),
            (Nothing(), lambda: Some("one else"), Some("one else")),
            (Nothing(), lambda: Nothing(), Nothing()),
        ),
    )
    def test_or_else(
        self,
        start: Option[str],
        fn: t.Callable[[], Option[str]],
        exp: Option[str],
    ) -> None:
        """Chains option-generating functions if results are `None`."""
        assert start.or_else(fn) == exp

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Can specify exception msg/cls if value is not Some()."""
        exp_exc = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Nothing().expect(msg, **kwargs)

        assert msg in str(exc_info.value)

    def test_expect_not_raising(self) -> None:
        """Expecting on a Some() returns the value."""
        assert Some("hello").expect("not what I expected") == "hello"

    @pytest.mark.parametrize(
        "start, exp",
        ((Nothing(), Nothing()), (Some(3), Nothing()), (Some(4), Some(4))),
    )
    def test_filter(self, start: Option[int], exp: Option[int]) -> None:
        """A satisfied predicate returns `Some()`, otherwise `None()`."""
        def is_even(val: int) -> bool:
            return val % 2 == 0

        assert start.filter(is_even) == exp

    @pytest.mark.parametrize("opt, exp", ((Nothing(), True), (Some(1), False)))
    def test_is_nothing(self, opt: Option[int], exp: bool) -> None:
        """"Nothings() are nothing, Some()s are not."""
        assert opt.is_nothing() is exp

    @pytest.mark.parametrize("opt, exp", ((Nothing(), False), (Some(1), True)))
    def test_is_some(self, opt: Option[int], exp: bool) -> None:
        """"Nothings() are nothing, Some()s are not."""
        assert opt.is_some() is exp

    @pytest.mark.parametrize("opt, exp", ((Nothing(), ()), (Some(5), (5, ))))
    def test_iter(self, opt: Option[int], exp: t.Tuple[int, ...]) -> None:
        """Iterating on a Some() yields the Some(); on a None() nothing."""
        assert tuple(opt.iter()) == tuple(iter(opt)) == exp

    @pytest.mark.parametrize("opt, exp", ((Some("hello"), Some(5)),
                                          (Nothing(), Nothing())))
    def test_map(self, opt: Option[str], exp: Option[int]) -> None:
        """Maps fn() onto `Some()` to make a new option, or ignores None()."""
        assert opt.map(len) == exp

    @pytest.mark.parametrize("opt, exp", ((Some("hello"), 5), (Nothing(), -1)))
    def test_map_or(self, opt: Option[str], exp: Option[int]) -> None:
        """Maps fn() onto `Some()` & return the value, or return a default."""
        assert opt.map_or(-1, lambda s: len(s)) == exp

    @pytest.mark.parametrize("opt, exp", ((Some("hello"), 5), (Nothing(), -1)))
    def test_map_or_else(self, opt: Option[str], exp: Option[int]) -> None:
        """Maps fn() onto `Some()` & return the value, or return a default."""
        assert opt.map_or_else(lambda: -1, lambda s: len(s)) == exp

    @pytest.mark.parametrize("opt, exp",
                             ((Some(2), Ok(2)), (Nothing(), Err("oh no"))))
    def test_ok_or(self, opt: Option[str], exp: Result[str, int]) -> None:
        """Map `Some(t)` to `Ok(t)`, or `Nothing()` to an `Err()`."""
        assert opt.ok_or("oh no") == exp

    @pytest.mark.parametrize("opt, exp",
                             ((Some(2), Ok(2)), (Nothing(), Err("oh no"))))
    def test_ok_or_else(self, opt: Option[str], exp: Result[str, int]) -> None:
        """Map `Some(t)` to `Ok(t)`, or `Nothing()` to an `Err()`."""
        assert opt.ok_or_else(lambda: "oh no") == exp

    def test_unwrap_raising(self) -> None:
        """Unwraping a Nothing() raises an error."""
        with pytest.raises(RuntimeError):
            Nothing().unwrap()

    def test_unwrap_success(self) -> None:
        """Unwrapping a Some() returns the wrapped value."""
        assert Some("thing").unwrap() == "thing"

    @pytest.mark.parametrize("opt, exp", ((Some(2), 2), (Nothing(), 42)))
    def test_unwrap_or(self, opt: Option[int], exp: int) -> None:
        """Unwraps a `Some()` or returns a default."""
        assert opt.unwrap_or(42) == exp

    @pytest.mark.parametrize("opt, exp", ((Some(2), 2), (Nothing(), 42)))
    def test_unwrap_or_else(self, opt: Option[int], exp: int) -> None:
        """Unwraps a `Some()` or returns a default."""
        assert opt.unwrap_or_else(lambda: 42) == exp

    @pytest.mark.parametrize(
        "inst, other, eq",
        (
            (Some(1), Some(1), True),
            (Some(1), Some(2), False),
            (Some(1), Nothing(), False),
            (Some(1), 1, False),
            (Nothing(), Nothing(), True),
            (Nothing(), Some(1), False),
            (Nothing(), None, False),
        ),
    )
    def test_equality_inequality(self, inst: t.Any, other: t.Any,
                                 eq: bool) -> None:
        """Test equality and inequality of results."""
        assert (inst == other) is eq
        assert (inst != other) is not eq

    def test_stringify(self) -> None:
        """Repr and str representations are equivalent."""
        assert repr(Some(1)) == str(Some(1)) == "Some(1)"
        assert repr(Nothing()) == str(Nothing()) == "Nothing()"
Beispiel #12
0
def _err(val: int) -> Result[int, int]:
    """Return an error."""
    return Err(val)
Beispiel #13
0
 def test_flatmap(self) -> None:
     """Flatmap is an alias for and_then"""
     ok: Result[int, int] = Ok(2)
     err: Result[int, int] = Err(2)
     assert ok.flatmap(_sq) == Ok(4)
     assert err.flatmap(_sq) == Err(2)
Beispiel #14
0
 def _validate_substring(self, string: str) -> Result[str, str]:
     """Check the string has the required substring."""
     if self.MUST_CONTAIN not in string:
         return Err(f"String did not contain '{self.MUST_CONTAIN}'")
     return Ok(string)
Beispiel #15
0
class TestResult:
    """Test the result type."""
    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Ok(2), Err("late error"), Err("late error")),
            (Err("early error"), Ok(2), Err("early error")),
            (Err("early error"), Err("late error"), Err("early error")),
            (Ok(2), Ok(3), Ok(3)),
        ),
    )
    def test_and(
        self,
        left: Result[int, str],
        right: Result[int, str],
        exp: Result[int, str],
    ) -> None:
        """Test that `and` returns an alternative for `Ok` values."""
        assert left.and_(right) == exp

    def test_and_multichain(self) -> None:
        """.and() calls can be chained indefinitely."""
        assert Ok(2).and_(Ok(3)).and_(Ok(4)).and_(Ok(5)) == Ok(5)

    @pytest.mark.parametrize(
        "start, first, second, exp",
        (
            (Ok(2), _sq, _sq, Ok(16)),
            (Ok(2), _sq, _err, Err(4)),
            (Ok(2), _err, _sq, Err(2)),
            (Ok(2), _err, _err, Err(2)),
            (Err(3), _sq, _sq, Err(3)),
        ),
    )
    def test_and_then(
        self,
        start: Result[int, int],
        first: t.Callable[[int], Result[int, int]],
        second: t.Callable[[int], Result[int, int]],
        exp: Result[int, int],
    ) -> None:
        """Test that and_then chains result-generating functions."""
        assert start.and_then(first).and_then(second) == exp

    def test_flatmap(self) -> None:
        """Flatmap is an alias for and_then"""
        ok: Result[int, int] = Ok(2)
        err: Result[int, int] = Err(2)
        assert ok.flatmap(_sq) == Ok(4)
        assert err.flatmap(_sq) == Err(2)

    @pytest.mark.parametrize(
        "start, first, second, exp",
        (
            (Ok(2), _sq, _sq, Ok(2)),
            (Ok(2), _err, _sq, Ok(2)),
            (Err(3), _sq, _err, Ok(9)),
            (Err(3), _err, _err, Err(3)),
        ),
    )
    def test_or_else(
        self,
        start: Result[int, int],
        first: t.Callable[[int], Result[int, int]],
        second: t.Callable[[int], Result[int, int]],
        exp: Result[int, int],
    ) -> None:
        """Test that and_then chains result-generating functions."""
        assert start.or_else(first).or_else(second) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(2), Nothing()), (Err("err"), Some("err"))))
    def test_err(self, start: Result[int, str], exp: Option[str]) -> None:
        """Test converting a result to an option."""
        assert start.err() == exp

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test expecting a value to be Ok()."""
        exp_exc = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Err(2).expect(msg, **kwargs)

        assert msg in str(exc_info.value)

    def test_expect_ok(self) -> None:
        """Expecting an Ok() value returns the value."""
        assert Ok(2).expect("err") == 2

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_expect_err_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test expecting a value to be Ok()."""
        exp_exc = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Ok(2).expect_err(msg, **kwargs)

        assert msg in str(exc_info.value)

    def test_expect_err_err(self) -> None:
        """Expecting an Ok() value returns the value."""
        assert Err(2).expect_err("err") == 2

    def test_is_err(self) -> None:
        """Err() returns true for is_err()."""
        assert Err(1).is_err()
        assert not Ok(1).is_err()

    def test_is_ok(self) -> None:
        """Ok() returns true for is_ok()."""
        assert Ok(1).is_ok()
        assert not Err(1).is_ok()

    @pytest.mark.parametrize("start, exp", ((Ok(1), (1, )), (Err(1), ())))
    def test_iter(self, start: Result[int, int], exp: t.Tuple[int,
                                                              ...]) -> None:
        """iter() returns a 1-member iterator on Ok(), 0-member for Err()."""
        assert tuple(start.iter()) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(2), Ok(4)), (Err("foo"), Err("foo"))))
    def test_map(self, start: Result[int, str], exp: Result[int, str]) -> None:
        """.map() will map onto Ok() and ignore Err()."""
        assert start.map(lambda x: int(x**2)) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok("foo"), Ok("foo")), (Err(2), Err("2"))))
    def test_map_err(self, start: Result[str, int], exp: Result[str,
                                                                str]) -> None:
        """.map_err() will map onto Err() and ignore Ok()."""
        assert start.map_err(str) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(1), Some(1)), (Err(1), Nothing())))
    def test_ok(self, start: Result[int, int], exp: Option[int]) -> None:
        """.ok() converts a result to an Option."""
        assert start.ok() == exp

    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Ok(5), Ok(6), Ok(5)),
            (Ok(5), Err(6), Ok(5)),
            (Err(5), Ok(6), Ok(6)),
            (Err(5), Err(6), Err(6)),
        ),
    )
    def test_or(
        self,
        left: Result[int, str],
        right: Result[int, str],
        exp: Result[int, str],
    ) -> None:
        """.or_() returns the first available non-err value."""
        assert left.or_(right) == exp

    def test_or_multichain(self) -> None:
        """.or_() calls can be chained indefinitely."""
        err: Result[int, int] = Err(5)
        assert err.or_(Err(6)).or_(Err(7)).or_(Ok(8)) == Ok(8)

    def test_unwrap_is_ok(self) -> None:
        """.unwrap() returns an ok() value."""
        assert Ok(5).unwrap() == 5

    def test_unwrap_is_err(self) -> None:
        """.unwrap() raises for an error value."""
        with pytest.raises(RuntimeError):
            Err(5).unwrap()

    def test_unwrap_err_is_ok(self) -> None:
        """.unwrap_err() raises for an ok value."""
        with pytest.raises(RuntimeError):
            Ok(5).unwrap_err()

    def test_unwrap_err_is_err(self) -> None:
        """.unwrap_err() returns an error value."""
        assert Err(5).unwrap_err() == 5

    @pytest.mark.parametrize("start, alt, exp",
                             ((Ok(5), 6, 5), (Err(5), 6, 6)))
    def test_unwrap_or(self, start: Result[int, int], alt: int,
                       exp: int) -> None:
        """.unwrap_or() provides the default if the result is Err()."""
        assert start.unwrap_or(alt) == exp

    @pytest.mark.parametrize(
        "start, fn, exp",
        ((Ok(5), lambda i: i + 2, 5), (Err(5), lambda i: i + 2, 7)),
    )
    def test_unwrap_or_else(self, start: Result[int, int],
                            fn: t.Callable[[int], int], exp: int) -> None:
        """Calculates a result from Err() value if present."""
        assert start.unwrap_or_else(fn) == exp

    @pytest.mark.parametrize(
        "inst, other, eq",
        (
            (Ok(1), Ok(1), True),
            (Ok(1), Ok(2), False),
            (Ok(1), Err(1), False),
            (Ok(1), 1, False),
            (Err(1), Err(1), True),
            (Err(1), Err(2), False),
            (Err(1), Ok(1), False),
            (Err(1), 1, False),
        ),
    )
    def test_equality_inequality(self, inst: t.Any, other: t.Any,
                                 eq: bool) -> None:
        """Test equality and inequality of results."""
        assert (inst == other) is eq
        assert (inst != other) is not eq

    def test_stringify(self) -> None:
        """Repr and str representations are equivalent."""
        assert repr(Ok(1)) == str(Ok(1)) == "Ok(1)"
        assert repr(Err(1)) == str(Err(1)) == "Err(1)"
Beispiel #16
0
 def _validate_end_char(self, string: str) -> Result[str, str]:
     """Check the string ends with a period."""
     if len(string) > 0 and string[-1] != ".":
         return Err("String does not end with a period")
     return Ok(string)
Beispiel #17
0
 def _validate_capitalized(self, string: str) -> Result[str, str]:
     """Check that the starting character is a capital."""
     if len(string) > 0 and not string[0].isupper():
         return Err("Starting character is not uppercase.")
     return Ok(string)
Beispiel #18
0
 def _validate_chars(self, string: str) -> Result[str, str]:
     """Check that none of the strings have disallowed chars."""
     if set(string).intersection(set(self.DISALLOWED_CHARS)):
         return Err("String has disallowed chars")
     return Ok(string)
Beispiel #19
0
 def test_unwrap_err_is_err(self) -> None:
     """.unwrap_err() returns an error value."""
     assert Err(5).unwrap_err() == 5
Beispiel #20
0
 def process_int(i: int) -> Result[MyInt, RuntimeError]:
     return Err(RuntimeError(f"it broke {i}"))
Beispiel #21
0
 def test_stringify(self) -> None:
     """Repr and str representations are equivalent."""
     assert repr(Ok(1)) == str(Ok(1)) == "Ok(1)"
     assert repr(Err(1)) == str(Err(1)) == "Err(1)"
Beispiel #22
0
 def test_expect_err_err(self) -> None:
     """Expecting an Ok() value returns the value."""
     assert Err(2).expect_err("err") == 2
Beispiel #23
0
async def try_download(id, url):
    try:
        response = await asks.get(url)
        return Ok((id, response.text))
    except Exception as err:
        return Err(err)
Beispiel #24
0
 def test_is_err(self) -> None:
     """Err() returns true for is_err()."""
     assert Err(1).is_err()
     assert not Ok(1).is_err()
Beispiel #25
0
class TestResult:
    """Test the result type."""
    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Ok(2), Err("late error"), Err("late error")),
            (Err("early error"), Ok(2), Err("early error")),
            (Err("early error"), Err("late error"), Err("early error")),
            (Ok(2), Ok(3), Ok(3)),
        ),
    )
    def test_and(
        self,
        left: Result[int, str],
        right: Result[int, str],
        exp: Result[int, str],
    ) -> None:
        """Test that `and` returns an alternative for `Ok` values."""
        assert left.and_(right) == exp

    def test_and_multichain(self) -> None:
        """.and() calls can be chained indefinitely."""
        assert Ok(2).and_(Ok(3)).and_(Ok(4)).and_(Ok(5)) == Ok(5)

    @pytest.mark.parametrize(
        "start, first, second, exp",
        (
            (Ok(2), _sq, _sq, Ok(16)),
            (Ok(2), _sq, _err, Err(4)),
            (Ok(2), _err, _sq, Err(2)),
            (Ok(2), _err, _err, Err(2)),
            (Err(3), _sq, _sq, Err(3)),
        ),
    )
    def test_and_then(
        self,
        start: Result[int, int],
        first: t.Callable[[int], Result[int, int]],
        second: t.Callable[[int], Result[int, int]],
        exp: Result[int, int],
    ) -> None:
        """Test that and_then chains result-generating functions."""
        assert start.and_then(first).and_then(second) == exp

    def test_and_then_covariance(self) -> None:
        """Covariant errors are acceptable for flatmapping."""
        class MyInt(int):
            """A subclass of int."""

        def process_int(i: int) -> Result[MyInt, RuntimeError]:
            return Err(RuntimeError(f"it broke {i}"))

        start: Result[int, Exception] = Ok(5)
        # We can flatmap w/a function that takes any covariant type of
        # int or Exception. The result remains the original exception type,
        # since we cannot guarantee narrowing to the covariant type.
        flatmapped: Result[int, Exception] = start.and_then(process_int)
        assert flatmapped

    def test_flatmap(self) -> None:
        """Flatmap is an alias for and_then"""
        ok: Result[int, int] = Ok(2)
        err: Result[int, int] = Err(2)
        assert ok.flatmap(_sq) == Ok(4)
        assert err.flatmap(_sq) == Err(2)

    @pytest.mark.parametrize(
        "start, first, second, exp",
        (
            (Ok(2), _sq, _sq, Ok(2)),
            (Ok(2), _err, _sq, Ok(2)),
            (Err(3), _sq, _err, Ok(9)),
            (Err(3), _err, _err, Err(3)),
        ),
    )
    def test_or_else(
        self,
        start: Result[int, int],
        first: t.Callable[[int], Result[int, int]],
        second: t.Callable[[int], Result[int, int]],
        exp: Result[int, int],
    ) -> None:
        """Test that and_then chains result-generating functions."""
        assert start.or_else(first).or_else(second) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(2), Nothing()), (Err("err"), Some("err"))))
    def test_err(self, start: Result[int, str], exp: Option[str]) -> None:
        """Test converting a result to an option."""
        assert start.err() == exp

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_expect_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test expecting a value to be Ok()."""
        exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        input_val = 2
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Err(input_val).expect(msg, **kwargs)

        assert msg in str(exc_info.value)
        assert str(input_val) in str(exc_info.value)

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_raise_if_err_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test raise_if_err for Err() values."""
        exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        input_val = 2
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Err(input_val).raise_if_err(msg, **kwargs)

        assert msg in str(exc_info.value)
        assert str(input_val) in str(exc_info.value)

    def test_expect_ok(self) -> None:
        """Expecting an Ok() value returns the value."""
        assert Ok(2).expect("err") == 2

    def test_raise_if_err_ok(self) -> None:
        """Raise_if_err returns the value when given an Ok() value."""
        assert Ok(2).raise_if_err("err") == 2

    @pytest.mark.parametrize("exc_cls", (None, IOError))
    def test_expect_err_raising(self, exc_cls: t.Type[Exception]) -> None:
        """Test expecting a value to be Ok()."""
        exp_exc: t.Type[Exception] = exc_cls if exc_cls else RuntimeError
        kwargs = {"exc_cls": exc_cls} if exc_cls else {}
        msg = "not what I expected"

        with pytest.raises(exp_exc) as exc_info:
            Ok(2).expect_err(msg, **kwargs)

        assert msg in str(exc_info.value)

    def test_expect_err_err(self) -> None:
        """Expecting an Ok() value returns the value."""
        assert Err(2).expect_err("err") == 2

    def test_is_err(self) -> None:
        """Err() returns true for is_err()."""
        assert Err(1).is_err()
        assert not Ok(1).is_err()

    def test_is_ok(self) -> None:
        """Ok() returns true for is_ok()."""
        assert Ok(1).is_ok()
        assert not Err(1).is_ok()

    @pytest.mark.parametrize("start, exp", ((Ok(1), (1, )), (Err(1), ())))
    def test_iter(self, start: Result[int, int], exp: t.Tuple[int,
                                                              ...]) -> None:
        """iter() returns a 1-member iterator on Ok(), 0-member for Err()."""
        assert tuple(start.iter()) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(2), Ok(4)), (Err("foo"), Err("foo"))))
    def test_map(self, start: Result[int, str], exp: Result[int, str]) -> None:
        """.map() will map onto Ok() and ignore Err()."""
        assert start.map(lambda x: int(x**2)) == exp

    def test_map_covariance(self) -> None:
        """The input type to the map fn is covariant."""
        class MyStr(str):
            """Subclass of str."""

        def to_mystr(string: str) -> MyStr:
            return MyStr(string) if not isinstance(string, MyStr) else string

        start: Result[str, str] = Ok("foo")
        # We can assign the result to [str, str] even though we know it's
        # actually a MyStr, since MyStr is covariant with str
        end: Result[str, str] = start.map(to_mystr)
        assert end == Ok(MyStr("foo"))

    @pytest.mark.parametrize("start, exp",
                             ((Ok("foo"), Ok("foo")), (Err(2), Err("2"))))
    def test_map_err(self, start: Result[str, int], exp: Result[str,
                                                                str]) -> None:
        """.map_err() will map onto Err() and ignore Ok()."""
        assert start.map_err(str) == exp

    @pytest.mark.parametrize("start, exp",
                             ((Ok(1), Some(1)), (Err(1), Nothing())))
    def test_ok(self, start: Result[int, int], exp: Option[int]) -> None:
        """.ok() converts a result to an Option."""
        assert start.ok() == exp

    @pytest.mark.parametrize(
        "left, right, exp",
        (
            (Ok(5), Ok(6), Ok(5)),
            (Ok(5), Err(6), Ok(5)),
            (Err(5), Ok(6), Ok(6)),
            (Err(5), Err(6), Err(6)),
        ),
    )
    def test_or(
        self,
        left: Result[int, str],
        right: Result[int, str],
        exp: Result[int, str],
    ) -> None:
        """.or_() returns the first available non-err value."""
        assert left.or_(right) == exp

    def test_or_multichain(self) -> None:
        """.or_() calls can be chained indefinitely."""
        err: Result[int, int] = Err(5)
        assert err.or_(Err(6)).or_(Err(7)).or_(Ok(8)) == Ok(8)

    def test_unwrap_is_ok(self) -> None:
        """.unwrap() returns an ok() value."""
        assert Ok(5).unwrap() == 5

    def test_unwrap_is_err(self) -> None:
        """.unwrap() raises for an error value."""
        with pytest.raises(RuntimeError):
            Err(5).unwrap()

    def test_unwrap_err_is_ok(self) -> None:
        """.unwrap_err() raises for an ok value."""
        with pytest.raises(RuntimeError):
            Ok(5).unwrap_err()

    def test_unwrap_err_is_err(self) -> None:
        """.unwrap_err() returns an error value."""
        assert Err(5).unwrap_err() == 5

    @pytest.mark.parametrize("start, alt, exp",
                             ((Ok(5), 6, 5), (Err(5), 6, 6)))
    def test_unwrap_or(self, start: Result[int, int], alt: int,
                       exp: int) -> None:
        """.unwrap_or() provides the default if the result is Err()."""
        assert start.unwrap_or(alt) == exp

    @pytest.mark.parametrize(
        "start, fn, exp",
        ((Ok(5), lambda i: i + 2, 5), (Err(5), lambda i: i + 2, 7)),
    )
    def test_unwrap_or_else(self, start: Result[int, int],
                            fn: t.Callable[[int], int], exp: int) -> None:
        """Calculates a result from Err() value if present."""
        assert start.unwrap_or_else(fn) == exp

    @pytest.mark.parametrize(
        "inst, other, eq",
        (
            (Ok(1), Ok(1), True),
            (Ok(1), Ok(2), False),
            (Ok(1), Err(1), False),
            (Ok(1), 1, False),
            (Err(1), Err(1), True),
            (Err(1), Err(2), False),
            (Err(1), Ok(1), False),
            (Err(1), 1, False),
        ),
    )
    def test_equality_inequality(self, inst: t.Any, other: t.Any,
                                 eq: bool) -> None:
        """Test equality and inequality of results."""
        assert (inst == other) is eq
        assert (inst != other) is not eq

    def test_stringify(self) -> None:
        """Repr and str representations are equivalent."""
        assert repr(Ok(1)) == str(Ok(1)) == "Ok(1)"
        assert repr(Err(1)) == str(Err(1)) == "Err(1)"
Beispiel #26
0
 def connect(self, fail: bool = False) -> Result["MonadicDataStore", str]:
     if fail:
         return Err("failed to connect")
     return Ok(self)