summary refs log tree commit diff stats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to 'tests')
-rw-r--r--tests/common/errorfunc.rs89
-rw-r--r--tests/common/errorpanic.rs114
-rw-r--r--tests/common/mod.rs12
-rw-r--r--tests/strcursor.rs592
4 files changed, 807 insertions, 0 deletions
diff --git a/tests/common/errorfunc.rs b/tests/common/errorfunc.rs
new file mode 100644
index 0000000..99ba641
--- /dev/null
+++ b/tests/common/errorfunc.rs
@@ -0,0 +1,89 @@
+// Copyright (c) 2021 Soni L.
+
+use ::std::marker::PhantomData;
+
+use ::iosonism::strcursor::ReadError;
+use ::iosonism::strcursor::StringReader;
+
+/// An error callback.
+pub trait ErrorFunc<'a, C: StringReader<'a>> {
+    fn call<'b>(context: &C, ty: ErrorType<'b>);
+}
+
+/// An implementation of various Iosonism errors that calls T.
+pub struct ErrorCall<T>(PhantomData<T>);
+
+#[non_exhaustive]
+#[derive(PartialEq, Eq, Debug)]
+pub enum ErrorType<'a> {
+    InvalidInteger(&'a str),
+    ExpectedInteger,
+    InvalidFloat(&'a str),
+    ExpectedFloat,
+    InvalidBool(&'a str),
+    ExpectedBool,
+    ExpectedStartOfQuote,
+    ExpectedEndOfQuote,
+    InvalidEscape(&'a str),
+    ExpectedSymbol(&'a str),
+}
+
+impl<T> ::std::fmt::Display for ErrorCall<T> {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "error!")
+    }
+}
+
+impl<T> ::std::fmt::Debug for ErrorCall<T> {
+    fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        write!(f, "ErrorCall")
+    }
+}
+
+impl<T> ::std::error::Error for ErrorCall<T> {
+}
+
+impl<'a, C: StringReader<'a>, T> ReadError<'a, C> for ErrorCall<T>
+where T: ErrorFunc<'a, C> {
+    fn invalid_integer(context: &C, from: &str) -> Self {
+        T::call(context, ErrorType::InvalidInteger(from));
+        Self(PhantomData)
+    }
+    fn expected_integer(context: &C) -> Self {
+        T::call(context, ErrorType::ExpectedInteger);
+        Self(PhantomData)
+    }
+    fn invalid_float(context: &C, from: &str) -> Self {
+        T::call(context, ErrorType::InvalidFloat(from));
+        Self(PhantomData)
+    }
+    fn expected_float(context: &C) -> Self {
+        T::call(context, ErrorType::ExpectedFloat);
+        Self(PhantomData)
+    }
+    fn invalid_bool(context: &C, from: &str) -> Self {
+        T::call(context, ErrorType::InvalidBool(from));
+        Self(PhantomData)
+    }
+    fn expected_bool(context: &C) -> Self {
+        T::call(context, ErrorType::ExpectedBool);
+        Self(PhantomData)
+    }
+    fn expected_start_of_quote(context: &C) -> Self {
+        T::call(context, ErrorType::ExpectedStartOfQuote);
+        Self(PhantomData)
+    }
+    fn expected_end_of_quote(context: &C) -> Self {
+        T::call(context, ErrorType::ExpectedEndOfQuote);
+        Self(PhantomData)
+    }
+    fn invalid_escape(context: &C, from: &str) -> Self {
+        T::call(context, ErrorType::InvalidEscape(from));
+        Self(PhantomData)
+    }
+    fn expected_symbol(context: &C, from: &str) -> Self {
+        T::call(context, ErrorType::ExpectedSymbol(from));
+        Self(PhantomData)
+    }
+}
+
diff --git a/tests/common/errorpanic.rs b/tests/common/errorpanic.rs
new file mode 100644
index 0000000..202c4be
--- /dev/null
+++ b/tests/common/errorpanic.rs
@@ -0,0 +1,114 @@
+// Copyright (c) 2021 Soni L.
+
+use ::iosonism::strcursor::ReadError;
+use ::iosonism::strcursor::StringReader;
+
+/// An implementation of various Iosonism errors that just panics.
+#[derive(Debug)]
+pub enum ErrorPanic {
+    // uninhabitable!
+}
+
+impl ::std::fmt::Display for ErrorPanic {
+    fn fmt(&self, _: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
+        match *self {
+        }
+    }
+}
+
+impl ::std::error::Error for ErrorPanic {
+}
+
+impl<'a, C: StringReader<'a>> ReadError<'a, C> for ErrorPanic {
+    fn invalid_integer(context: &C, from: &str) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!(
+                "invalid integer: {} at ...{}",
+                from,
+                context.get_remaining(),
+            );
+        } else {
+            panic!("invalid integer: {}", from);
+        }
+    }
+    fn expected_integer(context: &C) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!("expected integer at ...{}", context.get_remaining());
+        } else {
+            panic!("expected integer");
+        }
+    }
+    fn invalid_float(context: &C, from: &str) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!(
+                "invalid float: {} at ...{}",
+                from,
+                context.get_remaining(),
+            );
+        } else {
+            panic!("invalid float: {}", from);
+        }
+    }
+    fn expected_float(context: &C) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!("expected float at ...{}", context.get_remaining());
+        } else {
+            panic!("expected float");
+        }
+    }
+    fn invalid_bool(context: &C, from: &str) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!(
+                "invalid bool: {} at ...{}",
+                from,
+                context.get_remaining(),
+            );
+        } else {
+            panic!("invalid bool: {}", from);
+        }
+    }
+    fn expected_bool(context: &C) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!("expected bool at ...{}", context.get_remaining());
+        } else {
+            panic!("expected bool");
+        }
+    }
+    fn expected_start_of_quote(context: &C) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!("expected start of quote at ...{}", context.get_remaining());
+        } else {
+            panic!("expected start of quote");
+        }
+    }
+    fn expected_end_of_quote(context: &C) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!("expected end of quote at ...{}", context.get_remaining());
+        } else {
+            panic!("expected end of quote");
+        }
+    }
+    fn invalid_escape(context: &C, from: &str) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!(
+                "invalid escape: {} at ...{}",
+                from,
+                context.get_remaining(),
+            );
+        } else {
+            panic!("invalid escape: {}", from);
+        }
+    }
+    fn expected_symbol(context: &C, from: &str) -> Self {
+        if !context.get_remaining().is_empty() {
+            panic!(
+                "expected symbol: {} at ...{}",
+                from,
+                context.get_remaining(),
+            );
+        } else {
+            panic!("expected symbol: {}", from);
+        }
+    }
+}
+
diff --git a/tests/common/mod.rs b/tests/common/mod.rs
new file mode 100644
index 0000000..6760da1
--- /dev/null
+++ b/tests/common/mod.rs
@@ -0,0 +1,12 @@
+// Copyright (c) 2021 Soni L.
+
+// see rationale in tests/*.rs
+#![warn(non_snake_case)]
+
+pub mod errorfunc;
+pub mod errorpanic;
+
+pub use self::errorfunc::ErrorCall;
+pub use self::errorfunc::ErrorFunc;
+pub use self::errorfunc::ErrorType;
+pub use self::errorpanic::ErrorPanic;
diff --git a/tests/strcursor.rs b/tests/strcursor.rs
new file mode 100644
index 0000000..3ec876e
--- /dev/null
+++ b/tests/strcursor.rs
@@ -0,0 +1,592 @@
+// Copyright (c) 2021 Soni L.
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT license.
+
+// because we wanna use double underscore (__) for test names
+#![allow(non_snake_case)]
+
+use ::std::io::Cursor;
+
+use ::iosonism::strcursor::StringReader;
+
+mod common;
+
+use self::common::ErrorCall;
+use self::common::ErrorFunc;
+use self::common::ErrorPanic;
+use self::common::ErrorType;
+
+#[test]
+fn test_can_read() {
+    let mut reader = Cursor::new("abc");
+    assert_eq!(reader.can_read(), true);
+    reader.skip(); // 'a'
+    assert_eq!(reader.can_read(), true);
+    reader.skip(); // 'b'
+    assert_eq!(reader.can_read(), true);
+    reader.skip(); // 'c'
+    assert_eq!(reader.can_read(), false);
+}
+
+#[test]
+fn test_get_remaining_len() {
+    let mut reader = Cursor::new("abc");
+    assert_eq!(reader.get_remaining().len(), 3);
+    reader.set_position(1);
+    assert_eq!(reader.get_remaining().len(), 2);
+    reader.set_position(2);
+    assert_eq!(reader.get_remaining().len(), 1);
+    reader.set_position(3);
+    assert_eq!(reader.get_remaining().len(), 0);
+}
+
+#[test]
+fn test_can_read_n() {
+    let reader = Cursor::new("abc");
+    assert_eq!(reader.can_read_n(1), true);
+    assert_eq!(reader.can_read_n(2), true);
+    assert_eq!(reader.can_read_n(3), true);
+    assert_eq!(reader.can_read_n(4), false);
+    assert_eq!(reader.can_read_n(5), false);
+}
+
+#[test]
+fn test_peek() {
+    let mut reader = Cursor::new("abc");
+    assert_eq!(reader.peek(), 'a');
+    assert_eq!(reader.position(), 0);
+    reader.set_position(2);
+    assert_eq!(reader.peek(), 'c');
+    assert_eq!(reader.position(), 2);
+}
+
+#[test]
+fn test_peek_n() {
+    let mut reader = Cursor::new("abc");
+    assert_eq!(reader.peek_n(0), 'a');
+    assert_eq!(reader.peek_n(2), 'c');
+    assert_eq!(reader.position(), 0);
+    reader.set_position(1);
+    assert_eq!(reader.peek_n(1), 'c');
+    assert_eq!(reader.position(), 1);
+}
+
+#[test]
+fn test_read_char() {
+    let mut reader = Cursor::new("abc");
+    assert_eq!(reader.read_char(), Some('a'));
+    assert_eq!(reader.read_char(), Some('b'));
+    assert_eq!(reader.read_char(), Some('c'));
+    assert_eq!(reader.position(), 3);
+}
+
+#[test]
+fn test_skip() {
+    let mut reader = Cursor::new("abc");
+    reader.skip();
+    assert_eq!(reader.position(), 1);
+}
+
+#[test]
+fn test_get_remaining() {
+    let mut reader = Cursor::new("Hello!");
+    assert_eq!(reader.get_remaining(), "Hello!");
+    reader.set_position(3);
+    assert_eq!(reader.get_remaining(), "lo!");
+    reader.set_position(6);
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_get_read() {
+    let mut reader = Cursor::new("Hello!");
+    assert_eq!(reader.get_read(), "");
+    reader.set_position(3);
+    assert_eq!(reader.get_read(), "Hel");
+    reader.set_position(6);
+    assert_eq!(reader.get_read(), "Hello!");
+}
+
+#[test]
+fn test_skip_whitespace__none() {
+    let mut reader = Cursor::new("Hello!");
+    reader.skip_whitespace();
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_skip_whitespace__mixed() {
+    let mut reader = Cursor::new(" \t \t\nHello!");
+    reader.skip_whitespace();
+    assert_eq!(reader.position(), 5);
+}
+
+#[test]
+fn test_skip_whitespace__empty() {
+    let mut reader = Cursor::new("");
+    reader.skip_whitespace();
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_unquoted_str() {
+    let mut reader = Cursor::new("hello world");
+    assert_eq!(reader.read_unquoted_str(), "hello");
+    assert_eq!(reader.get_read(), "hello");
+    assert_eq!(reader.get_remaining(), " world");
+}
+
+#[test]
+fn test_read_unquoted_str__empty() {
+    let mut reader = Cursor::new("");
+    assert_eq!(reader.read_unquoted_str(), "");
+    assert_eq!(reader.get_read(), "");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_unquoted_str__empty_with_remaining() {
+    let mut reader = Cursor::new(" hello world");
+    assert_eq!(reader.read_unquoted_str(), "");
+    assert_eq!(reader.get_read(), "");
+    assert_eq!(reader.get_remaining(), " hello world");
+}
+
+#[test]
+fn test_read_quoted_string() {
+    let mut reader = Cursor::new("\"hello world\"");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "\"hello world\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__single() {
+    let mut reader = Cursor::new("'hello world'");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "'hello world'");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__double_inside_single() {
+    let mut reader = Cursor::new("'hello \"world\"'");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello \"world\"",
+    );
+    assert_eq!(reader.get_read(), "'hello \"world\"'");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__single_inside_double() {
+    let mut reader = Cursor::new("\"hello 'world'\"");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello 'world'",
+    );
+    assert_eq!(reader.get_read(), "\"hello 'world'\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__empty() {
+    let mut reader = Cursor::new("");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "",
+    );
+    assert_eq!(reader.get_read(), "");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__empty_quoted() {
+    let mut reader = Cursor::new("\"\"");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "",
+    );
+    assert_eq!(reader.get_read(), "\"\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+
+#[test]
+fn test_read_quoted_string__empty_quoted_with_remaining() {
+    let mut reader = Cursor::new("\"\" hello world");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "",
+    );
+    assert_eq!(reader.get_read(), "\"\"");
+    assert_eq!(reader.get_remaining(), " hello world");
+}
+
+#[test]
+fn test_read_quoted_string__with_escaped_quote() {
+    let mut reader = Cursor::new("\"hello \\\"world\\\"\"");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello \"world\"",
+    );
+    assert_eq!(reader.get_read(), "\"hello \\\"world\\\"\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__with_escaped_escapes() {
+    let mut reader = Cursor::new("\"\\\\o/\"");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "\\o/",
+    );
+    assert_eq!(reader.get_read(), "\"\\\\o/\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_quoted_string__with_remaining() {
+    let mut reader = Cursor::new("\"hello world\" foo bar");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "\"hello world\"");
+    assert_eq!(reader.get_remaining(), " foo bar");
+}
+
+#[test]
+fn test_read_quoted_string__with_immediate_remaining() {
+    let mut reader = Cursor::new("\"hello world\"foo bar");
+    assert_eq!(
+        reader.read_quoted_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "\"hello world\"");
+    assert_eq!(reader.get_remaining(), "foo bar");
+}
+
+#[test]
+fn test_read_quoted_string__no_open() {
+    let mut reader = Cursor::new("hello world\"");
+    assert!(reader.read_quoted_string::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedStartOfQuote);
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_quoted_string__no_close() {
+    let mut reader = Cursor::new("\"hello world");
+    assert!(reader.read_quoted_string::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedEndOfQuote);
+            assert_eq!(context.position(), 12);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_quoted_string__invalid_escape() {
+    let mut reader = Cursor::new("\"hello\\nworld\"");
+    assert!(reader.read_quoted_string::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::InvalidEscape("n"));
+            // NOTE: brigadier makes this 7. we make this 8.
+            // FIXME: maybe do the same as brigadier?
+            assert_eq!(context.position(), 8);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_quoted_string__invalid_quote_escape() {
+    let mut reader = Cursor::new("'hello\\\"'world");
+    assert!(reader.read_quoted_string::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::InvalidEscape("\""));
+            assert_eq!(context.position(), 8);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_string__no_quotes() {
+    let mut reader = Cursor::new("hello world");
+    assert_eq!(
+        reader.read_string::<ErrorPanic>().unwrap(),
+        "hello",
+    );
+    assert_eq!(reader.get_read(), "hello");
+    assert_eq!(reader.get_remaining(), " world");
+}
+
+#[test]
+fn test_read_string__single_quotes() {
+    let mut reader = Cursor::new("'hello world'");
+    assert_eq!(
+        reader.read_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "'hello world'");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_string__double_quotes() {
+    let mut reader = Cursor::new("\"hello world\"");
+    assert_eq!(
+        reader.read_string::<ErrorPanic>().unwrap(),
+        "hello world",
+    );
+    assert_eq!(reader.get_read(), "\"hello world\"");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_integer() {
+    let mut reader = Cursor::new("1234567890");
+    assert_eq!(
+        reader.read_integer::<i32, ErrorPanic>().unwrap(),
+        1234567890,
+    );
+    assert_eq!(reader.get_read(), "1234567890");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_integer__negative() {
+    let mut reader = Cursor::new("-1234567890");
+    assert_eq!(
+        reader.read_integer::<i32, ErrorPanic>().unwrap(),
+        -1234567890,
+    );
+    assert_eq!(reader.get_read(), "-1234567890");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+
+#[test]
+fn test_read_integer__invalid() {
+    let mut reader = Cursor::new("12.34");
+    assert!(reader.read_integer::<i32, ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::InvalidInteger("12.34"));
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_integer__none() {
+    let mut reader = Cursor::new("");
+    assert!(reader.read_integer::<i32, ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedInteger);
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_integer__with_remaining() {
+    let mut reader = Cursor::new("1234567890 foo bar");
+    assert_eq!(
+        reader.read_integer::<i32, ErrorPanic>().unwrap(),
+        1234567890,
+    );
+    assert_eq!(reader.get_read(), "1234567890");
+    assert_eq!(reader.get_remaining(), " foo bar");
+}
+
+#[test]
+fn test_read_integer__with_remaining_immediate() {
+    let mut reader = Cursor::new("1234567890foo bar");
+    assert_eq!(
+        reader.read_integer::<i32, ErrorPanic>().unwrap(),
+        1234567890,
+    );
+    assert_eq!(reader.get_read(), "1234567890");
+    assert_eq!(reader.get_remaining(), "foo bar");
+}
+
+#[test]
+fn test_read_float() {
+    let mut reader = Cursor::new("123");
+    assert_eq!(
+        reader.read_float::<f32, ErrorPanic>().unwrap(),
+        123.0,
+    );
+    assert_eq!(reader.get_read(), "123");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_float__with_decimal() {
+    let mut reader = Cursor::new("12.34");
+    assert_eq!(
+        reader.read_float::<f32, ErrorPanic>().unwrap(),
+        12.34,
+    );
+    assert_eq!(reader.get_read(), "12.34");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_float__negative() {
+    let mut reader = Cursor::new("-123");
+    assert_eq!(
+        reader.read_float::<f32, ErrorPanic>().unwrap(),
+        -123.0,
+    );
+    assert_eq!(reader.get_read(), "-123");
+    assert_eq!(reader.get_remaining(), "");
+}
+
+#[test]
+fn test_read_float__invalid() {
+    let mut reader = Cursor::new("12.34.56");
+    assert!(reader.read_float::<f32, ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::InvalidFloat("12.34.56"));
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_float__none() {
+    let mut reader = Cursor::new("");
+    assert!(reader.read_float::<f32, ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedFloat);
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_float__with_remaining() {
+    let mut reader = Cursor::new("12.34 foo bar");
+    assert_eq!(
+        reader.read_float::<f32, ErrorPanic>().unwrap(),
+        12.34,
+    );
+    assert_eq!(reader.get_read(), "12.34");
+    assert_eq!(reader.get_remaining(), " foo bar");
+}
+
+#[test]
+fn test_read_float__with_remaining_immediate() {
+    let mut reader = Cursor::new("12.34foo bar");
+    assert_eq!(
+        reader.read_float::<f32, ErrorPanic>().unwrap(),
+        12.34,
+    );
+    assert_eq!(reader.get_read(), "12.34");
+    assert_eq!(reader.get_remaining(), "foo bar");
+}
+
+#[test]
+fn test_expect__correct() {
+    let mut reader = Cursor::new("abc");
+    assert!(reader.expect::<ErrorPanic>('a').is_ok());
+    assert_eq!(reader.position(), 1);
+}
+
+#[test]
+fn test_expect__incorrect() {
+    let mut reader = Cursor::new("bcd");
+    assert!(reader.expect::<ErrorCall<ErrFn>>('a').is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedSymbol("a"));
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_expect__none() {
+    let mut reader = Cursor::new("");
+    assert!(reader.expect::<ErrorCall<ErrFn>>('a').is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedSymbol("a"));
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_bool__correct() {
+    let mut reader = Cursor::new("true");
+    assert_eq!(reader.read_bool::<ErrorPanic>().unwrap(), true);
+    assert_eq!(reader.get_read(), "true");
+}
+
+#[test]
+fn test_read_bool__incorrect() {
+    let mut reader = Cursor::new("tuesday");
+    assert!(reader.read_bool::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::InvalidBool("tuesday"));
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}
+
+#[test]
+fn test_read_bool__none() {
+    let mut reader = Cursor::new("");
+    assert!(reader.read_bool::<ErrorCall<ErrFn>>().is_err());
+    struct ErrFn;
+    impl<'a> ErrorFunc<'a, Cursor<&'a str>> for ErrFn {
+        fn call(context: &Cursor<&'a str>, ty: ErrorType) {
+            assert_eq!(ty, ErrorType::ExpectedBool);
+            assert_eq!(context.position(), 0);
+        }
+    }
+    assert_eq!(reader.position(), 0);
+}