From 8ab7d3a8c05a5f6a63268f35df501153487b1b86 Mon Sep 17 00:00:00 2001 From: Scott Anderson Date: Mon, 3 Jun 2024 00:12:23 -0400 Subject: [PATCH] Replace asserts with errors --- src/cursor_ext.rs | 10 +- src/error.rs | 79 ++++--- src/properties/array_property.rs | 2 +- src/properties/mod.rs | 143 ++++++------ src/properties/set_property.rs | 20 +- tests/gvas_tests/errors.rs | 371 +++++++++++++++++++++++++++++++ tests/gvas_tests/mod.rs | 1 + tests/gvas_tests/test_cursor.rs | 18 +- 8 files changed, 517 insertions(+), 127 deletions(-) create mode 100644 tests/gvas_tests/errors.rs diff --git a/src/cursor_ext.rs b/src/cursor_ext.rs index ac05de1..96fc31d 100644 --- a/src/cursor_ext.rs +++ b/src/cursor_ext.rs @@ -71,14 +71,8 @@ impl ReadExt for R { { let value = self.read_i8()?; let result = T::try_from(value).map_err(|_| { - DeserializeError::InvalidEnumValue( - format!( - "No discriminant in enum `{}` matches the value `{}`", - std::any::type_name::(), - value, - ) - .into_boxed_str(), - ) + let name = std::any::type_name::(); + DeserializeError::invalid_enum_value(name, value, self) })?; Ok(result) } diff --git a/src/error.rs b/src/error.rs index cab080f..709cca3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use num_enum::{TryFromPrimitive, TryFromPrimitiveError}; use std::io; use thiserror::Error; @@ -11,48 +10,69 @@ pub enum DeserializeError { #[error("Invalid header: {0}")] InvalidHeader(String), /// If a value has a size that was unexpected, e.g. UInt32Property has 8 bytes size - #[error("Invalid value size, expected {0} got {1} at position {2}")] + #[error("Invalid value size, expected {0} got {1} at position {2:#x}")] InvalidValueSize(u64, u64, u64), /// If a string has invalid size - #[error("Invalid string size, got {0} at position {1}")] + #[error("Invalid string size {0} at position {1:#x}")] InvalidString(u32, u64), /// If a boolean has invalid value - #[error("Invalid boolean value, got {0} at position {1}")] + #[error("Invalid boolean value, got {0} at position {1:#x}")] InvalidBoolean(u32, u64), /// If a hint is missing. - #[error("Missing hint for struct {0} at path {1}, cursor position: {2}")] + #[error("Missing hint for struct {0} at path {1} at position {2:#x}")] MissingHint(String, String, u64), /// If an argument is missing - #[error("Missing argument: {0} at position {1}")] + #[error("Missing argument: {0} at position {1:#x}")] MissingArgument(String, u64), - /// If an EnumProperty has an invalid enum type - #[error("Invalid enum type {0} at position {1}")] - InvalidEnumType(String, u64), /// If a Property creation fails - #[error("Invalid property {0} at position {1}")] + #[error("Invalid property {0} at position {1:#x}")] InvalidProperty(String, u64), /// Invalid enum value - #[error("{0}")] - InvalidEnumValue(Box), -} - -impl From> for DeserializeError { - fn from(value: TryFromPrimitiveError) -> Self { - DeserializeError::InvalidEnumValue(value.to_string().into_boxed_str()) - } + #[error("No discriminant in enum `{0}` matches the value `{1}` at position {2:#x}")] + InvalidEnumValue(String, i8, u64), + /// Invalid array index header + #[error("Unexpected array_index value {0} at position {1:#x}")] + InvalidArrayIndex(u32, u64), + /// Invalid terminator + #[error("Unexpected terminator value {0} at position {1:#x}")] + InvalidTerminator(u8, u64), + /// Invalid body length + #[error("Invalid body length: read {0:#x}, expected {1:#x} at position {2:#x}\n{3}")] + InvalidBodyLength(u64, u32, u64, String), } impl DeserializeError { /// A helper for creating `MissingArgument` errors - pub fn missing_argument(argument_name: &str, stream: &mut S) -> Self { + #[inline] + pub fn missing_argument(argument_name: A, stream: &mut S) -> Self + where + A: Into, + S: io::Seek, + { let position = stream.stream_position().unwrap_or_default(); - Self::MissingArgument(argument_name.to_string(), position) + Self::MissingArgument(argument_name.into(), position) } /// A helper for creating `InvalidProperty` errors - pub fn invalid_property(reason: &str, stream: &mut S) -> Self { + #[inline] + pub fn invalid_property(reason: R, stream: &mut S) -> Self + where + R: Into, + S: io::Seek, + { + let position = stream.stream_position().unwrap_or_default(); + Self::InvalidProperty(reason.into(), position) + } + + /// A helper for creating `InvalidEnumValue` errors + #[inline] + pub fn invalid_enum_value(name: N, value: i8, stream: &mut S) -> Self + where + N: Into, + S: io::Seek, + { let position = stream.stream_position().unwrap_or_default(); - Self::InvalidProperty(reason.to_string(), position) + Self::InvalidEnumValue(name.into(), value, position) } } @@ -69,13 +89,20 @@ pub enum SerializeError { impl SerializeError { /// A helper for creating `InvalidValue` errors - pub fn invalid_value(msg: &str) -> Self { - Self::InvalidValue(msg.to_string()) + pub fn invalid_value(msg: M) -> Self + where + M: Into, + { + Self::InvalidValue(msg.into()) } /// A helper for creating `StructMissingField` errors - pub fn struct_missing_field(type_name: &str, missing_field: &str) -> Self { - Self::StructMissingField(type_name.to_string(), missing_field.to_string()) + pub fn struct_missing_field(type_name: T, missing_field: M) -> Self + where + T: Into, + M: Into, + { + Self::StructMissingField(type_name.into(), missing_field.into()) } } diff --git a/src/properties/array_property.rs b/src/properties/array_property.rs index ac64d29..8a07eb0 100644 --- a/src/properties/array_property.rs +++ b/src/properties/array_property.rs @@ -237,7 +237,7 @@ impl ArrayProperty { guid, structs, }), - Err(p) => Err(SerializeError::invalid_value(&format!( + Err(p) => Err(SerializeError::invalid_value(format!( "Array property_type {} doesn't match property inside array: {:#?}", property_type, p )))?, diff --git a/src/properties/mod.rs b/src/properties/mod.rs index 4539784..13adf82 100644 --- a/src/properties/mod.rs +++ b/src/properties/mod.rs @@ -150,88 +150,85 @@ macro_rules! impl_read { /// ``` macro_rules! impl_read_header { (options, length $(, $var:ident)*) => { + /// Read GVAS property data from a reader. #[inline] - fn read_header( + pub fn read_header( reader: &mut R, options: &mut PropertyOptions, ) -> Result { let length = reader.read_u32::()?; let array_index = reader.read_u32::()?; - assert_eq!( - array_index, - 0, - "Expected array_index value zero @ {:#x}", - reader.stream_position()? - 4, - ); + if array_index != 0 { + let position = reader.stream_position()? - 4; + Err($crate::error::DeserializeError::InvalidArrayIndex(array_index, position))? + } $( let $var = reader.read_string()?; )* - let separator = reader.read_u8()?; - assert_eq!( - separator, - 0, - "Expected separator value zero @ {:#x}", - reader.stream_position()? - 1, - ); + let terminator = reader.read_u8()?; + if terminator != 0 { + let position = reader.stream_position()? - 1; + Err($crate::error::DeserializeError::InvalidTerminator(terminator, position))? + } let start = reader.stream_position()?; let result = Self::read_body(reader, options, length $(, $var)*)?; let end = reader.stream_position()?; - assert_eq!( - end - start, - length as u64, - "read_body did not read the expected length {:#x}", - length, - ); + if end - start != length as u64 { + Err($crate::error::DeserializeError::InvalidBodyLength( + end - start, + length, + start, + format!("{result:#?}"), + ))? + } Ok(result) } }; (options $(, $var:ident)*) => { + /// Read GVAS property data from a reader. #[inline] - fn read_header( + pub fn read_header( reader: &mut R, options: &mut PropertyOptions, ) -> Result { let length = reader.read_u32::()?; let array_index = reader.read_u32::()?; - assert_eq!( - array_index, - 0, - "Expected array_index value zero @ {:#x}", - reader.stream_position()? - 4, - ); + if array_index != 0 { + let position = reader.stream_position()? - 4; + Err($crate::error::DeserializeError::InvalidArrayIndex(array_index, position))? + } $( let $var = reader.read_string()?; )* - let separator = reader.read_u8()?; - assert_eq!( - separator, - 0, - "Expected separator value zero @ {:#x}", - reader.stream_position()? - 1, - ); + let terminator = reader.read_u8()?; + if terminator != 0 { + let position = reader.stream_position()? - 1; + Err($crate::error::DeserializeError::InvalidTerminator(terminator, position))? + } let start = reader.stream_position()?; let result = Self::read_body(reader, options $(, $var)*)?; let end = reader.stream_position()?; - assert_eq!( - end - start, - length as u64, - "read_body read {:#x}, expected {:#x}\n{:#?}", - end - start, - length, - result, - ); + if end - start != length as u64 { + Err($crate::error::DeserializeError::InvalidBodyLength( + end - start, + length, + start, + format!("{result:#?}"), + ))? + } Ok(result) } }; (array_index $(, $var:ident)*) => { + /// Read GVAS property data from a reader. #[inline] - fn read_header( + pub fn read_header( reader: &mut R, ) -> Result { let length = reader.read_u32::()?; @@ -239,56 +236,60 @@ macro_rules! impl_read_header { $( let $var = reader.read_string()?; )* - let separator = reader.read_u8()?; - assert_eq!( - separator, - 0, - "Expected separator value zero @ {:#x}", - reader.stream_position()? - 1, - ); + let terminator = reader.read_u8()?; + if terminator != 0 { + let position = reader.stream_position()? - 1; + Err($crate::error::DeserializeError::InvalidTerminator(terminator, position))? + } let start = reader.stream_position()?; let result = Self::read_body(reader, array_index $(, Some($var))*)?; let end = reader.stream_position()?; - assert_eq!( - end - start, - length as u64, - "read_body did not read the expected length {:#x}", - length, - ); + if end - start != length as u64 { + Err($crate::error::DeserializeError::InvalidBodyLength( + end - start, + length, + start, + format!("{result:#?}"), + ))? + } Ok(result) } }; ($($var:ident $(,)? )*) => { + /// Read GVAS property data from a reader. #[inline] - fn read_header( + pub fn read_header( reader: &mut R, ) -> Result { let length = reader.read_u32::()?; let array_index = reader.read_u32::()?; - assert_eq!(array_index, 0, "Expected array_index value zero @ {:#x}", reader.stream_position()? - 4); + if array_index != 0 { + let position = reader.stream_position()? - 4; + Err($crate::error::DeserializeError::InvalidArrayIndex(array_index, position))? + } $( let $var = reader.read_string()?; )* - let separator = reader.read_u8()?; - assert_eq!( - separator, - 0, - "Expected separator value zero @ {:#x}", - reader.stream_position()? - 1, - ); + let terminator = reader.read_u8()?; + if terminator != 0 { + let position = reader.stream_position()? - 1; + Err($crate::error::DeserializeError::InvalidTerminator(terminator, position))? + } let start = reader.stream_position()?; let result = Self::read_body(reader $(, Some($var))*)?; let end = reader.stream_position()?; - assert_eq!( - end - start, - length as u64, - "read_body did not read the expected length {:#x}", - length, - ); + if end - start != length as u64 { + Err($crate::error::DeserializeError::InvalidBodyLength( + end - start, + length, + start, + format!("{result:#?}"), + ))? + } Ok(result) } diff --git a/src/properties/set_property.rs b/src/properties/set_property.rs index e140698..d528ddf 100644 --- a/src/properties/set_property.rs +++ b/src/properties/set_property.rs @@ -64,16 +64,18 @@ impl SetProperty { let element_count = cursor.read_u32::()?; let mut properties: Vec = Vec::with_capacity(element_count as usize); - let total_bytes_per_property = (length - 8) / element_count; + if element_count > 0 { + let total_bytes_per_property = (length - 8) / element_count; - for _ in 0..element_count { - properties.push(Property::new( - cursor, - &property_type, - false, - options, - Some(total_bytes_per_property), - )?) + for _ in 0..element_count { + properties.push(Property::new( + cursor, + &property_type, + false, + options, + Some(total_bytes_per_property), + )?) + } } Ok(SetProperty { diff --git a/tests/gvas_tests/errors.rs b/tests/gvas_tests/errors.rs new file mode 100644 index 0000000..fd9f985 --- /dev/null +++ b/tests/gvas_tests/errors.rs @@ -0,0 +1,371 @@ +use gvas::{ + error::{DeserializeError, Error}, + game_version::GameVersion, + properties::{ + array_property::ArrayProperty, enum_property::EnumProperty, map_property::MapProperty, + set_property::SetProperty, str_property::StrProperty, PropertyOptions, + }, + GvasFile, +}; +use indexmap::IndexMap; +use std::{collections::HashMap, io::Cursor}; + +const UNEXPECTED_EOF: [u8; 0] = []; + +#[test] +fn test_unexpected_eof() { + let mut reader = Cursor::new(UNEXPECTED_EOF); + let result = GvasFile::read(&mut reader, GameVersion::Default); + match result { + Err(Error::Io(e)) => { + assert_eq!(e.to_string(), "failed to fill whole buffer"); + } + _ => panic!("Unexpected result {result:?}"), + } +} + +const INVALID_HEADER: [u8; 4] = [b'G', b'V', b'A', b'Z']; + +#[test] +fn test_invalid_header() { + let mut reader = Cursor::new(INVALID_HEADER); + let result = GvasFile::read(&mut reader, GameVersion::Default); + match result { + Err(Error::Deserialize(DeserializeError::InvalidHeader(reason))) => { + assert_eq!(reason, "File type 1514231367 not recognized"); + } + _ => panic!("Unexpected result {result:?}"), + } +} + +const INVALID_ARRAY_INDEX: [u8; 8] = [ + 0, 0, 0, 0, // length + 1, 0, 0, 0, // array_index +]; + +#[test] +fn test_invalid_array_index() { + // StrProperty + let mut reader = Cursor::new(INVALID_ARRAY_INDEX); + let result = StrProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidArrayIndex(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 4); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // EnumProperty + let mut reader = Cursor::new(INVALID_ARRAY_INDEX); + let result = EnumProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidArrayIndex(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 4); + } + _ => panic!("Unexpected result {result:?}"), + }; + + let mut options = PropertyOptions { + hints: &HashMap::new(), + properties_stack: &mut Vec::new(), + large_world_coordinates: false, + custom_versions: &IndexMap::new(), + }; + + // ArrayProperty + let mut reader = Cursor::new(INVALID_ARRAY_INDEX); + let result = ArrayProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidArrayIndex(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 4); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // SetProperty + let mut reader = Cursor::new(INVALID_ARRAY_INDEX); + let result = SetProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidArrayIndex(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 4); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // MapProperty + let mut reader = Cursor::new(INVALID_ARRAY_INDEX); + let result = MapProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidArrayIndex(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 4); + } + _ => panic!("Unexpected result {result:?}"), + }; +} + +const INVALID_TERMINATOR: [u8; 9] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, // terminator +]; + +const INVALID_TERMINATOR_ENUM: [u8; 14] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // enum_type + 1, // terminator +]; + +const INVALID_TERMINATOR_MAP: [u8; 19] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // key_type + 1, 0, 0, 0, 0, // value_type + 1, // terminator +]; + +#[test] +fn test_invalid_terminator() { + // StrProperty + let mut reader = Cursor::new(INVALID_TERMINATOR); + let result = StrProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidTerminator(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 8); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // EnumProperty + let mut reader = Cursor::new(INVALID_TERMINATOR_ENUM); + let result = EnumProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidTerminator(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 13); + } + _ => panic!("Unexpected result {result:?}"), + }; + + let mut options = PropertyOptions { + hints: &HashMap::new(), + properties_stack: &mut Vec::new(), + large_world_coordinates: false, + custom_versions: &IndexMap::new(), + }; + + // ArrayProperty + let mut reader = Cursor::new(INVALID_TERMINATOR_ENUM); + let result = ArrayProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidTerminator(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 13); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // SetProperty + let mut reader = Cursor::new(INVALID_TERMINATOR_ENUM); + let result = SetProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidTerminator(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 13); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // MapProperty + let mut reader = Cursor::new(INVALID_TERMINATOR_MAP); + let result = MapProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidTerminator(value, position))) => { + assert_eq!(value, 1); + assert_eq!(position, 18); + } + _ => panic!("Unexpected result {result:?}"), + }; +} + +const INVALID_LENGTH_STR: [u8; 13] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 0, // terminator + 0, 0, 0, 0, // string +]; + +const INVALID_LENGTH_ENUM: [u8; 19] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // enum_type + 0, // terminator + 1, 0, 0, 0, 0, // string +]; + +const INVALID_LENGTH_ARRAY: [u8; 18] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // property_type + 0, // terminator + 0, 0, 0, 0, // element_count +]; + +const INVALID_LENGTH_SET: [u8; 22] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // property_type + 0, // terminator + 0, 0, 0, 0, // allocation_flags + 0, 0, 0, 0, // element_count +]; + +const INVALID_LENGTH_MAP: [u8; 27] = [ + 0, 0, 0, 0, // length + 0, 0, 0, 0, // array_index + 1, 0, 0, 0, 0, // key_type + 1, 0, 0, 0, 0, // value_type + 0, // terminator + 0, 0, 0, 0, // allocation_flags + 0, 0, 0, 0, // element_count +]; + +#[test] +fn test_invalid_length() { + // StrProperty + let mut reader = Cursor::new(INVALID_LENGTH_STR); + let result = StrProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidBodyLength( + read, + expected, + position, + result, + ))) => { + assert_eq!(read, 4); + assert_eq!(expected, 0); + assert_eq!(position, 9); + assert_eq!( + result, + r#"StrProperty { + value: None, +}"# + ); + } + _ => panic!("Unexpected result {result:?}"), + } + + // EnumProperty + let mut reader = Cursor::new(INVALID_LENGTH_ENUM); + let result = EnumProperty::read_header(&mut reader); + match result { + Err(Error::Deserialize(DeserializeError::InvalidBodyLength( + read, + expected, + position, + result, + ))) => { + assert_eq!(read, 5); + assert_eq!(expected, 0); + assert_eq!(position, 14); + assert_eq!( + result, + r#"EnumProperty { + enum_type: Some( + "", + ), + value: "", +}"# + ); + } + _ => panic!("Unexpected result {result:?}"), + } + + let mut options = PropertyOptions { + hints: &HashMap::new(), + properties_stack: &mut Vec::new(), + large_world_coordinates: false, + custom_versions: &IndexMap::new(), + }; + + // ArrayProperty + let mut reader = Cursor::new(INVALID_LENGTH_ARRAY); + let result = ArrayProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidBodyLength( + read, + expected, + position, + result, + ))) => { + assert_eq!(read, 4); + assert_eq!(expected, 0); + assert_eq!(position, 14); + assert_eq!( + result, + r#"Properties { + property_type: "", + properties: [], +}"# + ); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // SetProperty + let mut reader = Cursor::new(INVALID_LENGTH_SET); + let result = SetProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidBodyLength( + read, + expected, + position, + result, + ))) => { + assert_eq!(read, 8); + assert_eq!(expected, 0); + assert_eq!(position, 14); + assert_eq!( + result, + r#"SetProperty { + property_type: "", + allocation_flags: 0, + properties: [], +}"# + ); + } + _ => panic!("Unexpected result {result:?}"), + }; + + // MapProperty + let mut reader = Cursor::new(INVALID_LENGTH_MAP); + let result = MapProperty::read_header(&mut reader, &mut options); + match result { + Err(Error::Deserialize(DeserializeError::InvalidBodyLength( + read, + expected, + position, + result, + ))) => { + assert_eq!(read, 8); + assert_eq!(expected, 0); + assert_eq!(position, 19); + assert_eq!( + result, + r#"Properties { + key_type: "", + value_type: "", + allocation_flags: 0, + value: {}, +}"# + ); + } + _ => panic!("Unexpected result {result:?}"), + }; +} diff --git a/tests/gvas_tests/mod.rs b/tests/gvas_tests/mod.rs index cc6311d..0385486 100644 --- a/tests/gvas_tests/mod.rs +++ b/tests/gvas_tests/mod.rs @@ -1,3 +1,4 @@ +mod errors; mod name_arrayindex; mod package_version_524; mod regression_01; diff --git a/tests/gvas_tests/test_cursor.rs b/tests/gvas_tests/test_cursor.rs index d7244af..a3bec23 100644 --- a/tests/gvas_tests/test_cursor.rs +++ b/tests/gvas_tests/test_cursor.rs @@ -40,24 +40,18 @@ fn test_read_string() -> Result<(), Error> { // Null let mut cursor = Cursor::new(vec![0u8; 4]); - let err = cursor.read_string().expect_err("Expected err"); - assert_eq!(err.to_string(), "Invalid string size, got 0 at position 4"); + let string = cursor.read_string().expect_err("Expected err").to_string(); + assert_eq!(string, "Invalid string size 0 at position 0x4"); // Missing null terminator let mut cursor = Cursor::new(vec![1u8, 0u8, 0u8, 0u8, b't']); - let err = cursor.read_string().expect_err("Expected err"); - assert_eq!( - err.to_string(), - "Invalid string terminator 116 at position 5" - ); + let string = cursor.read_string().expect_err("Expected err").to_string(); + assert_eq!(string, "Invalid string terminator 116 at position 5"); // Missing null terminator, UTF-16 let mut cursor = Cursor::new(vec![0xffu8, 0xffu8, 0xffu8, 0xffu8, b't', b'e']); - let err = cursor.read_string().expect_err("Expected err"); - assert_eq!( - err.to_string(), - "Invalid string terminator 25972 at position 6" - ); + let string = cursor.read_string().expect_err("Expected err").to_string(); + assert_eq!(string, "Invalid string terminator 25972 at position 6"); Ok(()) }