Download crates/mb3-dat/src/tests.rs from nmarshall/nanobrush: direct link, hf CLI and curl.
- Browser
- Download file 20.8 kB
-
https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/mb3-dat/src/tests.rs
- Command line
-
hf download hf://nmarshall/nanobrush/crates/mb3-dat/src/tests.rs
-
curl -L -o tests.rs https://huggingface.co/nmarshall/nanobrush/resolve/main/crates/mb3-dat/src/tests.rs
20.8 kB
| use super::*; | |
| use std::io::{self, Cursor, Read, Write}; | |
| const VERSIONS: [Version; 2] = [Version::V201501280, Version::V201512200]; | |
| fn empty(version: Version) -> Document { | |
| Document { | |
| version, | |
| world: World::default(), | |
| } | |
| } | |
| fn rich(version: Version) -> Document { | |
| let plane = Plane { | |
| p0: [1.0, -0.0, 3.0], | |
| u: [4.0, 5.0, 6.0], | |
| v: [7.0, 8.0, 9.0], | |
| polygon: Some(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), | |
| ..Plane::default() | |
| }; | |
| let mut d = Document { | |
| version, | |
| world: World { | |
| computed: true, | |
| brushes: vec![Brush { | |
| planes: vec![plane], | |
| }], | |
| patches: vec![Patch9 { | |
| anchors_u: 3, | |
| anchors_v: 3, | |
| quads_u: 2, | |
| quads_v: 2, | |
| anchors: vec![[1.0, 2.0, 3.0]; 9], | |
| computed: Some(PatchComputed { | |
| points: vec![[4.0, 5.0, 6.0]; 9], | |
| normals: vec![[0.0, 1.0, 0.0]; 9], | |
| }), | |
| }], | |
| ..World::default() | |
| }, | |
| }; | |
| if version == Version::V201512200 { | |
| d.world.string_properties = vec!["unused".into(), "name".into()]; | |
| d.world.uv_properties = vec!["uv".into()]; | |
| d.world.brushes[0].planes[0].strings = vec![Property { | |
| index: 1, | |
| value: String::new(), | |
| }]; | |
| d.world.brushes[0].planes[0].uv = vec![Property { | |
| index: 0, | |
| value: Uv { | |
| u: [1.0, 2.0, 3.0], | |
| v: [6.0, 7.0, 8.0], | |
| u_scale: 4.0, | |
| u_shift: 5.0, | |
| v_scale: 9.0, | |
| v_shift: 10.0, | |
| }, | |
| }]; | |
| } | |
| d | |
| } | |
| fn exact_empty_headers() { | |
| for version in VERSIONS { | |
| let mut expected = b"Mb3World".to_vec(); | |
| expected.extend_from_slice(&(version as u32).to_le_bytes()); | |
| expected.extend_from_slice(&[0; 9]); | |
| if version == Version::V201512200 { | |
| expected.extend_from_slice(&[0; 2]); | |
| } | |
| let d = empty(version); | |
| assert_eq!(encode(&d, &Limits::default()).unwrap(), expected); | |
| assert_eq!(decode(&expected, &Limits::default()).unwrap(), d); | |
| } | |
| } | |
| fn rich_semantic_and_bit_exact_roundtrips() { | |
| for version in VERSIONS { | |
| let d = rich(version); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| let decoded = decode(&data, &Limits::default()).unwrap(); | |
| assert_eq!(decoded, d); | |
| assert_eq!( | |
| decoded.world.brushes[0].planes[0].p0[1].to_bits(), | |
| (-0.0f32).to_bits() | |
| ); | |
| assert_eq!(encode(&decoded, &Limits::default()).unwrap(), data); | |
| } | |
| } | |
| fn rejects_every_prefix_truncation() { | |
| for version in VERSIONS { | |
| let data = encode(&rich(version), &Limits::default()).unwrap(); | |
| for n in 0..data.len() { | |
| assert!( | |
| decode(&data[..n], &Limits::default()).is_err(), | |
| "version {version:?}, prefix {n}" | |
| ); | |
| } | |
| } | |
| } | |
| fn rejects_header_flags_versions_trailing_and_reserved_widths() { | |
| for version in VERSIONS { | |
| let data = encode(&empty(version), &Limits::default()).unwrap(); | |
| for (at, value) in [(0, b'x'), (8, 0), (12, 0xe0), (12, 6)] { | |
| let mut bad = data.clone(); | |
| bad[at] = value; | |
| assert!(decode(&bad, &Limits::default()).is_err()); | |
| } | |
| let mut extra = data; | |
| extra.push(0); | |
| assert!(matches!( | |
| decode(&extra, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::TrailingData | |
| )); | |
| } | |
| for flags in [3, 12, 0xf0] { | |
| let mut data = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); | |
| data[15..19].copy_from_slice(&1i32.to_le_bytes()); | |
| data.splice(19..19, [flags, 0]); | |
| assert!(decode(&data, &Limits::default()).is_err()); | |
| } | |
| } | |
| fn counts_rejected_before_allocation() { | |
| for count in [-1i32, i32::MAX] { | |
| let mut data = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); | |
| data[15..19].copy_from_slice(&count.to_le_bytes()); | |
| assert!(decode(&data, &Limits::default()).is_err()); | |
| } | |
| let mut old = encode(&empty(Version::V201501280), &Limits::default()).unwrap(); | |
| old[13..17].copy_from_slice(&1i32.to_le_bytes()); | |
| old.extend_from_slice(&[1, 0, 128]); | |
| assert!(matches!( | |
| decode(&old, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::NegativeCount | |
| )); | |
| } | |
| fn plane_width_boundaries_and_legacy_repair() { | |
| for version in VERSIONS { | |
| for n in [255, 256, 32767, 32768, 65535, 65536] { | |
| let mut d = empty(version); | |
| d.world.brushes.push(Brush { | |
| planes: vec![Plane::default(); n], | |
| }); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| let flag_at = if version == Version::V201501280 { | |
| 21 | |
| } else { | |
| 19 | |
| }; | |
| let expected = if n <= 255 { | |
| 0 | |
| } else if n | |
| <= if version == Version::V201501280 { | |
| 32767 | |
| } else { | |
| 65535 | |
| } | |
| { | |
| 1 | |
| } else { | |
| 2 | |
| }; | |
| assert_eq!(data[flag_at], expected); | |
| assert_eq!(decode(&data, &Limits::default()).unwrap(), d); | |
| } | |
| } | |
| } | |
| fn string_and_uv_table_width_boundaries_preserve_unused_names() { | |
| for n in [255, 256, 65535, 65536] { | |
| let mut d = empty(Version::V201512200); | |
| d.world.string_properties = (0..n).map(|i| format!("s{i}")).collect(); | |
| d.world.uv_properties = (0..n).map(|i| format!("u{i}")).collect(); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| let width = if n <= 255 { | |
| 0 | |
| } else if n <= 65535 { | |
| 1 | |
| } else { | |
| 2 | |
| }; | |
| assert_eq!(data[12], width << 1 | width << 3); | |
| assert_eq!(decode(&data, &Limits::default()).unwrap(), d); | |
| } | |
| } | |
| fn sparse_index_and_sentinel_width_boundaries() { | |
| for n in [255, 256, 65535, 65536] { | |
| let mut d = empty(Version::V201512200); | |
| d.world.string_properties = (0..n).map(|i| format!("p{i}")).collect(); | |
| d.world.uv_properties = d.world.string_properties.clone(); | |
| d.world.brushes.push(Brush { | |
| planes: vec![Plane { | |
| strings: vec![Property { | |
| index: n - 1, | |
| value: String::new(), | |
| }], | |
| uv: vec![Property { | |
| index: n - 1, | |
| value: Uv { | |
| u: [1.0; 3], | |
| v: [2.0; 3], | |
| u_scale: 3.0, | |
| u_shift: 4.0, | |
| v_scale: 5.0, | |
| v_shift: 6.0, | |
| }, | |
| }], | |
| ..Plane::default() | |
| }], | |
| }); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| assert_eq!(decode(&data, &Limits::default()).unwrap(), d); | |
| } | |
| } | |
| fn shared_contour_width_and_legacy_repair() { | |
| for version in VERSIONS { | |
| for n in [256, 32768, 65536] { | |
| let mut d = empty(version); | |
| d.world.computed = true; | |
| d.world.brushes.push(Brush { | |
| planes: vec![ | |
| Plane { | |
| polygon: Some(vec![[1.0, 2.0, 3.0]; n]), | |
| ..Plane::default() | |
| }, | |
| Plane { | |
| polygon: Some(vec![[4.0, 5.0, 6.0]]), | |
| ..Plane::default() | |
| }, | |
| ], | |
| }); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| assert_eq!(decode(&data, &Limits::default()).unwrap(), d); | |
| } | |
| } | |
| } | |
| fn patch_dimensions_and_tessellation_widths() { | |
| for version in VERSIONS { | |
| for n in [255, 256, 32768, 65536] { | |
| let mut d = empty(version); | |
| d.world.patches.push(Patch9 { | |
| anchors_u: n, | |
| anchors_v: 0, | |
| quads_u: n, | |
| quads_v: 2, | |
| ..Patch9::default() | |
| }); | |
| assert_eq!( | |
| decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), | |
| d | |
| ); | |
| } | |
| } | |
| } | |
| fn strict_strings_on_read_and_write() { | |
| for text in ["é", "a\0b"] { | |
| let mut d = empty(Version::V201512200); | |
| d.world.string_properties.push(text.into()); | |
| assert!(encode(&d, &Limits::default()).is_err()); | |
| } | |
| for tail in [vec![1, b'a'], vec![1, 0xff, 0]] { | |
| let mut data = b"Mb3World".to_vec(); | |
| data.extend_from_slice(&201512200i32.to_le_bytes()); | |
| data.push(0); | |
| data.extend(tail); | |
| assert!(decode(&data, &Limits::default()).is_err()); | |
| } | |
| let mut d = empty(Version::V201512200); | |
| d.world.string_properties = vec!["same".into(), "same".into()]; | |
| assert_eq!( | |
| decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), | |
| d | |
| ); | |
| } | |
| fn sparse_wire(entries: &[u8]) -> Vec<u8> { | |
| let mut data = b"Mb3World".to_vec(); | |
| data.extend_from_slice(&201512200i32.to_le_bytes()); | |
| data.extend_from_slice(b"\0\x02a\0b\0\0"); | |
| data.extend_from_slice(&1i32.to_le_bytes()); | |
| data.extend_from_slice(&[0, 1]); | |
| data.extend_from_slice(entries); | |
| data.push(255); | |
| data.extend_from_slice(&[0; 36]); | |
| data.extend_from_slice(&0i32.to_le_bytes()); | |
| data | |
| } | |
| fn binary_properties_sorted_but_duplicates_and_bad_indices_rejected() { | |
| let d = decode(&sparse_wire(b"\x01B\0\0A\0"), &Limits::default()).unwrap(); | |
| assert_eq!( | |
| d.world.brushes[0].planes[0].strings, | |
| vec![ | |
| Property { | |
| index: 0, | |
| value: "A".into() | |
| }, | |
| Property { | |
| index: 1, | |
| value: "B".into() | |
| } | |
| ] | |
| ); | |
| for entries in [b"\0a\0\0b\0".as_slice(), b"\x02a\0"] { | |
| assert!(decode(&sparse_wire(entries), &Limits::default()).is_err()); | |
| } | |
| let mut d = d; | |
| d.world.brushes[0].planes[0].strings.reverse(); | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::UnsortedProperties | |
| )); | |
| d.world.brushes[0].planes[0].strings = vec![ | |
| Property { | |
| index: 0, | |
| value: "a".into() | |
| }; | |
| 2 | |
| ]; | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::DuplicateProperty(0) | |
| )); | |
| d.world.brushes[0].planes[0].strings[0].index = 9; | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::PropertyIndex(9) | |
| )); | |
| } | |
| fn legacy_downgrade_refuses_properties() { | |
| let mut d = rich(Version::V201512200); | |
| d.version = Version::V201501280; | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::LegacyProperties | |
| )); | |
| d.world.string_properties.clear(); | |
| d.world.uv_properties.clear(); | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::LegacyProperties | |
| )); | |
| } | |
| fn refuses_inconsistent_computed_arrays() { | |
| for version in VERSIONS { | |
| for mode in 0..5 { | |
| let mut d = rich(version); | |
| match mode { | |
| 0 => d.world.computed = false, | |
| 1 => d.world.brushes[0].planes[0].polygon = None, | |
| 2 => d.world.patches[0].computed = None, | |
| 3 => { | |
| d.world.patches[0].anchors.pop(); | |
| } | |
| _ => { | |
| d.world.patches[0].computed.as_mut().unwrap().normals.pop(); | |
| } | |
| } | |
| assert!(encode(&d, &Limits::default()).is_err()); | |
| } | |
| } | |
| } | |
| fn overflow_and_impossible_dimensions_are_errors() { | |
| for version in VERSIONS { | |
| let mut d = empty(version); | |
| d.world.computed = true; | |
| d.world.patches.push(Patch9 { | |
| anchors_u: 7, | |
| anchors_v: 7, | |
| quads_u: i32::MAX as u32, | |
| quads_v: i32::MAX as u32, | |
| anchors: vec![[0.0; 3]; 49], | |
| computed: Some(PatchComputed::default()), | |
| }); | |
| assert!(encode(&d, &Limits::default()).is_err()); | |
| d.world.computed = false; | |
| d.world.patches[0].computed = None; | |
| d.world.patches[0].quads_u = u32::MAX; | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::CountOverflow | |
| )); | |
| } | |
| } | |
| fn nonfinite_and_finite_extremes() { | |
| for f in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] { | |
| let mut d = rich(Version::V201512200); | |
| d.world.brushes[0].planes[0].p0[0] = f; | |
| assert!(matches!( | |
| encode(&d, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::NonFinite | |
| )); | |
| let mut data = sparse_wire(&[]); | |
| // The final 40 bytes are the plane vectors and the patch count. | |
| let pos = data.len() - 40; | |
| data[pos..pos + 4].copy_from_slice(&f.to_le_bytes()); | |
| assert!(matches!( | |
| decode(&data, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::NonFinite | |
| )); | |
| } | |
| let mut d = rich(Version::V201501280); | |
| d.world.brushes[0].planes[0].p0 = [f32::MAX, f32::MIN_POSITIVE, f32::from_bits(1)]; | |
| assert_eq!( | |
| decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), | |
| d | |
| ); | |
| } | |
| fn each_resource_limit_is_enforced() { | |
| let d = rich(Version::V201512200); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| for limits in [ | |
| Limits { | |
| max_bytes: data.len() - 1, | |
| ..Limits::default() | |
| }, | |
| Limits { | |
| max_items: 1, | |
| ..Limits::default() | |
| }, | |
| Limits { | |
| max_string_bytes: 1, | |
| ..Limits::default() | |
| }, | |
| ] { | |
| assert!(decode(&data, &limits).is_err()); | |
| assert!(encode(&d, &limits).is_err()); | |
| } | |
| assert!( | |
| decode( | |
| &data, | |
| &Limits { | |
| max_owned_bytes: 1, | |
| ..Limits::default() | |
| } | |
| ) | |
| .is_err() | |
| ); | |
| let mut input = Cursor::new(&data); | |
| assert!( | |
| read( | |
| &mut input, | |
| &Limits { | |
| max_bytes: 20, | |
| ..Limits::default() | |
| } | |
| ) | |
| .is_err() | |
| ); | |
| assert_eq!(input.position(), 21); | |
| } | |
| fn exact_string_and_item_limits() { | |
| let mut d = empty(Version::V201512200); | |
| d.world.string_properties.push("abcd".into()); | |
| let limits = Limits { | |
| max_items: 1, | |
| max_string_bytes: 4, | |
| ..Limits::default() | |
| }; | |
| let data = encode(&d, &limits).unwrap(); | |
| assert_eq!(decode(&data, &limits).unwrap(), d); | |
| assert!( | |
| decode( | |
| &data, | |
| &Limits { | |
| max_string_bytes: 3, | |
| ..limits | |
| } | |
| ) | |
| .is_err() | |
| ); | |
| } | |
| struct Choppy { | |
| bytes: Cursor<Vec<u8>>, | |
| interrupt: bool, | |
| } | |
| impl Read for Choppy { | |
| fn read(&mut self, b: &mut [u8]) -> io::Result<usize> { | |
| if self.interrupt { | |
| self.interrupt = false; | |
| return Err(io::ErrorKind::Interrupted.into()); | |
| } | |
| let n = b.len().min(1); | |
| self.bytes.read(&mut b[..n]) | |
| } | |
| } | |
| impl Write for Choppy { | |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { | |
| if self.interrupt { | |
| self.interrupt = false; | |
| return Err(io::ErrorKind::Interrupted.into()); | |
| } | |
| self.bytes.write(&b[..b.len().min(1)]) | |
| } | |
| fn flush(&mut self) -> io::Result<()> { | |
| Ok(()) | |
| } | |
| } | |
| fn streaming_short_reads_writes_and_interruptions() { | |
| let d = rich(Version::V201512200); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| assert_eq!( | |
| read( | |
| Choppy { | |
| bytes: Cursor::new(data.clone()), | |
| interrupt: true | |
| }, | |
| &Limits::default() | |
| ) | |
| .unwrap(), | |
| d | |
| ); | |
| let mut target = Choppy { | |
| bytes: Cursor::new(Vec::new()), | |
| interrupt: true, | |
| }; | |
| write(&mut target, &d, &Limits::default()).unwrap(); | |
| assert_eq!(target.bytes.into_inner(), data); | |
| } | |
| fn invalid_models_do_not_touch_writer() { | |
| let mut d = rich(Version::V201501280); | |
| d.world.patches[0].anchors.clear(); | |
| let mut target = vec![1, 2, 3]; | |
| assert!(write(&mut target, &d, &Limits::default()).is_err()); | |
| assert_eq!(target, [1, 2, 3]); | |
| } | |
| struct Broken { | |
| written: usize, | |
| } | |
| impl Write for Broken { | |
| fn write(&mut self, b: &[u8]) -> io::Result<usize> { | |
| if self.written >= 7 { | |
| return Err(io::ErrorKind::BrokenPipe.into()); | |
| } | |
| let n = b.len().min(7 - self.written); | |
| self.written += n; | |
| Ok(n) | |
| } | |
| fn flush(&mut self) -> io::Result<()> { | |
| Ok(()) | |
| } | |
| } | |
| impl Read for Broken { | |
| fn read(&mut self, _: &mut [u8]) -> io::Result<usize> { | |
| Err(io::ErrorKind::BrokenPipe.into()) | |
| } | |
| } | |
| fn io_errors_and_offsets_propagate() { | |
| let e = write( | |
| Broken { written: 0 }, | |
| &empty(Version::V201501280), | |
| &Limits::default(), | |
| ) | |
| .unwrap_err(); | |
| assert_eq!(e.offset, 7); | |
| assert!(matches!(e.kind, ErrorKind::Io(_))); | |
| assert!(matches!( | |
| read(Broken { written: 0 }, &Limits::default()) | |
| .unwrap_err() | |
| .kind, | |
| ErrorKind::Io(_) | |
| )); | |
| assert!(matches!( | |
| write( | |
| [0u8; 0].as_mut_slice(), | |
| &empty(Version::V201501280), | |
| &Limits::default() | |
| ) | |
| .unwrap_err() | |
| .kind, | |
| ErrorKind::Io(_) | |
| )); | |
| } | |
| fn deterministic_mutation_smoke_no_panics_or_unbounded_allocations() { | |
| let limits = Limits { | |
| max_bytes: 2048, | |
| max_string_bytes: 64, | |
| max_items: 256, | |
| max_owned_bytes: 32768, | |
| }; | |
| let seeds: Vec<_> = VERSIONS | |
| .iter() | |
| .map(|v| encode(&rich(*v), &limits).unwrap()) | |
| .collect(); | |
| let mut state = 0x37ab12cdu32; | |
| for i in 0..5000 { | |
| let mut data = seeds[i % seeds.len()].clone(); | |
| for _ in 0..1 + i % 8 { | |
| state = state.wrapping_mul(1664525).wrapping_add(1013904223); | |
| let at = state as usize % data.len(); | |
| data[at] ^= (state >> 24) as u8; | |
| } | |
| if let Ok(d) = decode(&data, &limits) { | |
| let canonical = encode(&d, &limits).unwrap(); | |
| assert_eq!(decode(&canonical, &limits).unwrap(), d); | |
| assert_eq!( | |
| encode(&decode(&canonical, &limits).unwrap(), &limits).unwrap(), | |
| canonical | |
| ); | |
| } | |
| } | |
| } | |
| fn valid_nonminimal_widths_are_normalized_and_patch_flags_are_strict() { | |
| let canonical = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); | |
| let mut wide = canonical.clone(); | |
| wide[12] = 2; // S uses u16 rather than u8, while its count is zero. | |
| wide.insert(13, 0); | |
| let d = decode(&wide, &Limits::default()).unwrap(); | |
| assert_eq!(encode(&d, &Limits::default()).unwrap(), canonical); | |
| for version in VERSIONS { | |
| let mut d = empty(version); | |
| d.world.patches.push(Patch9::default()); | |
| let data = encode(&d, &Limits::default()).unwrap(); | |
| let flag_at = if version == Version::V201501280 { | |
| 21 | |
| } else { | |
| 23 | |
| }; | |
| for flags in [3, 12, 0x80] { | |
| let mut bad = data.clone(); | |
| bad[flag_at] = flags; | |
| assert!(decode(&bad, &Limits::default()).is_err()); | |
| } | |
| } | |
| } | |
| fn reader_rejects_computed_grid_arithmetic_overflow() { | |
| for version in VERSIONS { | |
| let mut data = b"Mb3World".to_vec(); | |
| data.extend_from_slice(&(version as u32).to_le_bytes()); | |
| data.push(1); | |
| if version == Version::V201512200 { | |
| data.extend_from_slice(&[0, 0]); | |
| } | |
| data.extend_from_slice(&0i32.to_le_bytes()); | |
| data.extend_from_slice(&1i32.to_le_bytes()); | |
| data.extend_from_slice(&[8, 7, 7]); | |
| data.extend_from_slice(&i32::MAX.to_le_bytes()); | |
| data.extend_from_slice(&i32::MAX.to_le_bytes()); | |
| data.extend_from_slice(&[0; 49 * 12]); | |
| assert!(matches!( | |
| decode(&data, &Limits::default()).unwrap_err().kind, | |
| ErrorKind::CountOverflow | |
| )); | |
| } | |
| } | |