diff --git a/derive/Cargo.toml b/derive/Cargo.toml index 1005bcc..305e921 100644 --- a/derive/Cargo.toml +++ b/derive/Cargo.toml @@ -14,3 +14,6 @@ proc-macro-crate = "3.3.0" proc-macro2 = "1.0" quote = "1.0" syn = "2.0" + +[dev-dependencies] +proc-macro2 = { version = "1.0", features = ["span-locations"] } diff --git a/derive/src/funcs_for_expand.rs b/derive/src/funcs_for_expand.rs index 6afed9b..02fc97a 100644 --- a/derive/src/funcs_for_expand.rs +++ b/derive/src/funcs_for_expand.rs @@ -3,7 +3,10 @@ use std::collections::BTreeMap; use proc_macro_crate::{FoundCrate, crate_name}; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::quote; -use syn::{Data, DeriveInput, Error, Expr, Fields, Lit, LitInt, Result}; +use syn::{ + Data, DeriveInput, Error, Expr, Fields, Lit, LitInt, Meta, Result, Token, + punctuated::Punctuated, +}; pub(super) fn expand(input: DeriveInput) -> Result { require_repr_u8(&input)?; @@ -95,10 +98,11 @@ fn require_repr_u8(input: &DeriveInput) -> Result<()> { let mut found = false; for attribute in &input.attrs { if attribute.path().is_ident("repr") { - attribute.parse_nested_meta(|meta| { - found |= meta.path.is_ident("u8"); - Ok(()) - })?; + let repr = + attribute.parse_args_with(Punctuated::::parse_terminated)?; + found |= repr + .iter() + .any(|meta| matches!(meta, Meta::Path(path) if path.is_ident("u8"))); } } if found { diff --git a/derive/src/funcs_for_expand/tests_for_expand.rs b/derive/src/funcs_for_expand/tests_for_expand.rs index b8e08be..045b4da 100644 --- a/derive/src/funcs_for_expand/tests_for_expand.rs +++ b/derive/src/funcs_for_expand/tests_for_expand.rs @@ -1,11 +1,362 @@ use super::*; +use proc_macro2::{Delimiter, Spacing, TokenTree}; use quote::quote; +use std::{ + fs, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; use syn::parse2; fn error(input: TokenStream2) -> String { expand(parse2(input).unwrap()).unwrap_err().to_string() } +fn repr_error(input: TokenStream2) -> String { + require_repr_u8(&parse2(input).unwrap()) + .unwrap_err() + .to_string() +} + +fn parsed_bits(input: TokenStream2) -> u8 { + parse_bits(&parse2(input).unwrap()).unwrap().0 +} + +fn bits_error(input: TokenStream2) -> String { + parse_bits(&parse2(input).unwrap()).unwrap_err().to_string() +} + +#[derive(Debug, PartialEq, Eq)] +enum TokenShape { + Group(u8, Vec), + Ident(String), + Literal(String), + Punct(char, bool), +} + +fn token_shapes(tokens: TokenStream2) -> Vec { + tokens + .into_iter() + .map(|token| match token { + TokenTree::Group(group) => TokenShape::Group( + match group.delimiter() { + Delimiter::Parenthesis => 0, + Delimiter::Brace => 1, + Delimiter::Bracket => 2, + Delimiter::None => 3, + }, + token_shapes(group.stream()), + ), + TokenTree::Ident(ident) => TokenShape::Ident(ident.to_string()), + TokenTree::Literal(literal) => TokenShape::Literal(literal.to_string()), + TokenTree::Punct(punct) => { + TokenShape::Punct(punct.as_char(), punct.spacing() == Spacing::Joint) + } + }) + .collect() +} + +struct FixtureGuard(std::path::PathBuf); + +impl Drop for FixtureGuard { + fn drop(&mut self) { + let _ = fs::remove_dir_all(&self.0); + } +} + +#[test] +fn parses_bits_literals_and_defers_range_validation() { + for (input, expected) in [ + (quote! { #[packed(bits = 0)] enum Letter {} }, 0), + (quote! { #[packed(bits = 1)] enum Letter {} }, 1), + (quote! { #[packed(bits = 8)] enum Letter {} }, 8), + (quote! { #[packed(bits = 9)] enum Letter {} }, 9), + (quote! { #[packed(bits = 0xff)] enum Letter {} }, 255), + (quote! { #[packed(bits = 0b1)] enum Letter {} }, 1), + (quote! { #[packed(bits = 0o10)] enum Letter {} }, 8), + (quote! { #[packed(bits = 8u8)] enum Letter {} }, 8), + ] { + assert_eq!(parsed_bits(input), expected); + } +} + +#[test] +fn rejects_missing_unknown_duplicate_and_invalid_bits_metadata() { + for input in [ + quote! { enum Letter {} }, + quote! { #[derive(Clone)] enum Letter {} }, + quote! { #[packed()] enum Letter {} }, + ] { + assert!(bits_error(input).contains("packed requires #[packed(bits = N)]")); + } + + for input in [ + quote! { #[packed(width = 2)] enum Letter {} }, + quote! { #[packed(bits = 2, width = 3)] enum Letter {} }, + ] { + assert!(bits_error(input).contains("expected `bits = N`")); + } + + for input in [ + quote! { #[packed(bits = 1, bits = 2)] enum Letter {} }, + quote! { #[packed(bits = 1)] #[packed(bits = 2)] enum Letter {} }, + ] { + assert!(bits_error(input).contains("duplicate packed bit width")); + } + + for input in [ + quote! { #[packed(bits = "2")] enum Letter {} }, + quote! { #[packed(bits = true)] enum Letter {} }, + quote! { #[packed(bits = 1 + 1)] enum Letter {} }, + quote! { #[packed(bits = -1)] enum Letter {} }, + quote! { #[packed(bits = 256)] enum Letter {} }, + quote! { #[packed(bits)] enum Letter {} }, + ] { + assert!(parse_bits(&parse2(input).unwrap()).is_err()); + } +} + +#[test] +fn returns_span_of_bits_literal() { + let input = syn::parse_str::("#[packed(bits = 17)]\nenum Letter {}").unwrap(); + let (_, span) = parse_bits(&input).unwrap(); + + let mut expected = None; + input.attrs[0] + .parse_nested_meta(|meta| { + if meta.path.is_ident("bits") { + expected = Some(meta.value()?.parse::()?.span()); + } + Ok(()) + }) + .unwrap(); + let expected = expected.unwrap(); + + assert_eq!(span.start(), expected.start()); + assert_eq!(span.end(), expected.end()); +} + +#[test] +fn accepts_top_level_u8_in_repr_metadata() { + for input in [ + quote! { + #[repr(u8)] + enum Letter { A = 0 } + }, + quote! { + #[repr(C, u8)] + enum Letter { A = 0 } + }, + quote! { + #[repr(u8, C)] + enum Letter { A = 0 } + }, + quote! { + #[repr(C)] + #[repr(u8)] + enum Letter { A = 0 } + }, + ] { + assert!(require_repr_u8(&parse2(input).unwrap()).is_ok()); + } +} + +#[test] +fn falls_back_to_conventional_bit_string_path_when_unresolved() { + assert_eq!( + token_shapes(bit_string_path()), + token_shapes(quote!(::bit_string)) + ); +} + +#[test] +fn resolves_renamed_bit_string_dependency_in_isolated_fixture() { + let fixture = std::env::temp_dir().join(format!( + "bit-string-derive-renamed-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let _cleanup = FixtureGuard(fixture.clone()); + fs::create_dir_all(fixture.join("src")).unwrap(); + + let bit_string = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .canonicalize() + .unwrap(); + fs::write( + fixture.join("Cargo.toml"), + format!( + "[package]\nname = \"renamed-bit-string-fixture\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[workspace]\n\n[dependencies]\nbit_string_alias = {{ package = \"bit-string\", path = {:?} }}\n", + bit_string, + ), + ) + .unwrap(); + fs::write( + fixture.join("src/main.rs"), + r#" +use bit_string_alias::{packed, traits::PackedChar}; + +#[packed(bits = 2)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Symbol { + Zero = 0, + Two = 2, +} + +fn main() { + assert_eq!(Symbol::Zero.code(), 0); + assert_eq!(Symbol::Two.code(), 2); + assert_eq!(Symbol::from_code(0), Some(Symbol::Zero)); + assert_eq!(Symbol::from_code(1), None); + assert_eq!(Symbol::from_code(2), Some(Symbol::Two)); +} +"#, + ) + .unwrap(); + + let lockfile = Command::new("cargo") + .args(["generate-lockfile", "--offline", "--manifest-path"]) + .arg(fixture.join("Cargo.toml")) + .output() + .unwrap(); + assert!( + lockfile.status.success(), + "failed to generate fixture lockfile:\n{}", + String::from_utf8_lossy(&lockfile.stderr) + ); + + let run = Command::new("cargo") + .args(["run", "--locked", "--offline", "--manifest-path"]) + .arg(fixture.join("Cargo.toml")) + .output() + .unwrap(); + assert!( + run.status.success(), + "renamed dependency fixture failed:\n{}", + String::from_utf8_lossy(&run.stderr) + ); +} + +#[test] +fn public_packed_rejects_existing_repr_without_u8() { + let fixture = std::env::temp_dir().join(format!( + "bit-string-derive-existing-repr-{}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() + )); + let _cleanup = FixtureGuard(fixture.clone()); + fs::create_dir_all(fixture.join("src")).unwrap(); + + let bit_string = std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("..") + .canonicalize() + .unwrap(); + fs::write( + fixture.join("Cargo.toml"), + format!( + "[package]\nname = \"existing-repr-fixture\"\nversion = \"0.0.0\"\nedition = \"2024\"\n\n[workspace]\n\n[dependencies]\nbit-string = {{ path = {:?} }}\n", + bit_string, + ), + ) + .unwrap(); + fs::write( + fixture.join("src/main.rs"), + r#" +use bit_string::packed; + +#[repr(C)] +#[packed(bits = 2)] +enum Symbol { + Zero = 0, +} + +fn main() {} +"#, + ) + .unwrap(); + + let lockfile = Command::new("cargo") + .args(["generate-lockfile", "--offline", "--manifest-path"]) + .arg(fixture.join("Cargo.toml")) + .output() + .unwrap(); + assert!( + lockfile.status.success(), + "failed to generate fixture lockfile:\n{}", + String::from_utf8_lossy(&lockfile.stderr) + ); + + let check = Command::new("cargo") + .args(["check", "--locked", "--offline", "--manifest-path"]) + .arg(fixture.join("Cargo.toml")) + .output() + .unwrap(); + assert!(!check.status.success()); + assert!( + String::from_utf8_lossy(&check.stderr).contains("packed requires #[repr(u8)]"), + "missing packed repr diagnostic:\n{}", + String::from_utf8_lossy(&check.stderr) + ); +} + +#[test] +fn rejects_repr_without_top_level_u8() { + for input in [ + quote! { enum Letter { A = 0 } }, + quote! { + #[repr(C)] + enum Letter { A = 0 } + }, + quote! { + #[repr(usize)] + enum Letter { A = 0 } + }, + quote! { + #[repr(transparent)] + enum Letter { A = 0 } + }, + quote! { + #[repr(align(8))] + enum Letter { A = 0 } + }, + ] { + assert!(require_repr_u8(&parse2(input).unwrap()).is_err()); + } + + for input in [ + quote! { + #[repr(align(8))] + enum Letter { A = 0 } + }, + quote! { + #[repr(C, align(8))] + enum Letter { A = 0 } + }, + quote! { + #[repr(align(u8))] + enum Letter { A = 0 } + }, + ] { + assert_eq!(repr_error(input), "packed requires #[repr(u8)]"); + } +} + +#[test] +fn propagates_malformed_repr_metadata() { + let input = parse2(quote! { + #[repr(C,, u8)] + enum Letter { A = 0 } + }) + .unwrap(); + let error = require_repr_u8(&input).unwrap_err().to_string(); + + assert_ne!(error, "packed requires #[repr(u8)]"); +} + #[test] fn accepts_strict_enum_encoding() { let result = expand( @@ -19,6 +370,128 @@ fn accepts_strict_enum_encoding() { assert!(result.is_ok()); } +#[test] +fn accepts_width_boundaries_and_rejects_first_out_of_range_code() { + assert!( + expand( + parse2(quote! { + #[repr(u8)] + #[packed(bits = 1)] + enum Binary { Zero = 0, One = 1 } + }) + .unwrap(), + ) + .is_ok() + ); + + assert!( + expand( + parse2(quote! { + #[repr(u8)] + #[packed(bits = 8)] + enum Byte { Zero = 0, Maximum = 255 } + }) + .unwrap(), + ) + .is_ok() + ); + + assert!( + error(quote! { + #[repr(u8)] + #[packed(bits = 8)] + enum Byte { TooLarge = 256 } + }) + .contains("does not fit in 8 bits") + ); +} + +#[test] +fn emits_code_wise_decoding_for_sparse_alphabet() { + let output = expand( + parse2(quote! { + #[repr(u8)] + #[packed(bits = 3)] + enum Sparse { High = 7, Low = 0, Middle = 3 } + }) + .unwrap(), + ) + .unwrap() + .to_string(); + + assert!(output.contains("PackedChar < 3 > for Sparse")); + for (code, variant) in [(0, "Low"), (3, "Middle"), (7, "High")] { + let code = syn::LitInt::new(&code.to_string(), proc_macro2::Span::call_site()); + let variant = syn::Ident::new(variant, proc_macro2::Span::call_site()); + let expected = quote!( + #code => ::core::option::Option::Some(Self::#variant) + ) + .to_string(); + assert!( + output.contains(&expected), + "missing `{expected}` in `{output}`" + ); + } + assert!(output.contains("e!(_ => ::core::option::Option::None).to_string())); +} + +#[test] +fn emits_empty_enum_decoding_fallback() { + let output = expand( + parse2(quote! { + #[repr(u8)] + #[packed(bits = 2)] + enum Empty {} + }) + .unwrap(), + ) + .unwrap() + .to_string(); + + assert!(output.contains("PackedChar < 2 > for Empty")); + assert!(output.contains("e!(_ => ::core::option::Option::None).to_string())); + assert!(!output.contains("Option :: Some")); +} + +#[test] +fn preserves_generics_and_where_clause() { + let output = expand( + parse2(quote! { + #[repr(u8)] + #[packed(bits = 1)] + enum Generic where T: Copy { Zero = 0, One = 1 } + }) + .unwrap(), + ) + .unwrap() + .to_string(); + + assert!(output.contains("impl < T >")); + assert!(output.contains("for Generic < T > where T : Copy")); +} + +#[test] +fn rejects_non_enum_inputs() { + for input in [ + quote! { + #[repr(u8)] + #[packed(bits = 1)] + struct NotAnEnum; + }, + quote! { + #[repr(u8)] + #[packed(bits = 1)] + union NotAnEnum { value: u8 } + }, + ] { + let message = error(input); + assert!( + message.contains("packed can only be applied to enums"), + "unexpected error: {message}" + ); + } +} + #[test] fn requires_explicit_discriminants() { assert!( diff --git a/src/funcs_for_packed.rs b/src/funcs_for_packed.rs index 886fe03..65464f9 100644 --- a/src/funcs_for_packed.rs +++ b/src/funcs_for_packed.rs @@ -15,3 +15,6 @@ pub(crate) const fn code_mask() -> u8 { _ => unreachable!(), } } + +#[cfg(test)] +mod tests_for_packed; diff --git a/src/funcs_for_packed/tests_for_packed.rs b/src/funcs_for_packed/tests_for_packed.rs new file mode 100644 index 0000000..18d1f98 --- /dev/null +++ b/src/funcs_for_packed/tests_for_packed.rs @@ -0,0 +1,79 @@ +use super::*; + +const VALID_LOWER: () = assert_valid_width::<1>(); +const VALID_UPPER: () = assert_valid_width::<8>(); +const MASK_LOWER: u8 = code_mask::<1>(); +const MASK_UPPER: u8 = code_mask::<8>(); + +#[test] +fn accepts_the_complete_valid_width_domain() { + let _ = VALID_LOWER; + let _ = VALID_UPPER; + + assert_valid_width::<2>(); + assert_valid_width::<3>(); + assert_valid_width::<7>(); +} + +#[test] +fn returns_the_low_bits_mask_for_each_valid_width() { + let masks = [ + code_mask::<1>(), + code_mask::<2>(), + code_mask::<3>(), + code_mask::<7>(), + code_mask::<8>(), + ]; + assert_eq!( + masks, + [0b0000_0001, 0b0000_0011, 0b0000_0111, 0b0111_1111, 0xff] + ); + assert_eq!(MASK_LOWER, masks[0]); + assert_eq!(MASK_UPPER, masks[4]); + + for (bits, mask) in [ + (1, masks[0]), + (2, masks[1]), + (3, masks[2]), + (7, masks[3]), + (8, masks[4]), + ] { + assert_eq!(mask.count_ones(), bits); + let expected = if bits == 8 { + u8::MAX + } else { + ((1u16 << bits) - 1) as u8 + }; + assert_eq!(mask & !expected, 0); + } +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn code_mask_rejects_zero_width() { + code_mask::<0>(); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn code_mask_rejects_width_above_the_domain() { + code_mask::<9>(); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn rejects_zero_width() { + assert_valid_width::<0>(); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn rejects_first_width_above_the_domain() { + assert_valid_width::<9>(); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn rejects_maximum_u8_width() { + assert_valid_width::<{ u8::MAX }>(); +} diff --git a/src/packed_str/impls_for_conversion.rs b/src/packed_str/impls_for_conversion.rs index 5e5fc38..56ce754 100644 --- a/src/packed_str/impls_for_conversion.rs +++ b/src/packed_str/impls_for_conversion.rs @@ -29,3 +29,6 @@ where PackedStr::from_valid_bit_str(self.bits().as_bit_str()) } } + +#[cfg(test)] +mod tests_for_conversion; diff --git a/src/packed_str/impls_for_conversion/tests_for_conversion.rs b/src/packed_str/impls_for_conversion/tests_for_conversion.rs new file mode 100644 index 0000000..63dfa86 --- /dev/null +++ b/src/packed_str/impls_for_conversion/tests_for_conversion.rs @@ -0,0 +1,66 @@ +use crate::{BitString, PackedStr, traits::PackedChar}; +use int_intervals::UsizeCO; + +#[derive(Clone, Copy, Eq, PartialEq)] +struct TestChar(u8); + +impl PackedChar for TestChar { + fn code(self) -> u8 { + self.0 + } + + fn from_code(code: u8) -> Option { + Some(Self(code)) + } +} + +fn bit_view(bits: &BitString, start: usize) -> crate::BitStr<'_> { + bits.as_bit_str() + .slice(UsizeCO::checked_from_start_len(start, N).unwrap()) +} + +#[test] +fn trusted_constructor_preserves_valid_aligned_descriptors() { + let bits = BitString::zeros(80); + let source = bit_view::<21>(&bits, 3); + let packed: PackedStr<'_, TestChar, 3> = PackedStr::from_valid_bit_str(source); + + assert_eq!(packed.bits.start(), 3); + assert_eq!(packed.bits.bit_len(), 21); + assert_eq!(packed.char_len(), 7); + assert!(packed.bits == source); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn trusted_constructor_rejects_zero_width() { + let bits = BitString::new(); + let source = bits.as_bit_str(); + let _: PackedStr<'_, TestChar, 0> = PackedStr::from_valid_bit_str(source); +} + +#[test] +#[should_panic(expected = "packed character width must be between 1 and 8")] +fn trusted_constructor_rejects_width_above_eight() { + let bits = BitString::new(); + let source = bits.as_bit_str(); + let _: PackedStr<'_, TestChar, 9> = PackedStr::from_valid_bit_str(source); +} + +#[cfg(debug_assertions)] +#[test] +#[should_panic] +fn trusted_constructor_rejects_misaligned_start_in_debug() { + let bits = BitString::zeros(16); + let source = bit_view::<3>(&bits, 1); + let _: PackedStr<'_, TestChar, 3> = PackedStr::from_valid_bit_str(source); +} + +#[cfg(debug_assertions)] +#[test] +#[should_panic] +fn trusted_constructor_rejects_non_divisible_length_in_debug() { + let bits = BitString::zeros(16); + let source = bit_view::<4>(&bits, 0); + let _: PackedStr<'_, TestChar, 3> = PackedStr::from_valid_bit_str(source); +} diff --git a/src/packed_string/impls_for_construction/tests_for_construction.rs b/src/packed_string/impls_for_construction/tests_for_construction.rs index cbe7a9d..d39fedb 100644 --- a/src/packed_string/impls_for_construction/tests_for_construction.rs +++ b/src/packed_string/impls_for_construction/tests_for_construction.rs @@ -1,3 +1,6 @@ +#[cfg(debug_assertions)] +use crate::BitString; +use crate::PackedString; use crate::packed_string::tests_for_support::{Letter, LetterString}; #[test] @@ -14,3 +17,21 @@ fn collect_constructs_a_packed_string() { assert_eq!(value.char_len(), 2); assert_eq!(value.get(0), Some(Letter::A)); } + +#[test] +fn trusted_constructor_adopts_aligned_payload_unchanged() { + let source = LetterString::from_chars([Letter::A, Letter::D, Letter::B, Letter::C]); + let source_bits = source.bits().clone(); + let adopted = PackedString::::from_valid_bits(source_bits.clone()); + + assert_eq!(adopted.bits().bit_len(), source_bits.bit_len()); + assert_eq!(adopted.bits().words(), source_bits.words()); + assert_eq!(adopted.to_vec(), source.to_vec()); +} + +#[cfg(debug_assertions)] +#[test] +#[should_panic] +fn trusted_constructor_rejects_misaligned_payload_in_debug() { + PackedString::::from_valid_bits(BitString::from_iter([true])); +} diff --git a/tests/adversarial.rs b/tests/bit.rs similarity index 69% rename from tests/adversarial.rs rename to tests/bit.rs index 78c454a..1aa72af 100644 --- a/tests/adversarial.rs +++ b/tests/bit.rs @@ -51,41 +51,41 @@ pub(crate) fn cat(parts: &[&str]) -> String { // Module declarations // --------------------------------------------------------------------------- -#[path = "adversarial/tests_for_access.rs"] +#[path = "bit/adversarial/tests_for_access.rs"] mod tests_for_access; -#[path = "adversarial/tests_for_bitstr.rs"] +#[path = "bit/adversarial/tests_for_bitstr.rs"] mod tests_for_bitstr; -#[path = "adversarial/tests_for_bitwise.rs"] +#[path = "bit/adversarial/tests_for_bitwise.rs"] mod tests_for_bitwise; -#[path = "adversarial/tests_for_bugs.rs"] +#[path = "bit/adversarial/tests_for_bugs.rs"] mod tests_for_bugs; -#[path = "adversarial/tests_for_concat.rs"] +#[path = "bit/adversarial/tests_for_concat.rs"] mod tests_for_concat; -#[path = "adversarial/tests_for_construction.rs"] +#[path = "bit/adversarial/tests_for_construction.rs"] mod tests_for_construction; -#[path = "adversarial/tests_for_count.rs"] +#[path = "bit/adversarial/tests_for_count.rs"] mod tests_for_count; -#[path = "adversarial/tests_for_editing.rs"] +#[path = "bit/adversarial/tests_for_editing.rs"] mod tests_for_editing; -#[path = "adversarial/tests_for_fmt.rs"] +#[path = "bit/adversarial/tests_for_fmt.rs"] mod tests_for_fmt; -#[path = "adversarial/tests_for_iter.rs"] +#[path = "bit/adversarial/tests_for_iter.rs"] mod tests_for_iter; -#[path = "adversarial/tests_for_leading_trailing.rs"] +#[path = "bit/adversarial/tests_for_leading_trailing.rs"] mod tests_for_leading_trailing; -#[path = "adversarial/tests_for_matching.rs"] +#[path = "bit/adversarial/tests_for_matching.rs"] mod tests_for_matching; -#[path = "adversarial/tests_for_ord_hash.rs"] +#[path = "bit/adversarial/tests_for_ord_hash.rs"] mod tests_for_ord_hash; -#[path = "adversarial/tests_for_predicates.rs"] +#[path = "bit/adversarial/tests_for_predicates.rs"] mod tests_for_predicates; -#[path = "adversarial/tests_for_replace.rs"] +#[path = "bit/adversarial/tests_for_replace.rs"] mod tests_for_replace; -#[path = "adversarial/tests_for_retain.rs"] +#[path = "bit/adversarial/tests_for_retain.rs"] mod tests_for_retain; -#[path = "adversarial/tests_for_shift.rs"] +#[path = "bit/adversarial/tests_for_shift.rs"] mod tests_for_shift; -#[path = "adversarial/tests_for_slice_drain.rs"] +#[path = "bit/adversarial/tests_for_slice_drain.rs"] mod tests_for_slice_drain; -#[path = "adversarial/tests_for_stress.rs"] +#[path = "bit/adversarial/tests_for_stress.rs"] mod tests_for_stress; diff --git a/tests/adversarial/tests_for_access.rs b/tests/bit/adversarial/tests_for_access.rs similarity index 100% rename from tests/adversarial/tests_for_access.rs rename to tests/bit/adversarial/tests_for_access.rs diff --git a/tests/adversarial/tests_for_bitstr.rs b/tests/bit/adversarial/tests_for_bitstr.rs similarity index 100% rename from tests/adversarial/tests_for_bitstr.rs rename to tests/bit/adversarial/tests_for_bitstr.rs diff --git a/tests/adversarial/tests_for_bitwise.rs b/tests/bit/adversarial/tests_for_bitwise.rs similarity index 100% rename from tests/adversarial/tests_for_bitwise.rs rename to tests/bit/adversarial/tests_for_bitwise.rs diff --git a/tests/adversarial/tests_for_bugs.rs b/tests/bit/adversarial/tests_for_bugs.rs similarity index 100% rename from tests/adversarial/tests_for_bugs.rs rename to tests/bit/adversarial/tests_for_bugs.rs diff --git a/tests/adversarial/tests_for_concat.rs b/tests/bit/adversarial/tests_for_concat.rs similarity index 100% rename from tests/adversarial/tests_for_concat.rs rename to tests/bit/adversarial/tests_for_concat.rs diff --git a/tests/adversarial/tests_for_construction.rs b/tests/bit/adversarial/tests_for_construction.rs similarity index 100% rename from tests/adversarial/tests_for_construction.rs rename to tests/bit/adversarial/tests_for_construction.rs diff --git a/tests/adversarial/tests_for_count.rs b/tests/bit/adversarial/tests_for_count.rs similarity index 100% rename from tests/adversarial/tests_for_count.rs rename to tests/bit/adversarial/tests_for_count.rs diff --git a/tests/adversarial/tests_for_editing.rs b/tests/bit/adversarial/tests_for_editing.rs similarity index 100% rename from tests/adversarial/tests_for_editing.rs rename to tests/bit/adversarial/tests_for_editing.rs diff --git a/tests/adversarial/tests_for_fmt.rs b/tests/bit/adversarial/tests_for_fmt.rs similarity index 100% rename from tests/adversarial/tests_for_fmt.rs rename to tests/bit/adversarial/tests_for_fmt.rs diff --git a/tests/adversarial/tests_for_iter.rs b/tests/bit/adversarial/tests_for_iter.rs similarity index 100% rename from tests/adversarial/tests_for_iter.rs rename to tests/bit/adversarial/tests_for_iter.rs diff --git a/tests/adversarial/tests_for_leading_trailing.rs b/tests/bit/adversarial/tests_for_leading_trailing.rs similarity index 100% rename from tests/adversarial/tests_for_leading_trailing.rs rename to tests/bit/adversarial/tests_for_leading_trailing.rs diff --git a/tests/adversarial/tests_for_matching.rs b/tests/bit/adversarial/tests_for_matching.rs similarity index 100% rename from tests/adversarial/tests_for_matching.rs rename to tests/bit/adversarial/tests_for_matching.rs diff --git a/tests/adversarial/tests_for_ord_hash.rs b/tests/bit/adversarial/tests_for_ord_hash.rs similarity index 100% rename from tests/adversarial/tests_for_ord_hash.rs rename to tests/bit/adversarial/tests_for_ord_hash.rs diff --git a/tests/adversarial/tests_for_predicates.rs b/tests/bit/adversarial/tests_for_predicates.rs similarity index 100% rename from tests/adversarial/tests_for_predicates.rs rename to tests/bit/adversarial/tests_for_predicates.rs diff --git a/tests/adversarial/tests_for_replace.rs b/tests/bit/adversarial/tests_for_replace.rs similarity index 100% rename from tests/adversarial/tests_for_replace.rs rename to tests/bit/adversarial/tests_for_replace.rs diff --git a/tests/adversarial/tests_for_retain.rs b/tests/bit/adversarial/tests_for_retain.rs similarity index 100% rename from tests/adversarial/tests_for_retain.rs rename to tests/bit/adversarial/tests_for_retain.rs diff --git a/tests/adversarial/tests_for_shift.rs b/tests/bit/adversarial/tests_for_shift.rs similarity index 100% rename from tests/adversarial/tests_for_shift.rs rename to tests/bit/adversarial/tests_for_shift.rs diff --git a/tests/adversarial/tests_for_slice_drain.rs b/tests/bit/adversarial/tests_for_slice_drain.rs similarity index 100% rename from tests/adversarial/tests_for_slice_drain.rs rename to tests/bit/adversarial/tests_for_slice_drain.rs diff --git a/tests/adversarial/tests_for_stress.rs b/tests/bit/adversarial/tests_for_stress.rs similarity index 100% rename from tests/adversarial/tests_for_stress.rs rename to tests/bit/adversarial/tests_for_stress.rs diff --git a/tests/packed.rs b/tests/packed.rs new file mode 100644 index 0000000..0e654da --- /dev/null +++ b/tests/packed.rs @@ -0,0 +1,110 @@ +//! Packed-character tests grouped by API surface. + +use bit_string::{PackedString, packed, traits::PackedChar}; + +#[packed(bits = 1)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum PackedSymbol { + Zero = 0, + One = 1, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[packed(bits = 2)] +pub(crate) enum Symbol { + Zero = 0, + One = 1, + Two = 2, +} + +#[packed(bits = 3)] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) enum Oct { + V0 = 0, + V1 = 1, + V2 = 2, + V3 = 3, + V4 = 4, + V5 = 5, + V6 = 6, + V7 = 7, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[packed(bits = 8)] +pub(crate) enum SparseByte { + Maximum = 255, + Zero = 0, + Middle = 3, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub(crate) struct WideCode(pub(crate) u8); + +impl PackedChar<7> for WideCode { + fn code(self) -> u8 { + self.0 + } + + fn from_code(code: u8) -> Option { + (code < 128).then_some(Self(code)) + } +} + +pub(crate) fn symbol(code: u8) -> Symbol { + match code { + 0 => Symbol::Zero, + 1 => Symbol::One, + 2 => Symbol::Two, + _ => unreachable!(), + } +} + +pub(crate) fn oct(code: u8) -> Oct { + match code { + 0 => Oct::V0, + 1 => Oct::V1, + 2 => Oct::V2, + 3 => Oct::V3, + 4 => Oct::V4, + 5 => Oct::V5, + 6 => Oct::V6, + 7 => Oct::V7, + _ => unreachable!(), + } +} + +pub(crate) fn wide(code: u8) -> WideCode { + WideCode(code) +} + +pub(crate) fn packed(codes: &[u8]) -> PackedString { + PackedString::from_chars(codes.iter().copied().map(symbol)) +} + +pub(crate) fn packed_as( + codes: &[u8], + decode: fn(u8) -> C, +) -> PackedString +where + C: PackedChar, +{ + PackedString::from_chars(codes.iter().copied().map(decode)) +} + +#[path = "packed/tests_for_access.rs"] +mod tests_for_access; +#[path = "packed/tests_for_construction.rs"] +mod tests_for_construction; +#[path = "packed/tests_for_conversion.rs"] +mod tests_for_conversion; +#[path = "packed/tests_for_editing.rs"] +mod tests_for_editing; +#[path = "packed/tests_for_eq.rs"] +mod tests_for_eq; +#[path = "packed/tests_for_iter.rs"] +mod tests_for_iter; +#[path = "packed/tests_for_matching.rs"] +mod tests_for_matching; +#[path = "packed/tests_for_ord.rs"] +mod tests_for_ord; diff --git a/tests/packed/tests_for_access.rs b/tests/packed/tests_for_access.rs new file mode 100644 index 0000000..eb0766e --- /dev/null +++ b/tests/packed/tests_for_access.rs @@ -0,0 +1,49 @@ +use super::{ + Oct, PackedSymbol, SparseByte, Symbol, WideCode, oct, packed, packed_as, symbol, wide, +}; +use bit_string::{BitStr, PackedString, traits::PackedChar}; +use int_intervals::UsizeCO; + +fn assert_access_slice( + left: &[u8], + start: usize, + len: usize, + decode: fn(u8) -> C, +) where + C: PackedChar + core::fmt::Debug, +{ + let string = packed_as(left, decode); + assert_eq!(string.char_len(), left.len()); + let view = string.as_packed_str(); + for index in 0..=left.len() + 1 { + assert_eq!(string.get(index), left.get(index).copied().map(decode)); + assert_eq!(view.get(index), left.get(index).copied().map(decode)); + } + + let oracle_start = start.min(left.len()); + let oracle_end = start.saturating_add(len).min(left.len()).max(oracle_start); + let slice = string.slice(UsizeCO::checked_from_start_len(start, len).unwrap()); + assert_eq!( + slice.to_vec(), + left[oracle_start..oracle_end] + .iter() + .copied() + .map(decode) + .collect::>() + ); +} + +#[path = "tests_for_access/tests_for_view_metadata.rs"] +mod tests_for_view_metadata; + +#[path = "tests_for_access/tests_for_accessors.rs"] +mod tests_for_accessors; + +#[path = "tests_for_access/tests_for_slices.rs"] +mod tests_for_slices; + +#[path = "tests_for_access/tests_for_vec_access_semantics.rs"] +mod tests_for_vec_access_semantics; + +#[path = "tests_for_access/tests_for_cross_word_boundaries.rs"] +mod tests_for_cross_word_boundaries; diff --git a/tests/packed/tests_for_access/tests_for_accessors.rs b/tests/packed/tests_for_access/tests_for_accessors.rs new file mode 100644 index 0000000..7da9b4e --- /dev/null +++ b/tests/packed/tests_for_access/tests_for_accessors.rs @@ -0,0 +1,380 @@ +use super::*; + +#[test] +fn packed_str_get_decodes_bounds_offsets_and_cross_word_codes() { + let empty = PackedString::::new(); + assert_eq!(empty.as_packed_str().get(0), None); + assert_eq!(empty.as_packed_str().get(usize::MAX), None); + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!(binary.as_packed_str().get(0), Some(PackedSymbol::Zero)); + assert_eq!(binary.as_packed_str().get(1), Some(PackedSymbol::One)); + assert_eq!(binary.as_packed_str().get(2), None); + + let bytes = packed_as::(&[0, 3, 255], |code| match code { + 0 => SparseByte::Zero, + 3 => SparseByte::Middle, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + let byte_view = bytes.as_packed_str(); + assert_eq!(byte_view.get(0), Some(SparseByte::Zero)); + assert_eq!(byte_view.get(2), Some(SparseByte::Maximum)); + assert_eq!(byte_view.get(3), None); + assert_eq!(byte_view.get(usize::MAX), None); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_string = packed_as::(&oct_codes, oct); + let oct_view = oct_string.as_packed_str(); + for (index, &code) in oct_codes.iter().enumerate() { + assert_eq!(oct_view.get(index), Some(oct(code))); + } + assert_eq!(oct_view.get(oct_codes.len()), None); + let oct_slice = oct_view.slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + for (index, &code) in oct_codes[20..24].iter().enumerate() { + assert_eq!(oct_slice.get(index), Some(oct(code))); + } + assert_eq!(oct_slice.get(4), None); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_string = packed_as::(&wide_codes, wide); + let wide_view = wide_string.as_packed_str(); + for (index, &code) in wide_codes.iter().enumerate() { + assert_eq!(wide_view.get(index), Some(wide(code))); + } + assert_eq!(wide_view.get(wide_codes.len()), None); + let wide_slice = wide_view.slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + for (index, &code) in wide_codes[8..13].iter().enumerate() { + assert_eq!(wide_slice.get(index), Some(wide(code))); + } + assert_eq!(wide_slice.get(usize::MAX), None); +} + +#[test] +fn packed_string_get_isolates_codes_at_width_boundaries() { + let binary = packed_as::(&[0, 1, 0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary.to_vec(), + vec![ + PackedSymbol::Zero, + PackedSymbol::One, + PackedSymbol::Zero, + PackedSymbol::One, + ] + ); + assert_eq!(binary.get(4), None); + assert_eq!(binary.get(usize::MAX), None); + + let bytes = packed_as::(&[0, 255, 3, 255], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + for (index, code) in [0, 255, 3, 255].into_iter().enumerate() { + assert_eq!(bytes.get(index).map(PackedChar::code), Some(code)); + } + + let oct_codes: Vec<_> = (0..22) + .map(|index| if index == 21 { 1 } else { index as u8 % 8 }) + .collect(); + let oct_string = packed_as::(&oct_codes, oct); + for (index, &code) in oct_codes.iter().enumerate() { + assert_eq!(oct_string.get(index).map(PackedChar::code), Some(code)); + } + assert_eq!( + oct_string.get(20).map(PackedChar::code), + Some(oct_codes[20]) + ); + assert_eq!(oct_string.get(21).map(PackedChar::code), Some(1)); + + let wide_codes: Vec<_> = (0..10) + .map(|index| { + if index == 9 { + 1 + } else { + (index * 11) as u8 % 128 + } + }) + .collect(); + let wide_string = packed_as::(&wide_codes, wide); + for (index, &code) in wide_codes.iter().enumerate() { + assert_eq!(wide_string.get(index).map(PackedChar::code), Some(code)); + } + assert_eq!( + wide_string.get(8).map(PackedChar::code), + Some(wide_codes[8]) + ); + assert_eq!(wide_string.get(9).map(PackedChar::code), Some(1)); + assert_eq!(wide_string.get(10), None); +} + +#[test] +fn packed_string_first_is_index_zero_and_empty_safe() { + let new = PackedString::::new(); + let default = PackedString::::default(); + let from_empty = PackedString::::from_chars(core::iter::empty()); + for string in [&new, &default, &from_empty] { + assert_eq!(string.first(), None); + assert_eq!(string.first(), string.get(0)); + } + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!(binary.first(), Some(PackedSymbol::Zero)); + assert_eq!(binary.first(), binary.get(0)); + + let bytes = packed_as::(&[255, 0], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + assert_eq!(bytes.first(), Some(SparseByte::Maximum)); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let mut oct_string = packed_as::(&oct_codes, oct); + assert_eq!(oct_string.first(), Some(Oct::V0)); + assert_eq!(oct_string.first(), oct_string.to_vec().first().copied()); + + oct_string.insert(0, Oct::V7); + assert_eq!(oct_string.first(), Some(Oct::V7)); + assert_eq!(oct_string.remove(0), Oct::V7); + assert_eq!(oct_string.first(), Some(Oct::V0)); + assert_eq!(oct_string.set(0, Oct::V6), Some(Oct::V0)); + assert_eq!(oct_string.first(), Some(Oct::V6)); + oct_string.clear(); + assert_eq!(oct_string.first(), None); + oct_string.push(Oct::V3); + assert_eq!(oct_string.first(), Some(Oct::V3)); + + let wide_codes: Vec<_> = (0..10) + .map(|index| { + if index == 0 { + 127 + } else { + (index * 11) as u8 % 128 + } + }) + .collect(); + let wide_string = packed_as::(&wide_codes, wide); + assert_eq!(wide_string.first(), Some(WideCode(127))); + assert_eq!(wide_string.first(), wide_string.get(0)); +} + +#[test] +fn packed_string_get_matches_character_oracle_and_bounds() { + let codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let string = packed_as::(&codes, oct); + for (index, &code) in codes.iter().enumerate() { + assert_eq!(string.get(index), Some(oct(code))); + assert_eq!(string.get(index), string.as_packed_str().get(index)); + assert_eq!(string.get(index), string.as_packed_str().iter().nth(index)); + } + assert_eq!(string.get(codes.len()), None); + assert_eq!(string.get(codes.len() + 1), None); + assert_eq!(string.get(usize::MAX), None); + + let all_zero = packed_as::(&[0; 22], oct); + assert_eq!(all_zero.get(0), Some(Oct::V0)); + assert_eq!(all_zero.get(21), Some(Oct::V0)); + + let maximum = packed_as::(&[255, 0], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + assert_eq!(maximum.get(0), Some(SparseByte::Maximum)); + assert_eq!(maximum.get(1), Some(SparseByte::Zero)); + assert_eq!(maximum.get(2), None); +} + +#[test] +fn packed_string_last_matches_the_final_character_and_pop() { + let empty = PackedString::::default(); + assert_eq!(empty.last(), None); + assert_eq!(empty.last(), empty.as_packed_str().iter().next_back()); + + let mut oct_string = packed_as::(&[Oct::V7.code(); 22], oct); + assert_eq!(oct_string.last(), Some(Oct::V7)); + assert_eq!(oct_string.last(), oct_string.get(21)); + assert_eq!( + oct_string.last(), + oct_string.as_packed_str().iter().next_back() + ); + + assert_eq!(oct_string.set(21, Oct::V0), Some(Oct::V7)); + assert_eq!(oct_string.last(), Some(Oct::V0)); + assert_eq!(oct_string.pop(), Some(Oct::V0)); + assert_eq!(oct_string.last(), Some(Oct::V7)); + + while oct_string.pop().is_some() {} + assert_eq!(oct_string.char_len(), 0); + assert_eq!(oct_string.last(), None); + assert_eq!(oct_string.pop(), None); + + let wide_codes: Vec<_> = (0..10) + .map(|index| { + if index == 9 { + 127 + } else { + (index * 11) as u8 % 128 + } + }) + .collect(); + let wide_string = packed_as::(&wide_codes, wide); + assert_eq!(wide_string.last(), Some(WideCode(127))); + assert_eq!(wide_string.last(), wide_string.get(9)); + + let bytes = packed_as::(&[255, 0], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + assert_eq!(bytes.last(), Some(SparseByte::Zero)); +} + +#[test] +fn packed_str_first_is_view_relative_and_empty_safe() { + let empty = PackedString::::new(); + assert_eq!(empty.as_packed_str().first(), None); + + let binary = packed_as::(&[1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!(binary.as_packed_str().first(), Some(PackedSymbol::One)); + + let bytes = packed_as::(&[255, 0], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + let byte_view = bytes.as_packed_str(); + assert_eq!(byte_view.first(), Some(SparseByte::Maximum)); + assert_eq!(byte_view.slice_from(1).first(), Some(SparseByte::Zero)); + assert_eq!(byte_view.slice_from(2).first(), None); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_string = packed_as::(&oct_codes, oct); + let oct_view = oct_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(21, 2).unwrap()); + assert_eq!(oct_view.first(), Some(oct(5))); + assert_eq!(oct_view.first(), oct_view.clone().get(0)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_string = packed_as::(&wide_codes, wide); + let wide_view = wide_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(9, 2).unwrap()); + assert_eq!(wide_view.first(), Some(wide(wide_codes[9]))); +} + +#[test] +fn packed_str_last_is_view_relative_and_empty_safe() { + let empty = PackedString::::new(); + assert_eq!(empty.as_packed_str().last(), None); + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!(binary.as_packed_str().last(), Some(PackedSymbol::One)); + + let bytes = packed_as::(&[255, 3, 0], |code| match code { + 0 => SparseByte::Zero, + 3 => SparseByte::Middle, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + let byte_view = bytes.as_packed_str(); + assert_eq!(byte_view.last(), Some(SparseByte::Zero)); + assert_eq!(byte_view.slice_until(2).last(), Some(SparseByte::Middle)); + assert_eq!(byte_view.slice_until(0).last(), None); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_string = packed_as::(&oct_codes, oct); + let oct_view = oct_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 2).unwrap()); + assert_eq!(oct_view.last(), Some(oct(5))); + assert_eq!(oct_view.last(), oct_view.clone().get(1)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_string = packed_as::(&wide_codes, wide); + let wide_view = wide_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 2).unwrap()); + assert_eq!(wide_view.last(), Some(wide(wide_codes[9]))); + assert_eq!(wide_string.as_packed_str().slice_from(16).last(), None); +} + +#[test] +fn packed_string_is_empty_tracks_zero_packed_bits() { + let new = PackedString::::new(); + let default = PackedString::::default(); + let from_empty = PackedString::::from_chars(core::iter::empty()); + for value in [&new, &default, &from_empty] { + assert!(value.is_empty()); + assert_eq!(value.char_len(), 0); + assert_eq!(value.bits().bit_len(), 0); + } + + let one_zero = super::packed(&[0]); + assert!(!one_zero.is_empty()); + assert_eq!(one_zero.char_len(), 1); + assert_eq!(one_zero.bits().bit_len(), 2); + + let all_zero = super::packed(&[0; 64]); + assert!(!all_zero.is_empty()); + assert_eq!(all_zero.char_len(), 64); + assert_eq!(all_zero.bits().bit_len(), 128); + + let binary = packed_as::(&[0], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let oct = packed_as::(&[0; 22], super::oct); + let wide = packed_as::(&[0; 10], super::wide); + let bytes = packed_as::(&[0], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + assert!(!binary.is_empty()); + assert!(!oct.is_empty()); + assert!(!wide.is_empty()); + assert!(!bytes.is_empty()); + + let mut popped = super::packed(&[0]); + assert!(!popped.is_empty()); + assert_eq!(popped.pop(), Some(super::Symbol::Zero)); + assert!(popped.is_empty()); + + let mut cleared = super::packed(&[0, 1, 2]); + cleared.clear(); + assert!(cleared.is_empty()); + + let mut truncated = super::packed(&[0, 1, 2]); + truncated.truncate(0); + assert!(truncated.is_empty()); + + let mut drained = super::packed(&[0, 1, 2]); + drained.drain_interval_assign(UsizeCO::checked_from_start_len(0, 3).unwrap()); + assert!(drained.is_empty()); +} diff --git a/tests/packed/tests_for_access/tests_for_cross_word_boundaries.rs b/tests/packed/tests_for_access/tests_for_cross_word_boundaries.rs new file mode 100644 index 0000000..b1901cd --- /dev/null +++ b/tests/packed/tests_for_access/tests_for_cross_word_boundaries.rs @@ -0,0 +1,15 @@ +use super::*; +use proptest::prelude::*; + +proptest! { +#[test] + fn three_and_seven_bit_access_and_slice_cross_word_boundaries( + left3 in prop::collection::vec(0u8..=7, 22..=128), + left7 in prop::collection::vec(0u8..=127, 10..=128), + start in 0usize..160, + len in 1usize..80, + ) { + assert_access_slice::(&left3, start, len, oct); + assert_access_slice::(&left7, start, len, wide); + } +} diff --git a/tests/packed/tests_for_access/tests_for_slices.rs b/tests/packed/tests_for_access/tests_for_slices.rs new file mode 100644 index 0000000..c1471bb --- /dev/null +++ b/tests/packed/tests_for_access/tests_for_slices.rs @@ -0,0 +1,308 @@ +use super::*; + +#[test] +fn packed_str_slice_uses_clamped_character_ranges() { + let owner = super::packed(&[0, 1, 2, 1, 0]); + let view = owner.as_packed_str(); + let full = view.slice(UsizeCO::checked_from_start_len(0, 5).unwrap()); + assert_eq!(full.iter().collect::>(), owner.to_vec()); + + let middle = view.slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + assert_eq!( + middle.iter().collect::>(), + vec![super::Symbol::One, super::Symbol::Two, super::Symbol::One] + ); + let singleton = view.slice(UsizeCO::checked_from_start_len(4, 1).unwrap()); + assert_eq!( + singleton.iter().collect::>(), + vec![super::Symbol::Zero] + ); + let clamped_end = view.slice(UsizeCO::checked_from_start_len(3, 99).unwrap()); + assert_eq!( + clamped_end.iter().collect::>(), + vec![super::Symbol::One, super::Symbol::Zero] + ); + assert!( + view.slice(UsizeCO::checked_from_start_len(99, 1).unwrap()) + .is_empty() + ); + assert!( + view.slice(UsizeCO::checked_from_start_len(5, 1).unwrap()) + .is_empty() + ); + + let nested = middle.slice(UsizeCO::checked_from_start_len(1, 1).unwrap()); + assert_eq!(nested.iter().collect::>(), vec![super::Symbol::Two]); + assert_eq!(nested.to_packed_string().to_vec(), vec![super::Symbol::Two]); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let binary_slice = binary + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert_eq!( + binary_slice.iter().collect::>(), + vec![PackedSymbol::One, PackedSymbol::One] + ); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + let byte_slice = bytes + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert_eq!( + byte_slice.to_packed_string().to_vec(), + vec![SparseByte::Maximum, SparseByte::Middle] + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_slice = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()) + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert_eq!( + oct_slice + .iter() + .map(|character| character.code()) + .collect::>(), + vec![5, 6] + ); + assert_eq!(oct_slice.to_packed_string().bits().bit_len(), 6); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_slice = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()) + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + assert_eq!( + wide_slice + .iter() + .map(|character| character.code()) + .collect::>(), + vec![99, 110, 121] + ); + assert_eq!(wide_slice.to_packed_string().bits().bit_len(), 21); + + assert_eq!(view.char_len(), 5); + assert_eq!(view.get(0), Some(super::Symbol::Zero)); + assert_eq!(view.get(4), Some(super::Symbol::Zero)); +} + +#[test] +fn packed_str_slice_from_clamps_at_the_view_end() { + let owner = super::packed(&[0, 1, 2, 1, 0]); + let view = owner.as_packed_str(); + assert_eq!( + view.slice_from(0).iter().collect::>(), + owner.to_vec() + ); + assert_eq!( + view.slice_from(1).iter().collect::>(), + vec![ + super::Symbol::One, + super::Symbol::Two, + super::Symbol::One, + super::Symbol::Zero + ] + ); + assert_eq!( + view.slice_from(4).iter().collect::>(), + vec![super::Symbol::Zero] + ); + assert!(view.slice_from(5).is_empty()); + assert!(view.slice_from(6).is_empty()); + assert!(view.slice_from(usize::MAX).is_empty()); + + let empty_owner = PackedString::::new(); + assert!(empty_owner.as_packed_str().slice_from(0).is_empty()); + assert!( + empty_owner + .as_packed_str() + .slice_from(usize::MAX) + .is_empty() + ); + + let nested = view + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()) + .slice_from(1); + assert_eq!( + nested.iter().collect::>(), + vec![super::Symbol::Two, super::Symbol::One] + ); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let binary_suffix = binary.as_packed_str().slice_from(1); + assert_eq!( + binary_suffix.iter().collect::>(), + vec![PackedSymbol::One, PackedSymbol::One] + ); + assert_eq!(binary_suffix.to_packed_string().bits().bit_len(), 2); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + let byte_suffix = bytes.as_packed_str().slice_from(1); + assert_eq!( + byte_suffix.iter().collect::>(), + vec![SparseByte::Maximum, SparseByte::Middle] + ); + assert_eq!(byte_suffix.to_packed_string().bits().bit_len(), 16); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_suffix = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()) + .slice_from(1); + assert_eq!( + oct_suffix + .iter() + .map(|character| character.code()) + .collect::>(), + vec![5, 6, 7] + ); + assert_eq!(oct_suffix.to_packed_string().bits().bit_len(), 9); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_suffix = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()) + .slice_from(1); + assert_eq!( + wide_suffix + .iter() + .map(|character| character.code()) + .collect::>(), + vec![99, 110, 121] + ); + assert_eq!(wide_suffix.to_packed_string().bits().bit_len(), 21); + + assert_eq!(view.char_len(), 5); + assert_eq!(view.get(0), Some(super::Symbol::Zero)); + assert_eq!(view.get(4), Some(super::Symbol::Zero)); +} + +#[test] +fn packed_str_slice_until_clamps_at_the_view_start() { + let owner = super::packed(&[0, 1, 2, 1, 0]); + let view = owner.as_packed_str(); + assert!(view.slice_until(0).is_empty()); + assert_eq!( + view.slice_until(1).iter().collect::>(), + vec![super::Symbol::Zero] + ); + assert_eq!( + view.slice_until(4).iter().collect::>(), + vec![ + super::Symbol::Zero, + super::Symbol::One, + super::Symbol::Two, + super::Symbol::One, + ] + ); + assert_eq!( + view.slice_until(5).iter().collect::>(), + owner.to_vec() + ); + assert_eq!( + view.slice_until(usize::MAX).iter().collect::>(), + owner.to_vec() + ); + + let empty_owner = PackedString::::new(); + assert!(empty_owner.as_packed_str().slice_until(0).is_empty()); + assert!( + empty_owner + .as_packed_str() + .slice_until(usize::MAX) + .is_empty() + ); + + let offset_owner = super::packed(&[0, 1, 2, 1, 0]); + let offset_view = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + assert!(offset_view.slice_until(0).is_empty()); + assert_eq!( + offset_view.slice_until(1).iter().collect::>(), + vec![super::Symbol::One] + ); + let nested = offset_view.slice_until(2).slice_until(1); + assert_eq!(nested.iter().collect::>(), vec![super::Symbol::One]); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let binary_prefix = binary.as_packed_str().slice_until(2); + assert_eq!( + binary_prefix.iter().collect::>(), + vec![PackedSymbol::Zero, PackedSymbol::One] + ); + assert_eq!(binary_prefix.to_packed_string().bits().bit_len(), 2); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + let byte_prefix = bytes.as_packed_str().slice_until(2); + assert_eq!( + byte_prefix.to_packed_string().to_vec(), + vec![SparseByte::Zero, SparseByte::Maximum] + ); + assert_eq!(byte_prefix.to_packed_string().bits().bit_len(), 16); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_prefix = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()) + .slice_until(3); + assert_eq!( + oct_prefix + .iter() + .map(|character| character.code()) + .collect::>(), + vec![4, 5, 6] + ); + assert_eq!(oct_prefix.to_packed_string().bits().bit_len(), 9); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_prefix = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()) + .slice_until(3); + assert_eq!( + wide_prefix + .iter() + .map(|character| character.code()) + .collect::>(), + vec![88, 99, 110] + ); + assert_eq!(wide_prefix.to_packed_string().bits().bit_len(), 21); + + assert_eq!(view.char_len(), 5); + assert_eq!(view.get(0), Some(super::Symbol::Zero)); + assert_eq!(view.get(4), Some(super::Symbol::Zero)); +} diff --git a/tests/packed/tests_for_access/tests_for_vec_access_semantics.rs b/tests/packed/tests_for_access/tests_for_vec_access_semantics.rs new file mode 100644 index 0000000..4148a5f --- /dev/null +++ b/tests/packed/tests_for_access/tests_for_vec_access_semantics.rs @@ -0,0 +1,25 @@ +use int_intervals::UsizeCO; +use proptest::prelude::*; + +proptest! { +#[test] + fn packed_access_and_slice_match_vec_oracles( + left in prop::collection::vec(0u8..=2, 0..=32), + start in 0usize..40, + len in 1usize..40, + ) { + let string = super::packed(&left); + prop_assert_eq!(string.char_len(), left.len()); + let view = string.as_packed_str(); + for index in 0..=left.len() + 1 { + prop_assert_eq!(string.get(index), left.get(index).copied().map(super::symbol)); + prop_assert_eq!(view.get(index), left.get(index).copied().map(super::symbol)); + } + + let oracle_start = start.min(left.len()); + let oracle_end = start.saturating_add(len).min(left.len()).max(oracle_start); + let slice = string.slice(UsizeCO::checked_from_start_len(start, len).unwrap()); + prop_assert_eq!(slice.to_vec(), left[oracle_start..oracle_end].iter().copied().map(super::symbol).collect::>()); + + } +} diff --git a/tests/packed/tests_for_access/tests_for_view_metadata.rs b/tests/packed/tests_for_access/tests_for_view_metadata.rs new file mode 100644 index 0000000..a0fe1cd --- /dev/null +++ b/tests/packed/tests_for_access/tests_for_view_metadata.rs @@ -0,0 +1,133 @@ +use super::*; + +#[test] +fn packed_str_clone_preserves_sliced_cross_word_views() { + let oct_codes: Vec = (0..64).map(|index| index as u8 % 8).collect(); + let oct_string = packed_as::(&oct_codes, oct); + let oct_view = oct_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 24).unwrap()); + let oct_copy = oct_view; + let oct_clone = oct_view.clone(); + assert!(oct_clone == oct_view); + assert!(oct_clone == oct_copy); + assert_eq!( + oct_clone.iter().collect::>(), + oct_codes[20..44] + .iter() + .copied() + .map(oct) + .collect::>() + ); + assert_eq!(oct_clone.iter().count(), 24); + + let wide_codes: Vec = (0..20).map(|index| (index * 7) as u8 % 128).collect(); + let wide_string = packed_as::(&wide_codes, wide); + let wide_view = wide_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + let wide_clone = wide_view.clone(); + assert!(wide_clone == wide_view); + assert_eq!( + wide_clone.iter().collect::>(), + wide_codes[8..13] + .iter() + .copied() + .map(wide) + .collect::>() + ); + assert_eq!(wide_clone.first(), Some(wide(wide_codes[8]))); + assert_eq!(wide_clone.last(), Some(wide(wide_codes[12]))); + + let empty = wide_string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(999, 1).unwrap()); + assert!(empty.clone().is_empty()); + assert!(empty.clone() == empty); +} + +#[test] +fn packed_str_adds_no_runtime_metadata() { + assert_eq!( + core::mem::size_of::>(), + core::mem::size_of::>() + ); + assert_eq!( + core::mem::size_of::>(), + core::mem::size_of::>() + ); +} + +#[test] +fn packed_str_char_len_tracks_widths_boundaries_and_clamped_slices() { + let binary = packed_as::(&[0, 1], |code| { + if code == 0 { + PackedSymbol::Zero + } else { + PackedSymbol::One + } + }); + assert_eq!(binary.as_packed_str().char_len(), 2); + + let bytes = packed_as::(&[0, 3, 255], |code| match code { + 0 => SparseByte::Zero, + 3 => SparseByte::Middle, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + assert_eq!(bytes.as_packed_str().char_len(), 3); + + for (codes, expected) in [ + ((0..21).map(|index| index as u8 % 8).collect::>(), 21), + ((0..22).map(|index| index as u8 % 8).collect::>(), 22), + ] { + let string = packed_as::(&codes, oct); + assert_eq!(string.as_packed_str().char_len(), expected); + let sliced = string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, expected - 2).unwrap()); + assert_eq!(sliced.char_len(), expected - 2); + } + + for length in [9, 10] { + let codes: Vec<_> = (0..length).map(|index| (index * 7) as u8 % 128).collect(); + let string = packed_as::(&codes, wide); + assert_eq!(string.as_packed_str().char_len(), length); + } + + let empty = bytes + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(99, 1).unwrap()); + assert_eq!(empty.char_len(), 0); +} + +#[test] +fn packed_str_is_empty_depends_only_on_view_length() { + let empty_binary_owner = PackedString::::new(); + let empty_binary = empty_binary_owner.as_packed_str(); + assert!(empty_binary.is_empty()); + assert_eq!(empty_binary.char_len(), 0); + + let singleton = packed_as::(&[0], |code| match code { + 0 => SparseByte::Zero, + _ => unreachable!(), + }); + assert!(!singleton.as_packed_str().is_empty()); + + let string = packed_as::(&[0, 1, 2, 3, 4, 5, 6, 7], oct); + let empty_at_end = string + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(99, 1).unwrap()); + let empty_from_end = string.as_packed_str().slice_from(99); + let empty_until_start = string.as_packed_str().slice_until(0); + for view in [empty_at_end, empty_from_end, empty_until_start] { + assert!(view.is_empty()); + assert_eq!(view.char_len(), 0); + assert!(view == view.clone()); + } + + let cross_word_owner = packed_as::(&[0; 22], oct); + let cross_word = cross_word_owner.as_packed_str(); + assert!(!cross_word.is_empty()); + assert_eq!(cross_word.char_len(), 22); +} diff --git a/tests/packed/tests_for_construction.rs b/tests/packed/tests_for_construction.rs new file mode 100644 index 0000000..1de2850 --- /dev/null +++ b/tests/packed/tests_for_construction.rs @@ -0,0 +1,68 @@ +use super::{Oct, PackedSymbol, SparseByte, Symbol, WideCode, oct, packed_as, symbol, wide}; +use bit_string::{BitString, PackedString, traits::PackedChar}; + +fn assert_packed_owner(codes: &[u8], decode: fn(u8) -> C) +where + C: PackedChar + core::fmt::Debug, +{ + let owner = packed_as(codes, decode); + let expected = codes.iter().copied().map(decode).collect::>(); + assert_eq!(owner.char_len(), codes.len()); + assert_eq!(owner.bits_per_char(), usize::from(BITS)); + assert_eq!(owner.bits().bit_len(), codes.len() * usize::from(BITS)); + assert_eq!(owner.to_vec(), expected); + for (index, character) in expected.iter().copied().enumerate() { + assert_eq!(owner.get(index), Some(character)); + } + assert_eq!(owner.get(codes.len()), None); + assert!(owner.as_packed_str().iter().collect::>() == expected); + assert!(owner.clone().into_bits() == owner.bits().clone()); +} + +#[derive(Clone, Copy, PartialEq, Eq)] +struct ManualSymbol; + +#[derive(Clone, Copy, PartialEq, Eq)] +struct UnsupportedWidth; + +impl PackedChar<0> for UnsupportedWidth { + fn code(self) -> u8 { + 0 + } + + fn from_code(code: u8) -> Option { + (code == 0).then_some(Self) + } +} + +impl PackedChar<9> for UnsupportedWidth { + fn code(self) -> u8 { + 0 + } + + fn from_code(code: u8) -> Option { + (code == 0).then_some(Self) + } +} + +impl PackedChar<1> for ManualSymbol { + fn code(self) -> u8 { + 0 + } + + fn from_code(code: u8) -> Option { + (code == 0).then_some(Self) + } +} + +#[path = "tests_for_construction/tests_for_derive_repr.rs"] +mod tests_for_derive_repr; + +#[path = "tests_for_construction/tests_for_input_construction.rs"] +mod tests_for_input_construction; + +#[path = "tests_for_construction/tests_for_width_layout.rs"] +mod tests_for_width_layout; + +#[path = "tests_for_construction/tests_for_raw_bits_validation.rs"] +mod tests_for_raw_bits_validation; diff --git a/tests/packed/tests_for_construction/tests_for_derive_repr.rs b/tests/packed/tests_for_construction/tests_for_derive_repr.rs new file mode 100644 index 0000000..7fe85db --- /dev/null +++ b/tests/packed/tests_for_construction/tests_for_derive_repr.rs @@ -0,0 +1,41 @@ +use super::*; + +#[test] +fn generated_sparse_code_round_trip_is_executable() { + assert_eq!(SparseByte::Zero.code(), 0); + assert_eq!(SparseByte::Middle.code(), 3); + assert_eq!(SparseByte::Maximum.code(), u8::MAX); + + for code in 0..=u8::MAX { + let expected = match code { + 0 => Some(SparseByte::Zero), + 3 => Some(SparseByte::Middle), + u8::MAX => Some(SparseByte::Maximum), + _ => None, + }; + assert_eq!(SparseByte::from_code(code), expected); + } +} + +#[test] +fn packed_attribute_encoding_round_trips_through_packed_string() { + let string = PackedString::::from_chars([Symbol::Two, Symbol::Zero, Symbol::One]); + assert_eq!(string.get(0), Some(Symbol::Two)); + assert_eq!(string.get(1), Some(Symbol::Zero)); + assert_eq!(string.get(2), Some(Symbol::One)); +} + +#[test] +fn manual_packed_char_implementations_remain_supported() { + assert_eq!( + PackedString::::from_chars([ManualSymbol]).char_len(), + 1 + ); +} + +#[test] +fn attribute_macro_generates_packed_char_impl() { + let string = + PackedString::::from_chars([PackedSymbol::One, PackedSymbol::Zero]); + assert_eq!(string.get(0), Some(PackedSymbol::One)); +} diff --git a/tests/packed/tests_for_construction/tests_for_input_construction.rs b/tests/packed/tests_for_construction/tests_for_input_construction.rs new file mode 100644 index 0000000..305b321 --- /dev/null +++ b/tests/packed/tests_for_construction/tests_for_input_construction.rs @@ -0,0 +1,229 @@ +use super::*; + +#[test] +fn packed_from_array_preserves_order_length_and_layout() { + let empty_array: [Symbol; 0] = []; + let empty = PackedString::::from(empty_array); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + + let singleton = PackedString::::from([SparseByte::Maximum]); + assert_eq!(singleton.to_vec(), vec![SparseByte::Maximum]); + assert_eq!(singleton.char_len(), 1); + assert_eq!(singleton.bits().bit_len(), 8); + + let symbols = [Symbol::Zero, Symbol::Two, Symbol::One, Symbol::Zero]; + let symbol_from_array = PackedString::::from(symbols); + let symbol_from_chars = PackedString::from_chars(symbols); + assert!(symbol_from_array == symbol_from_chars); + assert_eq!(symbol_from_array.to_vec(), symbols); + + let oct_values = [Oct::V7; 22]; + let oct_from_array = PackedString::::from(oct_values); + let oct_from_chars = PackedString::from_chars(oct_values); + assert!(oct_from_array == oct_from_chars); + assert_eq!(oct_from_array.char_len(), 22); + assert_eq!(oct_from_array.bits().bit_len(), 22 * 3); + assert_eq!(oct_from_array.bits().words(), oct_from_chars.bits().words()); + + let wide_values = [WideCode(127); 10]; + let wide_from_array = PackedString::::from(wide_values); + let wide_from_chars = PackedString::from_chars(wide_values); + assert!(wide_from_array == wide_from_chars); + assert_eq!(wide_from_array.char_len(), 10); + assert_eq!(wide_from_array.bits().bit_len(), 10 * 7); + assert_eq!( + wide_from_array.bits().words(), + wide_from_chars.bits().words() + ); +} + +#[test] +fn packed_from_slice_copies_codes_in_order() { + let source = [Oct::V0, Oct::V7, Oct::V3, Oct::V1, Oct::V6]; + let from_slice = PackedString::::from(&source[..]); + assert_eq!(from_slice.to_vec(), source); + assert_eq!(from_slice.char_len(), source.len()); + assert_eq!(from_slice.bits().bit_len(), source.len() * 3); + + let empty_source: [Oct; 0] = []; + let empty = PackedString::::from(&empty_source[..]); + assert!(empty.is_empty()); + assert!(empty.bits().words().is_empty()); + + let wide_source = [WideCode(0), WideCode(64), WideCode(127), WideCode(1)]; + let wide = PackedString::::from(&wide_source[..]); + assert_eq!(wide.to_vec(), wide_source); + assert_eq!(wide.bits().bit_len(), wide_source.len() * 7); +} + +#[test] +fn packed_from_chars_preserves_sequence_and_packed_layout() { + let empty = PackedString::::from_chars(core::iter::empty::()); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + + let symbols = [Symbol::Zero, Symbol::Two, Symbol::One, Symbol::Zero]; + let symbol_string = PackedString::::from_chars(symbols); + assert_eq!(symbol_string.to_vec(), symbols); + assert_eq!(symbol_string.bits().bit_len(), symbols.len() * 2); + + let zero_codes = vec![0; 22]; + let zero_string = PackedString::::from_chars(zero_codes.iter().copied().map(oct)); + assert!(!zero_string.is_empty()); + assert_eq!(zero_string.char_len(), zero_codes.len()); + assert_eq!(zero_string.bits().bit_len(), zero_codes.len() * 3); + assert_eq!(zero_string.to_vec(), vec![Oct::V0; 22]); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let oct_string = PackedString::::from_chars(oct_codes.iter().copied().map(oct)); + assert_eq!( + oct_string.to_vec(), + oct_codes.iter().copied().map(oct).collect::>() + ); + let oct_expected = BitString::from_iter( + oct_codes + .iter() + .flat_map(|&code| (0..3).map(move |offset| code & (1 << offset) != 0)), + ); + assert_eq!(oct_string.bits().bit_len(), oct_expected.bit_len()); + assert_eq!(oct_string.bits().words(), oct_expected.words()); + + let wide_codes: Vec<_> = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let wide_string = PackedString::::from_chars(wide_codes.iter().copied().map(wide)); + assert_eq!( + wide_string.to_vec(), + wide_codes.iter().copied().map(wide).collect::>() + ); + assert_eq!(wide_string.char_len(), 10); + assert_eq!(wide_string.bits().bit_len(), 10 * 7); + + let byte_string = PackedString::::from_chars([ + SparseByte::Zero, + SparseByte::Maximum, + SparseByte::Middle, + ]); + assert_eq!( + byte_string.to_vec(), + vec![SparseByte::Zero, SparseByte::Maximum, SparseByte::Middle] + ); + assert_eq!(byte_string.get(1), Some(SparseByte::Maximum)); + + assert!( + std::panic::catch_unwind(|| { + PackedString::::from_chars(core::iter::empty()); + }) + .is_err() + ); + assert!( + std::panic::catch_unwind(|| { + PackedString::::from_chars(core::iter::empty()); + }) + .is_err() + ); +} + +#[test] +fn packed_from_iter_matches_from_chars_for_single_pass_inputs() { + let empty: PackedString = core::iter::empty().collect(); + assert!(empty.is_empty()); + assert_eq!(empty.bits().bit_len(), 0); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let collected: PackedString = oct_codes.iter().copied().map(oct).collect(); + let constructed = PackedString::from_chars(oct_codes.iter().copied().map(oct)); + assert!(collected == constructed); + assert_eq!( + collected.to_vec(), + oct_codes.iter().copied().map(oct).collect::>() + ); + assert_eq!(collected.bits().bit_len(), 22 * 3); + assert_eq!(collected.bits().words(), constructed.bits().words()); + + let mut next = 0; + let lazy = core::iter::from_fn(|| { + let value = (next < 10).then_some(WideCode((next * 11) as u8 % 128)); + next += 1; + value + }); + let wide: PackedString = lazy.collect(); + assert_eq!(wide.char_len(), 10); + assert_eq!(wide.bits().bit_len(), 10 * 7); + assert_eq!(wide.last(), Some(WideCode(99))); + + let filtered: PackedString = + [SparseByte::Zero, SparseByte::Maximum, SparseByte::Middle] + .into_iter() + .filter(|_| false) + .collect(); + assert!(filtered.is_empty()); + assert_eq!(filtered.bits().bit_len(), 0); + + let zeros: PackedString = core::iter::repeat(Oct::V0).take(22).collect(); + assert!(!zeros.is_empty()); + assert_eq!(zeros.char_len(), 22); + assert_eq!(zeros.bits().words(), &[0, 0]); + + assert!( + std::panic::catch_unwind(|| { + let _: PackedString = core::iter::empty().collect(); + }) + .is_err() + ); + assert!( + std::panic::catch_unwind(|| { + let _: PackedString = core::iter::empty().collect(); + }) + .is_err() + ); +} + +#[test] +fn packed_repeat_produces_exactly_the_requested_codes() { + let empty = PackedString::::repeat(Oct::V0, 0); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + + let zero = PackedString::::repeat(Oct::V0, 22); + assert!(!zero.is_empty()); + assert_eq!(zero.to_vec(), vec![Oct::V0; 22]); + assert_eq!(zero.bits().bit_len(), 22 * 3); + + let oct = PackedString::::repeat(Oct::V7, 22); + let oct_expected = PackedString::from_chars(core::iter::repeat_n(Oct::V7, 22)); + assert!(oct == oct_expected); + assert_eq!(oct.get(0), Some(Oct::V7)); + assert_eq!(oct.get(21), Some(Oct::V7)); + assert_eq!(oct.bits().words(), oct_expected.bits().words()); + + let binary = PackedString::::repeat(PackedSymbol::One, 65); + assert_eq!(binary.char_len(), 65); + assert_eq!(binary.bits().bit_len(), 65); + assert_eq!(binary.first(), Some(PackedSymbol::One)); + assert_eq!(binary.last(), Some(PackedSymbol::One)); + + let wide = PackedString::::repeat(WideCode(127), 10); + assert_eq!(wide.to_vec(), vec![WideCode(127); 10]); + assert_eq!(wide.bits().bit_len(), 10 * 7); + assert_eq!(wide.bits().words().len(), 2); + + let bytes = PackedString::::repeat(SparseByte::Maximum, 9); + assert_eq!(bytes.to_vec(), vec![SparseByte::Maximum; 9]); + assert_eq!(bytes.bits().bit_len(), 9 * 8); + + assert!( + std::panic::catch_unwind(|| { + PackedString::::repeat(UnsupportedWidth, 0); + }) + .is_err() + ); + assert!( + std::panic::catch_unwind(|| { + PackedString::::repeat(UnsupportedWidth, 1); + }) + .is_err() + ); +} diff --git a/tests/packed/tests_for_construction/tests_for_raw_bits_validation.rs b/tests/packed/tests_for_construction/tests_for_raw_bits_validation.rs new file mode 100644 index 0000000..4385da3 --- /dev/null +++ b/tests/packed/tests_for_construction/tests_for_raw_bits_validation.rs @@ -0,0 +1,78 @@ +use super::*; + +#[test] +fn from_bits_rejects_misaligned_and_unknown_codes() { + assert!(PackedString::::from_bits(BitString::from_iter([true])).is_none()); + assert!(PackedString::::from_bits(BitString::from_iter([true, true])).is_none()); +} + +fn assert_empty_from_bits() +where + C: PackedChar, +{ + let empty = + PackedString::::from_bits(BitString::from_iter(core::iter::empty::())) + .expect("empty aligned payload should be accepted"); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); +} + +#[test] +fn from_bits_validates_alignment_codes_and_widths() { + let raw_codes = |codes: &[u8], bits: u8| { + BitString::from_iter( + codes + .iter() + .flat_map(|&code| (0..bits).map(move |offset| code & (1 << offset) != 0)), + ) + }; + + assert_empty_from_bits::(); + assert_empty_from_bits::(); + assert_empty_from_bits::(); + assert_empty_from_bits::(); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let oct_original = packed_as::(&oct_codes, oct); + let oct_restored = PackedString::::from_bits(oct_original.clone().into_bits()) + .expect("valid three-bit payload should be accepted"); + assert!(oct_restored == oct_original); + assert_eq!( + oct_restored.to_vec(), + oct_codes.iter().copied().map(oct).collect::>() + ); + + let wide_codes: Vec<_> = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let wide_original = packed_as::(&wide_codes, wide); + let wide_restored = PackedString::::from_bits(wide_original.clone().into_bits()) + .expect("valid seven-bit payload should be accepted"); + assert!(wide_restored == wide_original); + assert_eq!( + wide_restored.to_vec(), + wide_codes.iter().copied().map(wide).collect::>() + ); + + let maximum_byte = PackedString::::from_bits(raw_codes(&[255], 8)) + .expect("maximum eight-bit code should be accepted"); + assert_eq!(maximum_byte.to_vec(), vec![SparseByte::Maximum]); + + assert!(PackedString::::from_bits(raw_codes(&[3], 2)).is_none()); + assert!(PackedString::::from_bits(raw_codes(&[0, 3, 0], 2)).is_none()); + assert!(PackedString::::from_bits(raw_codes(&[0, 0, 3], 2)).is_none()); + assert!(PackedString::::from_bits(raw_codes(&[1], 8)).is_none()); + assert!(PackedString::::from_bits(BitString::from_iter([true; 65])).is_none()); + assert!(PackedString::::from_bits(BitString::from_iter([true; 69])).is_none()); + + assert!( + std::panic::catch_unwind(|| { + PackedString::::from_bits(BitString::new()); + }) + .is_err() + ); + assert!( + std::panic::catch_unwind(|| { + PackedString::::from_bits(BitString::new()); + }) + .is_err() + ); +} diff --git a/tests/packed/tests_for_construction/tests_for_width_layout.rs b/tests/packed/tests_for_construction/tests_for_width_layout.rs new file mode 100644 index 0000000..41d768d --- /dev/null +++ b/tests/packed/tests_for_construction/tests_for_width_layout.rs @@ -0,0 +1,251 @@ +use super::*; + +#[test] +fn bits_per_char_is_the_type_level_width() { + let binary_empty = PackedString::::new(); + assert_eq!(binary_empty.bits_per_char(), 1); + + let oct_string = packed_as::(&[0; 22], oct); + assert_eq!(oct_string.bits_per_char(), 3); + assert_eq!(oct_string.bits().bit_len(), 22 * oct_string.bits_per_char()); + + let wide_string = packed_as::(&[0, 127], wide); + assert_eq!(wide_string.bits_per_char(), 7); + assert_eq!( + wide_string.bits().bit_len(), + 2 * wide_string.bits_per_char() + ); + + let byte_string = packed_as::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!(byte_string.bits_per_char(), 8); + assert_eq!( + byte_string.bits().bit_len(), + 3 * byte_string.bits_per_char() + ); +} + +#[test] +fn bits_exposes_the_owned_bitstring_layout() { + let empty = PackedString::::new(); + assert!(empty.bits().words().is_empty()); + assert_eq!(empty.bits().bit_len(), 0); + assert!(core::ptr::eq(empty.bits(), empty.bits())); + + let zero = packed_as::(&[0], |code| match code { + 0 => SparseByte::Zero, + _ => unreachable!(), + }); + assert_eq!(zero.bits().bit_len(), 8); + assert_eq!(zero.bits().words(), &[0]); + assert!(!zero.is_empty()); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_expected = BitString::from_iter( + oct_codes + .iter() + .flat_map(|&code| (0..3).map(move |offset| code & (1 << offset) != 0)), + ); + assert_eq!(oct_owner.bits().bit_len(), oct_expected.bit_len()); + assert_eq!(oct_owner.bits().words(), oct_expected.words()); + + let wide_codes: Vec<_> = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_expected = BitString::from_iter( + wide_codes + .iter() + .flat_map(|&code| (0..7).map(move |offset| code & (1 << offset) != 0)), + ); + assert_eq!(wide_owner.bits().bit_len(), wide_expected.bit_len()); + assert_eq!(wide_owner.bits().words(), wide_expected.words()); +} + +fn assert_default_matches_new() +where + C: PackedChar, +{ + let default = PackedString::::default(); + let new = PackedString::::new(); + assert!(default == new); + assert!(default.is_empty()); + assert_eq!(default.char_len(), 0); + assert_eq!(default.bits().bit_len(), 0); + assert!(default.bits().words().is_empty()); + assert_eq!(default.bits_per_char(), usize::from(BITS)); +} + +#[test] +fn packed_default_is_canonical_and_editable() { + assert_default_matches_new::(); + assert_default_matches_new::(); + assert_default_matches_new::(); + assert_default_matches_new::(); + assert_default_matches_new::(); + + let mut default = PackedString::::default(); + let mut new = PackedString::::new(); + for code in [ + Oct::V0, + Oct::V7, + Oct::V3, + Oct::V4, + Oct::V1, + Oct::V6, + Oct::V2, + Oct::V5, + ] { + default.push(code); + new.push(code); + } + assert!(default == new); + assert_eq!(default.char_len(), 8); + + default.clear(); + new.clear(); + assert!(default == new); + assert!(default.is_empty()); + assert_eq!(default.pop(), None); +} + +#[test] +fn packed_new_is_canonical_at_width_boundaries() { + fn assert_new_empty() + where + C: PackedChar, + { + let value = PackedString::::new(); + assert!(value.is_empty()); + assert_eq!(value.char_len(), 0); + assert_eq!(value.bits().bit_len(), 0); + assert!(value.bits().words().is_empty()); + } + assert_new_empty::(); + assert_new_empty::(); + assert_new_empty::(); + assert_new_empty::(); + assert_new_empty::(); + + let mut oct_string = PackedString::::new(); + oct_string.extend([Oct::V7; 22]); + assert_eq!(oct_string.char_len(), 22); + assert_eq!(oct_string.bits().bit_len(), 22 * 3); + assert_eq!(oct_string.get(21), Some(Oct::V7)); + + let mut wide_string = PackedString::::new(); + wide_string.extend([WideCode(127); 10]); + assert_eq!(wide_string.char_len(), 10); + assert_eq!(wide_string.bits().bit_len(), 10 * 7); + assert_eq!(wide_string.get(9), Some(WideCode(127))); + + assert!( + std::panic::catch_unwind(|| { + PackedString::::new(); + }) + .is_err() + ); + assert!( + std::panic::catch_unwind(|| { + PackedString::::new(); + }) + .is_err() + ); +} + +#[test] +fn packed_string_clone_copies_storage_and_preserves_invariants() { + let oct_codes: Vec = (0..22).map(|index| index as u8 % 8).collect(); + let original = packed_as::(&oct_codes, oct); + let original_bits = original.bits().clone(); + let original_values = original.to_vec(); + let mut clone = original.clone(); + assert!(clone.bits() == &original_bits); + assert_eq!(clone.to_vec(), original_values); + assert!(clone.as_packed_str() == original.as_packed_str()); + assert_eq!( + clone.as_packed_str().iter().collect::>(), + original_values + ); + + clone.push(Oct::V7); + assert_eq!(original.to_vec(), original_values); + assert_eq!(clone.to_vec().len(), original_values.len() + 1); + clone.pop(); + assert!(clone == original); + + let mut edited_original = original.clone(); + edited_original.clear(); + assert!(original == clone); + assert!(edited_original.is_empty()); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let mut binary_clone = binary.clone(); + binary_clone.push(PackedSymbol::Zero); + assert_eq!(binary.to_vec().len(), 3); + assert_eq!(binary_clone.to_vec().len(), 4); + assert_eq!(binary.bits().bit_len(), 3); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + let mut bytes_clone = bytes.clone(); + assert_eq!( + bytes_clone.set(0, SparseByte::Maximum), + Some(SparseByte::Zero) + ); + assert_eq!(bytes.get(0), Some(SparseByte::Zero)); + assert_eq!(bytes_clone.get(0), Some(SparseByte::Maximum)); + assert_eq!(bytes.bits().bit_len(), 24); + + let wide_codes: Vec = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_clone = wide_owner.clone(); + assert!(wide_clone.bits() == wide_owner.bits()); + assert_eq!( + wide_clone.to_vec(), + wide_codes.iter().copied().map(wide).collect::>() + ); + assert_eq!(wide_clone.as_packed_str().char_len(), 10); +} + +#[test] +fn packed_string_representation_invariant_is_width_derived() { + assert_eq!( + core::mem::size_of::>(), + core::mem::size_of::() + ); + assert_packed_owner::(&[0, 1, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert_packed_owner::(&[0, 1, 2, 1], super::symbol); + assert_packed_owner::(&[0, 1, 6, 7, 2, 5, 4, 3], oct); + assert_packed_owner::(&[0, 1, 63, 64, 126, 127], wide); + assert_packed_owner::(&[0, 255, 3], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + 3 => SparseByte::Middle, + _ => unreachable!(), + }); + + let empty = PackedString::::new(); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + assert!(empty.as_packed_str().is_empty()); + + let invalid_sparse_code = + BitString::from_iter([true, false, false, false, false, false, false, false]); + assert!(PackedString::::from_bits(invalid_sparse_code).is_none()); +} diff --git a/tests/packed/tests_for_conversion.rs b/tests/packed/tests_for_conversion.rs new file mode 100644 index 0000000..a940e29 --- /dev/null +++ b/tests/packed/tests_for_conversion.rs @@ -0,0 +1,164 @@ +use super::{Oct, PackedSymbol, SparseByte, WideCode, oct, packed_as, wide}; +use bit_string::PackedString; +use int_intervals::UsizeCO; + +#[test] +fn packed_str_to_packed_string_preserves_views_and_owns_storage() { + let empty_owner = PackedString::::new(); + let empty = empty_owner.as_packed_str().to_packed_string(); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_view = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_materialized = oct_view.to_packed_string(); + assert_eq!(oct_materialized.char_len(), 4); + assert_eq!( + oct_materialized.to_vec(), + oct_codes[20..24] + .iter() + .copied() + .map(oct) + .collect::>() + ); + assert!(oct_materialized.as_packed_str() == oct_view); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_view = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + let wide_materialized = wide_view.to_packed_string(); + assert!(wide_materialized.as_packed_str() == wide_view); + assert_eq!(wide_materialized.bits().bit_len(), 35); + + let sparse_materialized = { + let owner = packed_as::(&[255, 3, 0], |code| match code { + 0 => SparseByte::Zero, + 3 => SparseByte::Middle, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()) + .to_packed_string() + }; + assert_eq!( + sparse_materialized.to_vec(), + vec![SparseByte::Middle, SparseByte::Zero] + ); +} + +#[test] +fn packed_str_materialization_does_not_alias_the_source_storage() { + let codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let mut owner = packed_as::(&codes, oct); + let materialized = { + let view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + view.to_packed_string() + }; + + owner.set(20, Oct::V0); + assert_eq!( + materialized.to_vec(), + codes[20..].iter().copied().map(oct).collect::>() + ); + assert_eq!(materialized.bits().bit_len(), 4 * 3); + + let mut changed = materialized.clone(); + changed.set(0, Oct::V7); + assert_eq!(materialized.get(0), Some(Oct::V4)); + assert_eq!(changed.get(0), Some(Oct::V7)); + assert_eq!(owner.get(20), Some(Oct::V0)); +} + +#[test] +fn packed_string_as_packed_str_covers_the_full_owner_without_copying_values() { + let empty = PackedString::::new(); + let empty_view = empty.as_packed_str(); + assert!(empty_view.is_empty()); + assert_eq!(empty_view.char_len(), empty.char_len()); + assert!(empty_view.to_packed_string().bits() == empty.bits()); + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let binary_view = binary.as_packed_str(); + assert_eq!(binary_view.iter().collect::>(), binary.to_vec()); + assert!(binary_view.to_packed_string().bits() == binary.bits()); + + let bytes = packed_as::(&[0, 255], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + let byte_view = bytes.as_packed_str(); + assert_eq!(byte_view.char_len(), 2); + assert_eq!(byte_view.iter().collect::>(), bytes.to_vec()); + assert!(byte_view.to_packed_string().bits() == bytes.bits()); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_view = oct_owner.as_packed_str(); + assert_eq!( + oct_view.iter().collect::>(), + oct_codes.iter().copied().map(oct).collect::>() + ); + assert!(oct_view.to_packed_string().bits() == oct_owner.bits()); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_view = wide_owner.as_packed_str(); + assert_eq!( + wide_view.iter().collect::>(), + wide_codes.iter().copied().map(wide).collect::>() + ); + assert!(wide_view.to_packed_string().bits() == wide_owner.bits()); + assert!(wide_view == wide_view.clone()); +} + +#[test] +fn packed_into_bits_moves_exact_payload_and_round_trips() { + let empty = PackedString::::new(); + let empty_bits = empty.into_bits(); + assert_eq!(empty_bits.bit_len(), 0); + assert!(empty_bits.words().is_empty()); + + let zero = packed_as::(&[0], |code| match code { + 0 => SparseByte::Zero, + _ => unreachable!(), + }); + let zero_bit_len = zero.bits().bit_len(); + let zero_words = zero.bits().words().to_vec(); + let zero_bits = zero.into_bits(); + assert_eq!(zero_bits.bit_len(), zero_bit_len); + assert_eq!(zero_bits.words(), zero_words.as_slice()); + assert_eq!(zero_bits.bit_len(), 8); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_bit_len = oct_owner.bits().bit_len(); + let oct_words = oct_owner.bits().words().to_vec(); + let oct_round_trip = PackedString::::from_bits(oct_owner.clone().into_bits()) + .expect("a packed owner must round-trip through raw bits"); + assert_eq!(oct_round_trip.to_vec(), oct_owner.to_vec()); + let oct_bits = oct_owner.into_bits(); + assert_eq!(oct_bits.bit_len(), oct_bit_len); + assert_eq!(oct_bits.words(), oct_words.as_slice()); + assert_eq!(oct_bits.bit_len(), 22 * 3); + + let wide_codes: Vec<_> = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_bits = wide_owner.into_bits(); + assert_eq!(wide_bits.bit_len(), 10 * 7); + assert_eq!(wide_bits.words().len(), 2); +} diff --git a/tests/packed/tests_for_editing.rs b/tests/packed/tests_for_editing.rs new file mode 100644 index 0000000..e6ae358 --- /dev/null +++ b/tests/packed/tests_for_editing.rs @@ -0,0 +1,85 @@ +use super::{Oct, PackedSymbol, SparseByte, Symbol, WideCode, oct, packed, packed_as, wide}; +use bit_string::{PackedString, traits::PackedChar}; +use int_intervals::UsizeCO; + +#[path = "tests_for_editing/tests_for_append_and_lifecycle.rs"] +mod tests_for_append_and_lifecycle; + +#[path = "tests_for_editing/tests_for_vec_edit_semantics.rs"] +mod tests_for_vec_edit_semantics; + +#[path = "tests_for_editing/tests_for_cross_word_boundaries.rs"] +mod tests_for_cross_word_boundaries; + +fn assert_edits( + initial: &[u8], + replacement: &[u8], + insert_index: usize, + remove_index: usize, + replace_start: usize, + inserted_code: u8, + decode: fn(u8) -> C, +) where + C: PackedChar + core::fmt::Debug, +{ + let mut string = packed_as(initial, decode); + let mut oracle = initial.to_vec(); + + let oracle_insert_index = insert_index.min(oracle.len()); + string.insert(insert_index, decode(inserted_code)); + oracle.insert(oracle_insert_index, inserted_code); + + if !oracle.is_empty() { + let remove_index = remove_index.min(oracle.len() - 1); + assert_eq!( + string.remove(remove_index).code(), + oracle.remove(remove_index) + ); + } + + let oracle_replace_start = replace_start.min(oracle.len()); + let replace_end = oracle_replace_start + .saturating_add(replacement.len()) + .min(oracle.len()); + string.replace_assign(replace_start, &packed_as(replacement, decode)); + oracle.splice( + oracle_replace_start..replace_end, + replacement.iter().copied(), + ); + assert_eq!( + string + .to_vec() + .iter() + .copied() + .map(|value| value.code()) + .collect::>(), + oracle + ); + + let drain_len = insert_index.max(1); + let oracle_drain_start = remove_index.min(oracle.len()); + let oracle_drain_end = remove_index + .saturating_add(drain_len) + .min(oracle.len()) + .max(oracle_drain_start); + string.drain_interval_assign(UsizeCO::checked_from_start_len(remove_index, drain_len).unwrap()); + oracle.drain(oracle_drain_start..oracle_drain_end); + assert_eq!( + string + .to_vec() + .iter() + .copied() + .map(|value| value.code()) + .collect::>(), + oracle + ); +} + +#[path = "tests_for_editing/tests_for_replacement.rs"] +mod tests_for_replacement; + +#[path = "tests_for_editing/tests_for_slicing.rs"] +mod tests_for_slicing; + +#[path = "tests_for_editing/tests_for_sequence_operations.rs"] +mod tests_for_sequence_operations; diff --git a/tests/packed/tests_for_editing/tests_for_append_and_lifecycle.rs b/tests/packed/tests_for_editing/tests_for_append_and_lifecycle.rs new file mode 100644 index 0000000..3655bfb --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_append_and_lifecycle.rs @@ -0,0 +1,251 @@ +use super::{Oct, WideCode, packed_as}; +use bit_string::{BitString, PackedString, traits::PackedChar}; + +#[test] +fn packed_out_of_bounds_intervals_preserve_empty_semantics() { + let string = super::packed(&[0, 1, 2]); + let out_of_bounds = int_intervals::UsizeCO::checked_from_start_len(99, 1).unwrap(); + + assert!(string.slice(out_of_bounds).is_empty()); + + let mut drained = string.clone(); + drained.drain_interval_assign(out_of_bounds); + assert_eq!(drained, string); +} + +#[test] +fn packed_clear_removes_all_codes_and_allows_reuse() { + let mut empty = PackedString::::new(); + empty.clear(); + empty.clear(); + assert!(empty.is_empty()); + assert_eq!(empty.char_len(), 0); + assert_eq!(empty.bits().bit_len(), 0); + assert!(empty.bits().words().is_empty()); + assert!(empty.to_vec().is_empty()); + assert_eq!(empty.get(0), None); + assert_eq!(empty.first(), None); + assert_eq!(empty.last(), None); + assert_eq!(empty.as_packed_str().iter().next(), None); + + let mut zero = super::packed_as::(&[0], |code| match code { + 0 => super::SparseByte::Zero, + _ => unreachable!(), + }); + assert!(!zero.is_empty()); + assert_eq!(zero.bits().words(), &[0]); + zero.clear(); + assert!(zero.is_empty()); + assert!(zero.bits().words().is_empty()); + + let oct_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let mut oct_string = packed_as::(&oct_codes, super::oct); + oct_string.clear(); + assert_eq!(oct_string.char_len(), 0); + assert_eq!(oct_string.bits().bit_len(), 0); + assert!(oct_string.bits().words().is_empty()); + oct_string.push(Oct::V7); + assert_eq!(oct_string.to_vec(), vec![Oct::V7]); + assert_eq!(oct_string.bits().bit_len(), 3); + + let wide_codes: Vec<_> = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let mut wide_string = packed_as::(&wide_codes, super::wide); + wide_string.clear(); + wide_string.push(WideCode(127)); + assert_eq!(wide_string.to_vec(), vec![WideCode(127)]); + assert_eq!(wide_string.bits().bit_len(), 7); +} + +#[test] +fn packed_extend_copies_borrowed_codes_in_order() { + let mut oct_string = packed_as::(&[Oct::V7.code()], super::oct); + let mut oct_source: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let mut oct_expected = oct_string.to_vec(); + oct_expected.extend(oct_source.iter().copied()); + oct_string.extend(oct_source.iter()); + assert_eq!(oct_string.to_vec(), oct_expected); + assert_eq!(oct_string.char_len(), oct_expected.len()); + assert_eq!(oct_string.bits().bit_len(), oct_expected.len() * 3); + + oct_source[0] = Oct::V0; + oct_source.clear(); + assert_eq!(oct_string.to_vec(), oct_expected); + + let mut wide_string = packed_as::(&[126], super::wide); + let mut wide_source: Vec<_> = (0..10) + .map(|index| { + if index == 9 { + WideCode(127) + } else { + WideCode((index * 11) as u8 % 128) + } + }) + .collect(); + let mut wide_expected = wide_string.to_vec(); + wide_expected.extend(wide_source.iter().copied()); + wide_string.extend(wide_source.iter()); + assert_eq!(wide_string.to_vec(), wide_expected); + assert_eq!(wide_string.bits().bit_len(), wide_expected.len() * 7); + + wide_source.clear(); + wide_string.extend(wide_source.iter()); + assert_eq!(wide_string.to_vec(), wide_expected); + + let mut bytes = PackedString::::new(); + let byte_source = [ + super::SparseByte::Zero, + super::SparseByte::Maximum, + super::SparseByte::Middle, + ]; + bytes.extend(byte_source.iter()); + assert_eq!(bytes.to_vec(), byte_source); + assert_eq!(bytes.bits().bit_len(), 24); +} + +#[test] +fn packed_extend_appends_owned_codes_without_changing_the_prefix() { + let mut oct_string = packed_as::(&[7; 20], super::oct); + let original = oct_string.to_vec(); + oct_string.extend(core::iter::empty::()); + assert_eq!(oct_string.to_vec(), original); + + let appended = [Oct::V0, Oct::V7, Oct::V3, Oct::V4, Oct::V1, Oct::V6]; + let mut expected = original.clone(); + expected.extend(appended); + oct_string.extend(appended); + assert_eq!(oct_string.to_vec(), expected); + assert_eq!(oct_string.char_len(), expected.len()); + assert_eq!(oct_string.bits().bit_len(), expected.len() * 3); + + let expected_bits = BitString::from_iter( + expected + .iter() + .flat_map(|character| (0..3).map(move |offset| character.code() & (1 << offset) != 0)), + ); + assert_eq!(oct_string.bits().words(), expected_bits.words()); + + let mut wide_string = PackedString::::new(); + let wide_values = [WideCode(0), WideCode(127), WideCode(64), WideCode(1)]; + wide_string.extend(wide_values); + wide_string.extend([WideCode(126), WideCode(2), WideCode(127)]); + assert_eq!( + wide_string.to_vec(), + vec![ + WideCode(0), + WideCode(127), + WideCode(64), + WideCode(1), + WideCode(126), + WideCode(2), + WideCode(127), + ] + ); + assert_eq!(wide_string.bits().bit_len(), 7 * 7); +} + +#[test] +fn packed_pop_returns_final_codes_and_shrinks_to_empty() { + let mut empty = PackedString::::new(); + assert_eq!(empty.pop(), None); + assert!(empty.bits().words().is_empty()); + + let mut maximum = + PackedString::::from_chars([super::SparseByte::Maximum]); + assert_eq!(maximum.pop(), Some(super::SparseByte::Maximum)); + assert!(maximum.is_empty()); + assert_eq!(maximum.pop(), None); + + let mut oct_codes: Vec<_> = (0..23) + .map(|index| if index == 22 { 0 } else { index as u8 % 8 }) + .map(super::oct) + .collect(); + let mut oct_string = PackedString::from_chars(oct_codes.clone()); + while let Some(expected) = oct_codes.pop() { + assert_eq!(oct_string.pop(), Some(expected)); + assert_eq!(oct_string.to_vec(), oct_codes); + assert_eq!(oct_string.char_len(), oct_codes.len()); + assert_eq!(oct_string.bits().bit_len(), oct_codes.len() * 3); + assert_eq!( + oct_string.bits().words().len(), + (oct_codes.len() * 3).div_ceil(64) + ); + assert_eq!(oct_string.last(), oct_codes.last().copied()); + } + assert_eq!(oct_string.pop(), None); + + let mut wide_codes: Vec<_> = (0..10) + .map(|index| { + if index == 9 { + 127 + } else { + (index * 11) as u8 % 128 + } + }) + .map(super::wide) + .collect(); + let mut wide_string = PackedString::from_chars(wide_codes.clone()); + while let Some(expected) = wide_codes.pop() { + assert_eq!(wide_string.pop(), Some(expected)); + assert_eq!(wide_string.last(), wide_codes.last().copied()); + assert_eq!(wide_string.bits().bit_len(), wide_codes.len() * 7); + } + assert!(wide_string.is_empty()); +} + +#[test] +fn packed_push_packed_string_concatenates_raw_payloads() { + let mut oct_left = packed_as::(&[7; 21], super::oct); + let oct_right_codes = [0, 7, 3]; + let oct_right = packed_as::(&oct_right_codes, super::oct); + let oct_right_snapshot = oct_right.clone(); + let mut oct_oracle = oct_left.to_vec(); + oct_oracle.extend(oct_right.to_vec()); + + oct_left.push_packed_string(&oct_right); + assert_eq!(oct_left.to_vec(), oct_oracle); + assert_eq!(oct_left.char_len(), 24); + assert_eq!(oct_left.bits().bit_len(), 24 * 3); + assert!(oct_right == oct_right_snapshot); + assert_eq!(oct_left.get(21), Some(Oct::V0)); + assert_eq!(oct_left.get(22), Some(Oct::V7)); + assert_eq!(oct_left.get(23), Some(Oct::V3)); + + let expected_bits = BitString::from_iter( + oct_oracle + .iter() + .flat_map(|character| (0..3).map(move |offset| character.code() & (1 << offset) != 0)), + ); + assert_eq!(oct_left.bits().words(), expected_bits.words()); + + let empty = PackedString::::new(); + let before_empty_append = oct_left.clone(); + oct_left.push_packed_string(&empty); + assert!(oct_left == before_empty_append); + + let original = packed_as::(&[0, 7, 3, 1], super::oct); + let mut duplicated = original.clone(); + let source = original.clone(); + duplicated.push_packed_string(&source); + assert_eq!( + duplicated.to_vec(), + [0, 7, 3, 1, 0, 7, 3, 1].map(super::oct) + ); + + let mut wide_left = packed_as::(&[126; 9], super::wide); + let wide_right = packed_as::(&[0, 127, 64], super::wide); + wide_left.push_packed_string(&wide_right); + assert_eq!(wide_left.char_len(), 12); + assert_eq!(wide_left.bits().bit_len(), 12 * 7); + assert_eq!(wide_left.get(9), Some(WideCode(0))); + assert_eq!(wide_left.get(10), Some(WideCode(127))); + assert_eq!(wide_left.get(11), Some(WideCode(64))); + + let mut bytes = PackedString::::new(); + let byte_right = + PackedString::from_chars([super::SparseByte::Zero, super::SparseByte::Maximum]); + bytes.push_packed_string(&byte_right); + assert_eq!( + bytes.to_vec(), + vec![super::SparseByte::Zero, super::SparseByte::Maximum] + ); +} diff --git a/tests/packed/tests_for_editing/tests_for_cross_word_boundaries.rs b/tests/packed/tests_for_editing/tests_for_cross_word_boundaries.rs new file mode 100644 index 0000000..ba30362 --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_cross_word_boundaries.rs @@ -0,0 +1,18 @@ +use super::{Oct, WideCode, assert_edits}; +use proptest::prelude::*; + +proptest! { +#[test] + fn three_and_seven_bit_edits_cross_word_boundaries( + initial3 in prop::collection::vec(0u8..=7, 22..=128), + replacement3 in prop::collection::vec(0u8..=7, 0..=64), + initial7 in prop::collection::vec(0u8..=127, 10..=128), + replacement7 in prop::collection::vec(0u8..=127, 0..=32), + insert_index in 0usize..160, + remove_index in 0usize..160, + replace_start in 0usize..160, + ) { + assert_edits::(&initial3, &replacement3, insert_index, remove_index, replace_start, 2, super::super::oct); + assert_edits::(&initial7, &replacement7, insert_index, remove_index, replace_start, 2, super::super::wide); + } +} diff --git a/tests/packed/tests_for_editing/tests_for_replacement.rs b/tests/packed/tests_for_editing/tests_for_replacement.rs new file mode 100644 index 0000000..97748d9 --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_replacement.rs @@ -0,0 +1,231 @@ +use super::*; + +#[test] +fn packed_replace_splices_characters_without_mutating_the_source() { + let original_codes: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let original = PackedString::from_chars(original_codes.clone()); + let replacement = PackedString::from_chars([Oct::V7, Oct::V0, Oct::V3]); + + let mut expected = original_codes.clone(); + expected.splice(10..13, [Oct::V7, Oct::V0, Oct::V3]); + let replaced = original.replace(10, &replacement); + assert_eq!(replaced.to_vec(), expected); + assert_eq!(replaced.bits().bit_len(), expected.len() * 3); + assert_eq!(original.to_vec(), original_codes); + + let mut appended = original_codes.clone(); + appended.extend([Oct::V7, Oct::V0, Oct::V3]); + assert_eq!( + original.replace(usize::MAX, &replacement).to_vec(), + appended + ); + + let empty_replacement = PackedString::::new(); + assert!(original.replace(5, &empty_replacement) == original); + + let wide_original = PackedString::from_chars([ + WideCode(0), + WideCode(11), + WideCode(22), + WideCode(33), + WideCode(44), + WideCode(55), + WideCode(66), + WideCode(77), + WideCode(88), + ]); + let wide_replacement = PackedString::from_chars([WideCode(127), WideCode(1), WideCode(64)]); + let wide_result = wide_original.replace(8, &wide_replacement); + assert_eq!( + wide_result.to_vec(), + vec![ + WideCode(0), + WideCode(11), + WideCode(22), + WideCode(33), + WideCode(44), + WideCode(55), + WideCode(66), + WideCode(77), + WideCode(127), + WideCode(1), + WideCode(64), + ] + ); + assert_eq!(wide_result.bits().bit_len(), 11 * 7); +} + +#[test] +fn packed_replace_assign_matches_replace_in_place() { + let original = packed_as::(&[0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3], super::oct); + let replacement = packed_as::( + &[Oct::V7.code(), Oct::V0.code(), Oct::V3.code()], + super::oct, + ); + + let expected = original.replace(8, &replacement); + let mut assigned = original.clone(); + assigned.replace_assign(8, &replacement); + assert!(assigned == expected); + assert_eq!(assigned.bits().words(), expected.bits().words()); + + let mut appended = original.clone(); + appended.replace_assign(usize::MAX, &replacement); + assert_eq!(appended.to_vec(), { + let mut values = original.to_vec(); + values.extend(replacement.to_vec()); + values + }); + + let empty = PackedString::::new(); + let before = assigned.clone(); + assigned.replace_assign(4, &empty); + assert!(assigned == before); + + let wide = packed_as::(&[0; 9], super::wide); + let wide_replacement = packed_as::(&[127, 64, 1], super::wide); + let wide_expected = wide.replace(8, &wide_replacement); + let mut wide_assigned = wide.clone(); + wide_assigned.replace_assign(8, &wide_replacement); + assert!(wide_assigned == wide_expected); + assert_eq!(wide_assigned.bits().bit_len(), 11 * 7); +} + +#[test] +fn packed_replace_interval_splices_clamped_character_ranges() { + let original_codes: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let original = PackedString::from_chars(original_codes.clone()); + let replacement = PackedString::from_chars([Oct::V7, Oct::V0, Oct::V3]); + + let interval = UsizeCO::checked_from_start_len(9, 5).unwrap(); + let mut expected = original_codes.clone(); + expected.splice(9..14, replacement.to_vec()); + let result = original.replace_interval(interval, &replacement); + assert_eq!(result.to_vec(), expected); + assert_eq!(result.bits().bit_len(), expected.len() * 3); + assert_eq!(original.to_vec(), original_codes); + + let tail_interval = UsizeCO::checked_from_start_len(21, 2).unwrap(); + let tail_result = original.replace_interval(tail_interval, &replacement); + let mut tail_expected = original_codes.clone(); + tail_expected.splice(21..22, replacement.to_vec()); + assert_eq!(tail_result.to_vec(), tail_expected); + assert_eq!(tail_result.bits().bit_len(), tail_expected.len() * 3); + + let out_of_bounds = UsizeCO::checked_from_start_len(99, 4).unwrap(); + let out_result = original.replace_interval(out_of_bounds, &replacement); + let mut out_expected = original_codes.clone(); + out_expected.extend(replacement.to_vec()); + assert_eq!(out_result.to_vec(), out_expected); + + let empty = PackedString::::new(); + let remove_result = + original.replace_interval(UsizeCO::checked_from_start_len(20, 2).unwrap(), &empty); + assert_eq!(remove_result.to_vec(), original_codes[..20].to_vec()); + + let wide_original = PackedString::from_chars([ + WideCode(0), + WideCode(11), + WideCode(22), + WideCode(33), + WideCode(44), + WideCode(55), + WideCode(66), + WideCode(77), + WideCode(88), + WideCode(99), + ]); + let wide_replacement = PackedString::from_chars([WideCode(127), WideCode(1)]); + let wide_result = wide_original.replace_interval( + UsizeCO::checked_from_start_len(8, 2).unwrap(), + &wide_replacement, + ); + assert_eq!( + wide_result.to_vec(), + vec![ + WideCode(0), + WideCode(11), + WideCode(22), + WideCode(33), + WideCode(44), + WideCode(55), + WideCode(66), + WideCode(77), + WideCode(127), + WideCode(1), + ] + ); + assert_eq!(wide_result.bits().bit_len(), 10 * 7); +} + +#[test] +fn packed_replace_interval_assign_matches_vec_splice_semantics() { + let original_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let replacement_codes = vec![7, 0, 3]; + let interval = UsizeCO::checked_from_start_len(9, 5).unwrap(); + let replacement = packed_as::(&replacement_codes, super::oct); + let mut string = packed_as::(&original_codes, super::oct); + let mut oracle = original_codes.clone(); + + string.replace_interval_assign(interval, &replacement); + oracle.splice(9..14, replacement_codes.iter().copied()); + assert_eq!( + string + .to_vec() + .iter() + .map(|value| value.code()) + .collect::>(), + oracle + ); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + + let empty = PackedString::::new(); + string.replace_interval_assign(UsizeCO::checked_from_start_len(4, 2).unwrap(), &empty); + oracle.splice(4..6, core::iter::empty()); + assert_eq!( + string + .to_vec() + .iter() + .map(|value| value.code()) + .collect::>(), + oracle + ); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + + string.replace_interval_assign( + UsizeCO::checked_from_start_len(99, 4).unwrap(), + &replacement, + ); + oracle.extend(replacement_codes.iter().copied()); + assert_eq!( + string + .to_vec() + .iter() + .map(|value| value.code()) + .collect::>(), + oracle + ); + + let wide_replacement = packed_as::(&[127, 1], super::wide); + let mut wide = packed_as::(&[0; 10], super::wide); + wide.replace_interval_assign( + UsizeCO::checked_from_start_len(8, 2).unwrap(), + &wide_replacement, + ); + assert_eq!( + wide.to_vec(), + vec![ + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(127), + WideCode(1), + ] + ); + assert_eq!(wide.bits().bit_len(), 10 * 7); +} diff --git a/tests/packed/tests_for_editing/tests_for_sequence_operations.rs b/tests/packed/tests_for_editing/tests_for_sequence_operations.rs new file mode 100644 index 0000000..f2496e0 --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_sequence_operations.rs @@ -0,0 +1,248 @@ +use super::*; + +#[test] +fn packed_reverse_reverses_characters_without_mutating_source() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let string = PackedString::from_chars(original.clone()); + let reversed = string.reverse(); + + let mut expected = original.clone(); + expected.reverse(); + assert_eq!(reversed.to_vec(), expected); + assert_eq!(string.to_vec(), original); + assert_eq!(reversed.char_len(), expected.len()); + assert_eq!(reversed.bits().bit_len(), expected.len() * 3); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 17) as u8 % 128)) + .collect(); + let wide = PackedString::from_chars(wide_values.clone()); + let wide_reversed = wide.reverse(); + let mut wide_expected = wide_values.clone(); + wide_expected.reverse(); + assert_eq!(wide_reversed.to_vec(), wide_expected); + assert_eq!(wide.to_vec(), wide_values); + assert_eq!(wide_reversed.bits().bit_len(), wide_expected.len() * 7); + + assert!(PackedString::::new().reverse().is_empty()); + assert_eq!( + PackedString::from_chars([Oct::V6]).reverse().to_vec(), + vec![Oct::V6] + ); +} + +#[test] +fn packed_reverse_assign_matches_reverse_and_preserves_packed_invariants() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let expected = PackedString::from_chars(original.clone()).reverse(); + let mut string = PackedString::from_chars(original); + string.reverse_assign(); + + assert_eq!(string.to_vec(), expected.to_vec()); + assert_eq!(string.bits().words(), expected.bits().words()); + assert_eq!(string.char_len(), expected.char_len()); + assert_eq!(string.bits().bit_len(), string.char_len() * 3); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 17) as u8 % 128)) + .collect(); + let wide_expected = PackedString::from_chars(wide_values.clone()).reverse(); + let mut wide = PackedString::from_chars(wide_values); + wide.reverse_assign(); + assert_eq!(wide.to_vec(), wide_expected.to_vec()); + assert_eq!(wide.bits().bit_len(), wide.char_len() * 7); + + let mut empty = PackedString::::new(); + empty.reverse_assign(); + assert!(empty.is_empty()); +} + +#[test] +fn packed_truncate_keeps_the_prefix_and_clamps_at_current_length() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let mut string = PackedString::from_chars(original.clone()); + let original_bits = string.bits().clone(); + + string.truncate(21); + assert_eq!(string.to_vec(), original[..21].to_vec()); + assert_eq!(string.char_len(), 21); + assert_eq!(string.bits().bit_len(), 21 * 3); + + let truncated_bits = string.bits().clone(); + string.truncate(usize::MAX); + assert_eq!(string.bits().words(), truncated_bits.words()); + assert_eq!(string.to_vec(), original[..21].to_vec()); + + string.truncate(0); + assert!(string.is_empty()); + assert!(string.bits().words().is_empty()); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 17) as u8 % 128)) + .collect(); + let mut wide = PackedString::from_chars(wide_values.clone()); + wide.truncate(9); + assert_eq!(wide.to_vec(), wide_values[..9].to_vec()); + assert_eq!(wide.bits().bit_len(), 9 * 7); + + let mut unchanged = PackedString::from_chars(original); + unchanged.truncate(22); + assert_eq!(unchanged.bits().words(), original_bits.words()); +} + +#[test] +fn packed_push_appends_one_aligned_code_at_a_time() { + let mut oct_string = PackedString::::new(); + let mut oracle = Vec::new(); + let values: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + + for value in values.iter().copied() { + let old_prefix = oct_string.to_vec(); + let old_bit_len = oct_string.bits().bit_len(); + oct_string.push(value); + oracle.push(value); + assert_eq!(oct_string.to_vec(), oracle); + assert_eq!(oct_string.bits().bit_len(), old_bit_len + 3); + assert_eq!(oct_string.to_vec()[..old_prefix.len()], old_prefix); + } + assert_eq!(oct_string.get(21), Some(Oct::V5)); + assert_eq!(oct_string.char_len(), 22); + + oct_string.push(Oct::V0); + oracle.push(Oct::V0); + assert_eq!(oct_string.to_vec(), oracle); + assert_eq!(oct_string.bits().bit_len(), 23 * 3); + assert_eq!(oct_string.get(22), Some(Oct::V0)); + assert!(!oct_string.is_empty()); + + let mut binary = PackedString::::new(); + binary.push(PackedSymbol::One); + binary.push(PackedSymbol::Zero); + assert_eq!(binary.to_vec(), vec![PackedSymbol::One, PackedSymbol::Zero]); + assert_eq!(binary.bits().bit_len(), 2); + + let mut bytes = PackedString::::new(); + bytes.push(super::SparseByte::Zero); + bytes.push(super::SparseByte::Maximum); + assert_eq!( + bytes.to_vec(), + vec![super::SparseByte::Zero, super::SparseByte::Maximum] + ); + assert_eq!(bytes.bits().bit_len(), 16); +} + +#[test] +fn packed_views_and_editing_remain_character_aligned() { + let mut string = PackedString::::from_chars([ + super::Symbol::Zero, + super::Symbol::One, + super::Symbol::Two, + super::Symbol::One, + ]); + + let view = string.as_packed_str(); + assert_eq!(view.char_len(), 4); + assert_eq!( + view.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()) + .get(0), + Some(super::Symbol::One) + ); + assert_eq!(view.find(view.slice_until(1)), Some(0)); + assert!(view.contains(view.slice(UsizeCO::checked_from_start_len(1, 1).unwrap()))); + + let haystack_string = PackedString::::from_chars([ + super::Symbol::Zero, + super::Symbol::Zero, + super::Symbol::One, + ]); + let needle_string = PackedString::::from_chars([super::Symbol::Two]); + let haystack = haystack_string.as_packed_str(); + let needle = needle_string.as_packed_str(); + assert!(!haystack.contains(needle)); + assert_eq!(haystack.find(needle), None); + assert_eq!(haystack.rfind(needle), None); + assert!(!haystack.matches_at(1, needle)); + + string.insert(2, super::Symbol::Zero); + assert_eq!(string.remove(2), super::Symbol::Zero); + assert_eq!( + string.reverse().to_vec(), + vec![ + super::Symbol::One, + super::Symbol::Two, + super::Symbol::One, + super::Symbol::Zero + ] + ); + string.retain(|symbol| symbol != super::Symbol::Two); + assert_eq!( + string.to_vec(), + vec![super::Symbol::Zero, super::Symbol::One, super::Symbol::One] + ); +} + +#[test] +fn packed_string_set_replaces_only_one_character_slot() { + let mut string = packed(&[0, 1, 2, 1, 0]); + let original_bits = string.bits().clone(); + assert_eq!(string.set(0, super::Symbol::Two), Some(super::Symbol::Zero)); + assert_eq!(string.get(0), Some(super::Symbol::Two)); + assert_eq!(string.get(1), Some(super::Symbol::One)); + assert_eq!(string.get(4), Some(super::Symbol::Zero)); + assert_eq!(string.char_len(), 5); + assert_eq!(string.bits().bit_len(), original_bits.bit_len()); + + assert_eq!( + string.set(4, super::Symbol::Zero), + Some(super::Symbol::Zero) + ); + assert_eq!(string.set(99, super::Symbol::One), None); + assert_eq!(string.get(0), Some(super::Symbol::Two)); + assert_eq!(string.get(4), Some(super::Symbol::Zero)); + + let mut binary = packed_as::(&[0, 1, 0], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary.set(1, super::PackedSymbol::Zero), + Some(super::PackedSymbol::One) + ); + assert_eq!(binary.to_vec(), vec![super::PackedSymbol::Zero; 3]); + assert_eq!(binary.bits().bit_len(), 3); + + let mut bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!( + bytes.set(1, super::SparseByte::Zero), + Some(super::SparseByte::Maximum) + ); + assert_eq!( + bytes.to_vec(), + vec![ + super::SparseByte::Zero, + super::SparseByte::Zero, + super::SparseByte::Middle, + ] + ); + assert_eq!(bytes.bits().bit_len(), 24); + + let oct_codes: Vec = (0..22).map(|index| index as u8 % 8).collect(); + let mut oct_string = packed_as::(&oct_codes, super::oct); + assert_eq!(oct_string.set(21, Oct::V7), Some(Oct::V5)); + assert_eq!(oct_string.get(20), Some(Oct::V4)); + assert_eq!(oct_string.get(21), Some(Oct::V7)); + assert_eq!(oct_string.bits().bit_len(), 66); + + let wide_codes: Vec = (0..10).map(|index| (index * 11) as u8 % 128).collect(); + let mut wide_string = packed_as::(&wide_codes, super::wide); + assert_eq!(wide_string.set(9, WideCode(127)), Some(WideCode(99))); + assert_eq!(wide_string.get(8), Some(WideCode(88))); + assert_eq!(wide_string.get(9), Some(WideCode(127))); + assert_eq!(wide_string.bits().bit_len(), 70); +} diff --git a/tests/packed/tests_for_editing/tests_for_slicing.rs b/tests/packed/tests_for_editing/tests_for_slicing.rs new file mode 100644 index 0000000..93ee251 --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_slicing.rs @@ -0,0 +1,108 @@ +use super::*; + +#[test] +fn packed_slice_returns_a_clamped_character_aligned_owner() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let string = PackedString::from_chars(original.clone()); + + let sliced = string.slice(UsizeCO::checked_from_start_len(9, 5).unwrap()); + assert_eq!(sliced.to_vec(), original[9..14].to_vec()); + assert_eq!(sliced.char_len(), 5); + assert_eq!(sliced.bits().bit_len(), 5 * 3); + assert_eq!(string.to_vec(), original); + + let tail = string.slice(UsizeCO::checked_from_start_len(20, 9).unwrap()); + assert_eq!(tail.to_vec(), original[20..].to_vec()); + assert_eq!(tail.bits().bit_len(), 2 * 3); + + let empty = string.slice(UsizeCO::checked_from_start_len(99, 4).unwrap()); + assert!(empty.is_empty()); + assert!(empty.bits().words().is_empty()); + + let wide_values: Vec<_> = (0..16) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let wide = PackedString::from_chars(wide_values.clone()); + let wide_sliced = wide.slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + assert_eq!(wide_sliced.to_vec(), wide_values[8..13].to_vec()); + assert_eq!(wide_sliced.bits().bit_len(), 5 * 7); +} + +#[test] +fn packed_slice_from_returns_the_clamped_suffix() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let string = PackedString::from_chars(original.clone()); + + let suffix = string.slice_from(9); + assert_eq!(suffix.to_vec(), original[9..].to_vec()); + assert_eq!(suffix.bits().bit_len(), (original.len() - 9) * 3); + assert_eq!(string.to_vec(), original); + + assert_eq!(string.slice_from(0).to_vec(), original); + let empty = string.slice_from(usize::MAX); + assert!(empty.is_empty()); + assert!(empty.bits().words().is_empty()); + + let wide_values: Vec<_> = (0..16) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let wide = PackedString::from_chars(wide_values.clone()); + let wide_suffix = wide.slice_from(8); + assert_eq!(wide_suffix.to_vec(), wide_values[8..].to_vec()); + assert_eq!(wide_suffix.bits().bit_len(), 8 * 7); +} + +#[test] +fn packed_slice_until_returns_the_clamped_prefix() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let string = PackedString::from_chars(original.clone()); + + let prefix = string.slice_until(9); + assert_eq!(prefix.to_vec(), original[..9].to_vec()); + assert_eq!(prefix.bits().bit_len(), 9 * 3); + assert_eq!(string.to_vec(), original); + + assert!(string.slice_until(0).is_empty()); + assert_eq!(string.slice_until(usize::MAX).to_vec(), original); + + let wide_values: Vec<_> = (0..16) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let wide = PackedString::from_chars(wide_values.clone()); + let wide_prefix = wide.slice_until(8); + assert_eq!(wide_prefix.to_vec(), wide_values[..8].to_vec()); + assert_eq!(wide_prefix.bits().bit_len(), 8 * 7); +} + +#[test] +fn packed_split_off_returns_a_character_aligned_suffix() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let mut string = PackedString::from_chars(original.clone()); + let suffix = string.split_off(21); + assert_eq!(string.to_vec(), original[..21].to_vec()); + assert_eq!(suffix.to_vec(), original[21..].to_vec()); + assert_eq!(string.bits().bit_len(), 21 * 3); + assert_eq!(suffix.bits().bit_len(), 3); + + let mut at_end = PackedString::from_chars(original.clone()); + let empty = at_end.split_off(usize::MAX); + assert_eq!(at_end.to_vec(), original); + assert!(empty.is_empty()); + assert!(empty.bits().words().is_empty()); + + let mut at_start = PackedString::from_chars(original.clone()); + let all = at_start.split_off(0); + assert!(at_start.is_empty()); + assert_eq!(all.to_vec(), original); + assert_eq!(all.bits().bit_len(), 22 * 3); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let mut wide = PackedString::from_chars(wide_values.clone()); + let wide_suffix = wide.split_off(8); + assert_eq!(wide.to_vec(), wide_values[..8].to_vec()); + assert_eq!(wide_suffix.to_vec(), wide_values[8..].to_vec()); + assert_eq!(wide.bits().bit_len(), 8 * 7); + assert_eq!(wide_suffix.bits().bit_len(), 2 * 7); +} diff --git a/tests/packed/tests_for_editing/tests_for_vec_edit_semantics.rs b/tests/packed/tests_for_editing/tests_for_vec_edit_semantics.rs new file mode 100644 index 0000000..f561ed2 --- /dev/null +++ b/tests/packed/tests_for_editing/tests_for_vec_edit_semantics.rs @@ -0,0 +1,312 @@ +use super::super::symbol; +use super::*; +use proptest::prelude::*; + +#[test] +fn packed_remove_returns_deleted_code_and_preserves_the_rest() { + let mut oct_codes: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let mut oct_string = PackedString::from_chars(oct_codes.clone()); + for index in [0, 10, 19] { + let expected = oct_codes.remove(index); + assert_eq!(oct_string.remove(index), expected); + assert_eq!(oct_string.to_vec(), oct_codes); + assert_eq!(oct_string.char_len(), oct_codes.len()); + assert_eq!(oct_string.bits().bit_len(), oct_codes.len() * 3); + } + + let mut wide_codes: Vec<_> = (0..10) + .map(|index| { + if index == 9 { + WideCode(127) + } else { + WideCode((index * 11) as u8 % 128) + } + }) + .collect(); + let mut wide_string = PackedString::from_chars(wide_codes.clone()); + assert_eq!(wide_string.remove(9), WideCode(127)); + wide_codes.pop(); + assert_eq!(wide_string.to_vec(), wide_codes); + assert_eq!(wide_string.bits().bit_len(), wide_codes.len() * 7); + + let mut bytes = PackedString::from_chars([ + super::SparseByte::Zero, + super::SparseByte::Maximum, + super::SparseByte::Middle, + ]); + assert_eq!(bytes.remove(1), super::SparseByte::Maximum); + assert_eq!( + bytes.to_vec(), + vec![super::SparseByte::Zero, super::SparseByte::Middle] + ); + + let mut empty = PackedString::::new(); + assert!(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| empty.remove(0))).is_err()); + let mut singleton = PackedString::from_chars([Oct::V0]); + assert!( + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| singleton.remove(1))).is_err() + ); + assert_eq!(singleton.to_vec(), vec![Oct::V0]); +} + +#[test] +fn packed_retain_matches_vec_order_and_predicate_calls() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let mut string = PackedString::from_chars(original.clone()); + let mut seen = Vec::new(); + string.retain(|character| { + seen.push(character); + character.code() % 2 == 0 + }); + + let mut oracle = original.clone(); + oracle.retain(|character| character.code() % 2 == 0); + assert_eq!(string.to_vec(), oracle); + assert_eq!(seen, original); + assert_eq!(string.char_len(), oracle.len()); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + + let mut all = PackedString::from_chars([Oct::V7; 22]); + all.retain(|_| true); + assert_eq!(all.to_vec(), vec![Oct::V7; 22]); + + let mut none = PackedString::from_chars([Oct::V0; 22]); + none.retain(|_| false); + assert!(none.is_empty()); + assert!(none.bits().words().is_empty()); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 11) as u8 % 128)) + .collect(); + let mut wide = PackedString::from_chars(wide_values.clone()); + wide.retain(|character| character.code() >= 64); + let expected_wide: Vec<_> = wide_values + .into_iter() + .filter(|character| character.code() >= 64) + .collect(); + assert_eq!(wide.to_vec(), expected_wide); + assert_eq!(wide.bits().bit_len(), expected_wide.len() * 7); + + let mut bytes = PackedString::from_chars([ + super::SparseByte::Zero, + super::SparseByte::Maximum, + super::SparseByte::Middle, + ]); + bytes.retain(|character| character.code() != 3); + assert_eq!( + bytes.to_vec(), + vec![super::SparseByte::Zero, super::SparseByte::Maximum] + ); +} + +#[test] +fn packed_insert_matches_vec_insert_and_clamps_out_of_bounds_indices() { + let mut string = PackedString::from_chars((0..22).map(|index| super::oct(index as u8 % 8))); + let mut oracle = string.to_vec(); + + for (index, character) in [(0, Oct::V7), (10, Oct::V0), (usize::MAX, Oct::V5)] { + let insertion_index = index.min(oracle.len()); + string.insert(index, character); + oracle.insert(insertion_index, character); + assert_eq!(string.to_vec(), oracle); + assert_eq!(string.char_len(), oracle.len()); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + } + + let mut wide = + PackedString::from_chars((0..10).map(|index| WideCode((index * 13) as u8 % 128))); + let mut wide_oracle = wide.to_vec(); + let insertion_index = 9; + wide.insert(insertion_index, WideCode(127)); + wide_oracle.insert(insertion_index, WideCode(127)); + assert_eq!(wide.to_vec(), wide_oracle); + assert_eq!(wide.bits().bit_len(), wide_oracle.len() * 7); + + let mut empty = PackedString::::new(); + empty.insert(usize::MAX, Oct::V0); + assert_eq!(empty.to_vec(), vec![Oct::V0]); + assert_eq!(empty.bits().bit_len(), 3); +} + +#[test] +fn packed_insert_packed_string_splices_codes_and_preserves_source() { + let original_codes: Vec<_> = (0..22).map(|index| index as u8 % 8).collect(); + let replacement_codes = vec![7, 0, 3]; + let mut string = packed_as::(&original_codes, super::oct); + let replacement = packed_as::(&replacement_codes, super::oct); + let mut oracle = original_codes.clone(); + + string.insert_packed_string(9, &replacement); + oracle.splice(9..9, replacement_codes.iter().copied()); + assert_eq!( + string + .to_vec() + .iter() + .map(|value| value.code()) + .collect::>(), + oracle + ); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + assert_eq!( + replacement.to_vec(), + replacement_codes + .iter() + .map(|&code| super::oct(code)) + .collect::>() + ); + + string.insert_packed_string(usize::MAX, &replacement); + oracle.extend(replacement_codes.iter().copied()); + assert_eq!( + string + .to_vec() + .iter() + .map(|value| value.code()) + .collect::>(), + oracle + ); + assert_eq!(string.bits().bit_len(), oracle.len() * 3); + + let empty = PackedString::::new(); + let before = string.clone(); + string.insert_packed_string(4, &empty); + assert_eq!(string, before); + + let wide = packed_as::(&[1, 64, 127], super::wide); + let mut wide_target = packed_as::(&[0; 10], super::wide); + wide_target.insert_packed_string(8, &wide); + assert_eq!( + wide_target.to_vec(), + vec![ + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(0), + WideCode(1), + WideCode(64), + WideCode(127), + WideCode(0), + WideCode(0), + ] + ); + assert_eq!(wide_target.bits().bit_len(), 13 * 7); +} + +#[test] +fn packed_drain_interval_removes_clamped_character_ranges_without_mutating_source() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let string = PackedString::from_chars(original.clone()); + + let interval = UsizeCO::checked_from_start_len(9, 5).unwrap(); + let drained = string.drain_interval(interval); + let mut expected = original.clone(); + expected.drain(9..14); + assert_eq!(drained.to_vec(), expected); + assert_eq!(drained.char_len(), expected.len()); + assert_eq!(drained.bits().bit_len(), expected.len() * 3); + assert_eq!(string.to_vec(), original); + + let tail = string.drain_interval(UsizeCO::checked_from_start_len(20, 9).unwrap()); + assert_eq!(tail.to_vec(), original[..20].to_vec()); + assert_eq!(tail.bits().bit_len(), 20 * 3); + + let out_of_bounds = string.drain_interval(UsizeCO::checked_from_start_len(99, 4).unwrap()); + assert_eq!(out_of_bounds.to_vec(), original); + assert_eq!(out_of_bounds.bits().words(), string.bits().words()); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let wide = PackedString::from_chars(wide_values.clone()); + let wide_drained = wide.drain_interval(UsizeCO::checked_from_start_len(7, 2).unwrap()); + let mut wide_expected = wide_values.clone(); + wide_expected.drain(7..9); + assert_eq!(wide_drained.to_vec(), wide_expected); + assert_eq!(wide_drained.bits().bit_len(), wide_expected.len() * 7); +} + +#[test] +fn packed_drain_interval_assign_matches_non_mutating_drain() { + let original: Vec<_> = (0..22).map(|index| super::oct(index as u8 % 8)).collect(); + let interval = UsizeCO::checked_from_start_len(9, 5).unwrap(); + let expected = PackedString::from_chars(original.clone()).drain_interval(interval); + let mut assigned = PackedString::from_chars(original); + assigned.drain_interval_assign(interval); + assert_eq!(assigned.to_vec(), expected.to_vec()); + assert_eq!(assigned.bits().words(), expected.bits().words()); + assert_eq!(assigned.bits().bit_len(), assigned.char_len() * 3); + + let mut unchanged = assigned.clone(); + let before = unchanged.bits().clone(); + unchanged.drain_interval_assign(UsizeCO::checked_from_start_len(99, 4).unwrap()); + assert_eq!(unchanged.bits().words(), before.words()); + assert_eq!(unchanged.to_vec(), assigned.to_vec()); + + let mut empty = PackedString::::new(); + empty.drain_interval_assign(UsizeCO::checked_from_start_len(0, 1).unwrap()); + assert!(empty.is_empty()); + + let wide_values: Vec<_> = (0..10) + .map(|index| WideCode((index * 13) as u8 % 128)) + .collect(); + let wide_interval = UsizeCO::checked_from_start_len(7, 2).unwrap(); + let wide_expected = PackedString::from_chars(wide_values.clone()).drain_interval(wide_interval); + let mut wide = PackedString::from_chars(wide_values); + wide.drain_interval_assign(wide_interval); + assert_eq!(wide.to_vec(), wide_expected.to_vec()); + assert_eq!(wide.bits().bit_len(), wide.char_len() * 7); +} + +proptest! { +#[test] + fn packed_edits_match_vec_splice_semantics( + initial in prop::collection::vec(0u8..=2, 0..=24), + replacement in prop::collection::vec(0u8..=2, 0..=12), + insert_index in 0usize..32, + remove_index in 0usize..32, + replace_start in 0usize..32, + ) { + let mut string = packed(&initial); + let mut oracle = initial.clone(); + + let oracle_insert_index = insert_index.min(oracle.len()); + let inserted = symbol(2); + string.insert(insert_index, inserted); + oracle.insert(oracle_insert_index, 2); + + if !oracle.is_empty() { + let remove_index = remove_index.min(oracle.len() - 1); + prop_assert_eq!(string.remove(remove_index).code(), oracle.remove(remove_index)); + } + + let oracle_replace_start = replace_start.min(oracle.len()); + let replace_end = oracle_replace_start + .saturating_add(replacement.len()) + .min(oracle.len()); + string.replace_assign(replace_start, &packed(&replacement)); + oracle.splice(oracle_replace_start..replace_end, replacement.iter().copied()); + prop_assert_eq!( + string.to_vec().iter().copied().map(|value| value.code()).collect::>(), + oracle.clone(), + ); + + let drain_len = insert_index.max(1); + let oracle_drain_start = remove_index.min(oracle.len()); + let oracle_drain_end = remove_index + .saturating_add(drain_len) + .min(oracle.len()) + .max(oracle_drain_start); + string.drain_interval_assign( + UsizeCO::checked_from_start_len(remove_index, drain_len).unwrap(), + ); + oracle.drain(oracle_drain_start..oracle_drain_end); + prop_assert_eq!( + string.to_vec().iter().copied().map(|value| value.code()).collect::>(), + oracle.clone(), + ); + } +} diff --git a/tests/packed/tests_for_eq.rs b/tests/packed/tests_for_eq.rs new file mode 100644 index 0000000..ed2deaf --- /dev/null +++ b/tests/packed/tests_for_eq.rs @@ -0,0 +1,87 @@ +use super::{Oct, PackedSymbol, WideCode, oct, packed_as, wide}; +use bit_string::PackedString; +use core::hash::{Hash, Hasher}; +use int_intervals::UsizeCO; + +fn hash_of(value: &T) -> u64 { + let mut hasher = std::collections::hash_map::DefaultHasher::new(); + value.hash(&mut hasher); + hasher.finish() +} + +#[test] +fn packed_str_equality_compares_viewed_bits_not_source_identity() { + let empty_left = PackedString::::new(); + let empty_right = PackedString::::new(); + assert!(empty_left.as_packed_str() == empty_right.as_packed_str()); + + let binary_left = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + let binary_right = packed_as::(&[0, 1], |code| match code { + 0 => PackedSymbol::Zero, + 1 => PackedSymbol::One, + _ => unreachable!(), + }); + assert!(binary_left.as_packed_str() == binary_right.as_packed_str()); + assert!(binary_left.as_packed_str() != binary_left.as_packed_str().slice_from(1)); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_equal_owner = packed_as::(&oct_codes, oct); + let oct_view = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_equal_view = oct_equal_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert!(oct_view == oct_equal_view); + assert!(oct_view == oct_view.clone()); + assert!(oct_owner.as_packed_str().slice_until(0) == oct_owner.as_packed_str().slice_from(24)); + assert!( + oct_view + != oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 3).unwrap()) + ); + + let mut oct_different_codes = oct_codes.clone(); + oct_different_codes[21] = 6; + let oct_different_owner = packed_as::(&oct_different_codes, oct); + let oct_different_view = oct_different_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert!(oct_view != oct_different_view); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_equal_owner = packed_as::(&wide_codes, wide); + let wide_view = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + let wide_equal_view = wide_equal_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + assert!(wide_view == wide_equal_view); + + let mut wide_different_codes = wide_codes.clone(); + wide_different_codes[9] ^= 1; + let wide_different_owner = packed_as::(&wide_different_codes, wide); + let wide_different_view = wide_different_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + assert!(wide_view != wide_different_view); +} + +#[test] +fn packed_string_hash_matches_equal_values_and_owned_bits() { + let codes = [0, 7, 3, 1, 6, 2, 5, 4]; + let left = PackedString::from_chars(codes.iter().copied().map(oct)); + let right = packed_as::(&codes, oct); + + assert_eq!(left, right); + assert_eq!(hash_of(&left), hash_of(&right)); + assert_eq!(hash_of(&left), hash_of(left.bits())); +} diff --git a/tests/packed/tests_for_iter.rs b/tests/packed/tests_for_iter.rs new file mode 100644 index 0000000..20eb0f1 --- /dev/null +++ b/tests/packed/tests_for_iter.rs @@ -0,0 +1,220 @@ +use super::{Oct, PackedSymbol, SparseByte, WideCode, oct, packed_as, wide}; +use bit_string::{PackedStr, PackedString, traits::PackedChar}; +use int_intervals::UsizeCO; + +fn assert_iterator(view: PackedStr<'_, C, BITS>, expected: &[C]) +where + C: PackedChar + core::fmt::Debug, +{ + let mut iterator = view.iter(); + assert_eq!(iterator.len(), expected.len()); + assert_eq!(iterator.size_hint(), (expected.len(), Some(expected.len()))); + + let mut front = 0; + let mut back = expected.len(); + while front < back { + if front % 2 == 0 { + assert_eq!(iterator.next(), Some(expected[front])); + front += 1; + } else { + back -= 1; + assert_eq!(iterator.next_back(), Some(expected[back])); + } + assert_eq!(iterator.len(), back - front); + assert_eq!(iterator.size_hint(), (back - front, Some(back - front))); + } + assert_eq!(iterator.next(), None); + assert_eq!(iterator.next_back(), None); +} + +#[test] +fn packed_str_iter_is_exact_double_ended_and_view_relative() { + let empty = PackedString::::new(); + assert_iterator(empty.as_packed_str(), &[]); + + let bytes = packed_as::(&[0, 255], |code| match code { + 0 => SparseByte::Zero, + 255 => SparseByte::Maximum, + _ => unreachable!(), + }); + assert_iterator( + bytes.as_packed_str(), + &[SparseByte::Zero, SparseByte::Maximum], + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_view = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_expected: Vec<_> = oct_codes[20..24].iter().copied().map(oct).collect(); + assert_iterator(oct_view, &oct_expected); + assert_eq!((&oct_view).into_iter().collect::>(), oct_expected); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_view = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + let wide_expected: Vec<_> = wide_codes[8..13].iter().copied().map(wide).collect(); + assert_iterator(wide_view, &wide_expected); + + let iterator_after_view_drop = { + let view = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 5).unwrap()); + view.iter() + }; + assert_eq!(iterator_after_view_drop.collect::>(), wide_expected); +} + +#[test] +fn packed_str_iter_cursor_state_meets_at_the_middle() { + let owner = packed_as::(&[0, 1, 2, 3, 4], oct); + let mut iterator = owner.as_packed_str().iter(); + + assert_eq!(iterator.len(), 5); + assert_eq!(iterator.next(), Some(Oct::V0)); + assert_eq!(iterator.len(), 4); + assert_eq!(iterator.next_back(), Some(Oct::V4)); + assert_eq!(iterator.len(), 3); + assert_eq!(iterator.next(), Some(Oct::V1)); + assert_eq!(iterator.next_back(), Some(Oct::V3)); + assert_eq!(iterator.len(), 1); + assert_eq!(iterator.next(), Some(Oct::V2)); + assert_eq!(iterator.len(), 0); + assert_eq!(iterator.next(), None); + assert_eq!(iterator.next_back(), None); + assert_eq!(iterator.len(), 0); +} + +#[test] +fn packed_str_iter_next_advances_one_front_cursor_and_fuses() { + let owner = packed_as::(&[0, 1, 2, 3, 4], oct); + let view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let mut iterator = view.iter(); + + assert_eq!(iterator.next(), Some(Oct::V1)); + assert_eq!(iterator.len(), 2); + assert_eq!(iterator.next(), Some(Oct::V2)); + assert_eq!(iterator.len(), 1); + assert_eq!(iterator.next(), Some(Oct::V3)); + assert_eq!(iterator.len(), 0); + assert_eq!(iterator.next(), None); + assert_eq!(iterator.next(), None); + assert_eq!(iterator.len(), 0); + + let empty = PackedString::::new(); + let mut empty_iterator = empty.as_packed_str().iter(); + assert_eq!(empty_iterator.next(), None); + assert_eq!(empty_iterator.len(), 0); +} + +#[test] +fn packed_str_iter_size_hint_tracks_exact_remaining_count() { + let owner = packed_as::(&[0, 1, 2, 3], oct); + let mut iterator = owner.as_packed_str().iter(); + + assert_eq!(iterator.size_hint(), (4, Some(4))); + assert_eq!(iterator.next(), Some(Oct::V0)); + assert_eq!(iterator.size_hint(), (3, Some(3))); + assert_eq!(iterator.next_back(), Some(Oct::V3)); + assert_eq!(iterator.size_hint(), (2, Some(2))); + assert_eq!(iterator.next(), Some(Oct::V1)); + assert_eq!(iterator.next_back(), Some(Oct::V2)); + assert_eq!(iterator.size_hint(), (0, Some(0))); + assert_eq!(iterator.next(), None); + assert_eq!(iterator.next_back(), None); + assert_eq!(iterator.size_hint(), (0, Some(0))); +} + +#[test] +fn packed_str_iter_next_back_decrements_from_the_exclusive_end() { + let owner = packed_as::(&[0, 1, 2, 3, 4], oct); + let view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let mut iterator = view.iter(); + + assert_eq!(iterator.next_back(), Some(Oct::V3)); + assert_eq!(iterator.len(), 2); + assert_eq!(iterator.next_back(), Some(Oct::V2)); + assert_eq!(iterator.len(), 1); + assert_eq!(iterator.next_back(), Some(Oct::V1)); + assert_eq!(iterator.len(), 0); + assert_eq!(iterator.next_back(), None); + assert_eq!(iterator.next_back(), None); + assert_eq!(iterator.len(), 0); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_view = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 2).unwrap()); + let mut oct_iterator = oct_view.iter(); + assert_eq!(oct_iterator.next_back(), Some(Oct::V5)); + assert_eq!(oct_iterator.next_back(), Some(Oct::V4)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_view = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 2).unwrap()); + assert_eq!(wide_view.iter().next_back(), Some(wide(wide_codes[9]))); + + let empty = PackedString::::new(); + assert_eq!(empty.as_packed_str().iter().next_back(), None); +} + +#[test] +fn packed_str_borrowed_into_iter_uses_the_view_range() { + let codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let owner = packed_as::(&codes, oct); + let view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let expected: Vec<_> = codes[20..24].iter().copied().map(oct).collect(); + + let explicit: Vec<_> = (&view).into_iter().collect(); + assert_eq!(explicit, expected); + + let mut from_for_loop = Vec::new(); + for value in &view { + from_for_loop.push(value); + } + assert_eq!(from_for_loop, expected); + + let mut iterator = (&view).into_iter(); + assert_eq!(iterator.len(), 4); + assert_eq!(iterator.next_back(), Some(Oct::V7)); + assert_eq!(iterator.next(), Some(Oct::V4)); + assert_eq!(iterator.len(), 2); + assert_eq!(iterator.collect::>(), vec![Oct::V5, Oct::V6]); +} + +#[test] +fn packed_string_borrowed_into_iter_uses_the_full_owner() { + let codes: Vec = (0..22).map(|index| index as u8 % 8).collect(); + let owner = packed_as::(&codes, oct); + let expected: Vec<_> = codes.iter().copied().map(oct).collect(); + + let mut iterator = (&owner).into_iter(); + assert_eq!(iterator.len(), expected.len()); + assert_eq!(iterator.size_hint(), (expected.len(), Some(expected.len()))); + assert_eq!(iterator.next(), Some(expected[0])); + assert_eq!(iterator.next_back(), Some(*expected.last().unwrap())); + assert_eq!( + iterator.collect::>(), + expected[1..expected.len() - 1] + ); + + let wide_codes: Vec = (0..10).map(|index| (index * 13) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_expected: Vec<_> = wide_codes.iter().copied().map(wide).collect(); + assert_eq!((&wide_owner).into_iter().collect::>(), wide_expected); + + let empty = PackedString::::new(); + assert_eq!((&empty).into_iter().next(), None); +} diff --git a/tests/packed/tests_for_matching.rs b/tests/packed/tests_for_matching.rs new file mode 100644 index 0000000..ba84358 --- /dev/null +++ b/tests/packed/tests_for_matching.rs @@ -0,0 +1,45 @@ +use super::{ + Oct, PackedString, PackedSymbol, SparseByte, Symbol, WideCode, oct, packed, packed_as, wide, +}; +use bit_string::traits::PackedChar; +use int_intervals::UsizeCO; + +#[path = "tests_for_matching/tests_for_vec_search_semantics.rs"] +mod tests_for_vec_search_semantics; + +#[path = "tests_for_matching/tests_for_prefix_suffix_stripping.rs"] +mod tests_for_prefix_suffix_stripping; + +fn matches_at(haystack: &[u8], start: usize, needle: &[u8]) -> bool { + haystack + .get(start..start.saturating_add(needle.len())) + .is_some_and(|window| window == needle) +} + +fn assert_matching(haystack: &[u8], needle: &[u8], decode: fn(u8) -> C) +where + C: PackedChar, +{ + let haystack_string = packed_as(haystack, decode); + let needle_string = packed_as(needle, decode); + let haystack_view = haystack_string.as_packed_str(); + let needle_view = needle_string.as_packed_str(); + let max_start = haystack.len().saturating_sub(needle.len()); + let expected_find = (0..=max_start).find(|&start| matches_at(haystack, start, needle)); + let expected_rfind = (0..=max_start) + .rev() + .find(|&start| matches_at(haystack, start, needle)); + + assert_eq!(haystack_view.find(needle_view), expected_find); + assert_eq!(haystack_view.rfind(needle_view), expected_rfind); + assert_eq!(haystack_view.contains(needle_view), expected_find.is_some()); +} + +#[path = "tests_for_matching/tests_for_character_alignment.rs"] +mod tests_for_character_alignment; + +#[path = "tests_for_matching/tests_for_search.rs"] +mod tests_for_search; + +#[path = "tests_for_matching/tests_for_cross_word_boundaries.rs"] +mod tests_for_cross_word_boundaries; diff --git a/tests/packed/tests_for_matching/tests_for_character_alignment.rs b/tests/packed/tests_for_matching/tests_for_character_alignment.rs new file mode 100644 index 0000000..dd24654 --- /dev/null +++ b/tests/packed/tests_for_matching/tests_for_character_alignment.rs @@ -0,0 +1,257 @@ +use super::*; + +#[test] +fn packed_views_match_only_at_character_boundaries() { + let string = packed(&[0, 1, 2, 1]); + let view = string.as_packed_str(); + assert_eq!( + view.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()) + .get(0), + Some(super::Symbol::One) + ); + assert_eq!(view.find(view.slice_until(1)), Some(0)); + assert!(view.contains(view.slice(UsizeCO::checked_from_start_len(1, 1).unwrap()))); + + let haystack_string = packed(&[0, 0, 1]); + let needle_string = packed(&[2]); + let haystack = haystack_string.as_packed_str(); + let needle = needle_string.as_packed_str(); + assert!(!haystack.contains(needle)); + assert_eq!(haystack.find(needle), None); + assert_eq!(haystack.rfind(needle), None); + assert!(!haystack.matches_at(1, needle)); +} + +#[test] +fn packed_str_matches_at_is_character_aligned_and_bounds_checked() { + let haystack_owner = packed(&[0, 1, 2, 1, 0]); + let haystack = haystack_owner.as_packed_str(); + let needle_owner = packed(&[1, 2]); + let needle = needle_owner.as_packed_str(); + + assert!(haystack.matches_at(1, needle)); + assert!(!haystack.matches_at(0, needle)); + assert!(!haystack.matches_at(4, needle)); + assert!(!haystack.matches_at(5, needle)); + assert!(!haystack.matches_at(usize::MAX, needle)); + + let empty = haystack.slice_until(0); + assert!(haystack.matches_at(0, empty)); + assert!(haystack.matches_at(haystack.char_len(), empty)); + assert!(!haystack.matches_at(haystack.char_len() + 1, empty)); + + let offset_haystack = haystack.slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_needle = haystack.slice(UsizeCO::checked_from_start_len(2, 2).unwrap()); + assert!(offset_haystack.matches_at(1, offset_needle)); + assert!(!offset_haystack.matches_at(0, offset_needle)); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_haystack = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_needle = oct_haystack.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert!(oct_haystack.matches_at(1, oct_needle)); + assert!(!oct_haystack.matches_at(2, oct_needle)); + assert!(!oct_haystack.matches_at(usize::MAX, oct_needle)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_haystack = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + let wide_needle = wide_haystack.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert!(wide_haystack.matches_at(1, wide_needle)); + assert!(!wide_haystack.matches_at(3, wide_needle)); + assert!(!wide_haystack.matches_at(usize::MAX, wide_needle)); +} + +#[test] +fn packed_string_matches_at_is_character_aligned_and_bounds_checked() { + let haystack = packed_as::(&[0, 1, 2, 3, 4, 5, 6, 7, 0, 1, 2, 3], oct); + let needle = packed_as::(&[4, 5], oct); + assert!(haystack.matches_at(4, &needle)); + assert!(!haystack.matches_at(3, &needle)); + assert!(!haystack.matches_at(11, &needle)); + assert!(!haystack.matches_at(usize::MAX, &needle)); + + let empty = PackedString::::new(); + assert!(haystack.matches_at(haystack.char_len(), &empty)); + assert!(!haystack.matches_at(haystack.char_len() + 1, &empty)); + + let wide_haystack = packed_as::(&[0, 11, 22, 33, 44, 55, 66, 77, 88, 99], wide); + let wide_needle = packed_as::(&[88, 99], wide); + assert!(wide_haystack.matches_at(8, &wide_needle)); + assert!(!wide_haystack.matches_at(7, &wide_needle)); + assert!(!wide_haystack.matches_at(9, &wide_needle)); +} + +#[test] +fn packed_str_starts_with_compares_character_aligned_prefixes() { + let owner = packed(&[0, 1, 2, 1, 0]); + let receiver = owner.as_packed_str(); + let exact = packed(&[0, 1, 2, 1, 0]); + let shorter = packed(&[0, 1, 2]); + let different = packed(&[0, 2]); + let oversized = packed(&[0, 1, 2, 1, 0, 1]); + assert!(receiver.starts_with(exact.as_packed_str())); + assert!(receiver.starts_with(shorter.as_packed_str())); + assert!(!receiver.starts_with(different.as_packed_str())); + assert!(!receiver.starts_with(oversized.as_packed_str())); + + let empty_owner = packed(&[]); + let empty = empty_owner.as_packed_str(); + let nonempty_owner = packed(&[0]); + assert!(empty.starts_with(empty)); + assert!(receiver.starts_with(empty)); + assert!(!empty.starts_with(nonempty_owner.as_packed_str())); + + let offset_receiver = receiver.slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_prefix = receiver.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + let offset_different = receiver.slice(UsizeCO::checked_from_start_len(2, 2).unwrap()); + assert!(offset_receiver.starts_with(offset_prefix)); + assert!(!offset_receiver.starts_with(offset_different)); + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert!( + binary + .as_packed_str() + .starts_with(binary.as_packed_str().slice_until(1)) + ); + + let bytes = packed_as::(&[255, 0], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + _ => unreachable!(), + }); + assert!( + bytes + .as_packed_str() + .starts_with(bytes.as_packed_str().slice_until(1)) + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_receiver = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert!(oct_receiver.starts_with(oct_receiver.slice_until(2))); + assert!(!oct_receiver.starts_with(oct_receiver.slice_from(1).slice_until(2))); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_receiver = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + assert!(wide_receiver.starts_with(wide_receiver.slice_until(2))); + assert!(!wide_receiver.starts_with(wide_receiver.slice_from(1).slice_until(2))); +} + +#[test] +fn packed_string_starts_with_compares_character_prefixes() { + let receiver = packed(&[0, 1, 2, 1, 0]); + assert!(receiver.starts_with(&packed(&[0, 1, 2, 1, 0]))); + assert!(receiver.starts_with(&packed(&[0, 1, 2]))); + assert!(!receiver.starts_with(&packed(&[0, 2]))); + assert!(!receiver.starts_with(&packed(&[0, 1, 2, 1, 0, 1]))); + assert!(receiver.starts_with(&PackedString::::new())); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_receiver = packed_as::(&oct_codes, oct); + let oct_prefix = packed_as::(&oct_codes[..22], oct); + assert!(oct_receiver.starts_with(&oct_prefix)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_receiver = packed_as::(&wide_codes, wide); + let wide_prefix = packed_as::(&wide_codes[..10], wide); + assert!(wide_receiver.starts_with(&wide_prefix)); +} + +#[test] +fn packed_str_ends_with_compares_character_aligned_suffixes() { + let owner = packed(&[0, 1, 2, 1, 0]); + let receiver = owner.as_packed_str(); + let exact = packed(&[0, 1, 2, 1, 0]); + let shorter = packed(&[2, 1, 0]); + let different = packed(&[1, 1, 0]); + let oversized = packed(&[0, 1, 2, 1, 0, 1]); + assert!(receiver.ends_with(exact.as_packed_str())); + assert!(receiver.ends_with(shorter.as_packed_str())); + assert!(!receiver.ends_with(different.as_packed_str())); + assert!(!receiver.ends_with(oversized.as_packed_str())); + + let empty_owner = packed(&[]); + let empty = empty_owner.as_packed_str(); + let nonempty_owner = packed(&[0]); + assert!(empty.ends_with(empty)); + assert!(receiver.ends_with(empty)); + assert!(!empty.ends_with(nonempty_owner.as_packed_str())); + + let offset_receiver = receiver.slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_suffix = receiver.slice(UsizeCO::checked_from_start_len(2, 2).unwrap()); + let offset_different = receiver.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert!(offset_receiver.ends_with(offset_suffix)); + assert!(!offset_receiver.ends_with(offset_different)); + + let binary = packed_as::(&[0, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert!( + binary + .as_packed_str() + .ends_with(binary.as_packed_str().slice_from(1)) + ); + + let bytes = packed_as::(&[0, 255], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + _ => unreachable!(), + }); + assert!( + bytes + .as_packed_str() + .ends_with(bytes.as_packed_str().slice_from(1)) + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_receiver = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert!(oct_receiver.ends_with(oct_receiver.slice_from(2))); + assert!(!oct_receiver.ends_with(oct_receiver.slice_from(1).slice_until(2))); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_receiver = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + assert!(wide_receiver.ends_with(wide_receiver.slice_from(2))); + assert!(!wide_receiver.ends_with(wide_receiver.slice_from(1).slice_until(2))); +} + +#[test] +fn packed_string_ends_with_compares_character_suffixes() { + let receiver = packed(&[0, 1, 2, 1, 0]); + assert!(receiver.ends_with(&packed(&[0, 1, 2, 1, 0]))); + assert!(receiver.ends_with(&packed(&[2, 1, 0]))); + assert!(!receiver.ends_with(&packed(&[1, 1, 0]))); + assert!(!receiver.ends_with(&packed(&[0, 1, 2, 1, 0, 1]))); + assert!(receiver.ends_with(&PackedString::::new())); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_receiver = packed_as::(&oct_codes, oct); + let oct_suffix = packed_as::(&oct_codes[2..], oct); + assert!(oct_receiver.ends_with(&oct_suffix)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_receiver = packed_as::(&wide_codes, wide); + let wide_suffix = packed_as::(&wide_codes[6..], wide); + assert!(wide_receiver.ends_with(&wide_suffix)); +} diff --git a/tests/packed/tests_for_matching/tests_for_cross_word_boundaries.rs b/tests/packed/tests_for_matching/tests_for_cross_word_boundaries.rs new file mode 100644 index 0000000..70b54b0 --- /dev/null +++ b/tests/packed/tests_for_matching/tests_for_cross_word_boundaries.rs @@ -0,0 +1,15 @@ +use super::{Oct, WideCode, assert_matching, oct, wide}; +use proptest::prelude::*; + +proptest! { +#[test] + fn three_and_seven_bit_matching_crosses_word_boundaries( + haystack3 in prop::collection::vec(0u8..=7, 22..=128), + needle3 in prop::collection::vec(0u8..=7, 0..=32), + haystack7 in prop::collection::vec(0u8..=127, 10..=128), + needle7 in prop::collection::vec(0u8..=127, 0..=16), + ) { + assert_matching::(&haystack3, &needle3, oct); + assert_matching::(&haystack7, &needle7, wide); + } +} diff --git a/tests/packed/tests_for_matching/tests_for_prefix_suffix_stripping.rs b/tests/packed/tests_for_matching/tests_for_prefix_suffix_stripping.rs new file mode 100644 index 0000000..252ad01 --- /dev/null +++ b/tests/packed/tests_for_matching/tests_for_prefix_suffix_stripping.rs @@ -0,0 +1,362 @@ +use super::{Oct, PackedString, UsizeCO, WideCode, oct, packed, packed_as, wide}; +use bit_string::traits::PackedChar; + +#[test] +fn packed_string_strip_prefix_returns_the_remaining_owner() { + let receiver = packed(&[0, 1, 2, 1, 0]); + let prefix = packed(&[0, 1]); + assert_eq!( + receiver.strip_prefix(&prefix).unwrap().to_vec(), + vec![super::Symbol::Two, super::Symbol::One, super::Symbol::Zero] + ); + assert_eq!( + receiver.strip_prefix(&PackedString::::new()), + Some(receiver.clone()) + ); + assert!(receiver.strip_prefix(&receiver).unwrap().is_empty()); + assert!(receiver.strip_prefix(&packed(&[2, 1])).is_none()); + assert!( + receiver + .strip_prefix(&packed(&[0, 1, 2, 1, 0, 1])) + .is_none() + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_receiver = packed_as::(&oct_codes, oct); + let oct_prefix = packed_as::(&oct_codes[..20], oct); + let oct_stripped = oct_receiver.strip_prefix(&oct_prefix).unwrap(); + assert_eq!( + oct_stripped.to_vec(), + oct_codes[20..].iter().copied().map(oct).collect::>() + ); + assert_eq!(oct_stripped.bits().bit_len(), 4 * 3); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_receiver = packed_as::(&wide_codes, wide); + let wide_prefix = packed_as::(&wide_codes[..8], wide); + let wide_stripped = wide_receiver.strip_prefix(&wide_prefix).unwrap(); + assert_eq!( + wide_stripped.to_vec(), + wide_codes[8..] + .iter() + .copied() + .map(wide) + .collect::>() + ); + assert_eq!(wide_stripped.bits().bit_len(), 8 * 7); +} + +#[test] +fn packed_str_strip_prefix_returns_the_remaining_character_view() { + let owner = packed(&[0, 1, 2, 1, 0]); + let receiver = owner.as_packed_str(); + let prefix = packed(&[0, 1]); + let stripped = receiver.strip_prefix(prefix.as_packed_str()).unwrap(); + assert_eq!( + stripped.iter().collect::>(), + vec![super::Symbol::Two, super::Symbol::One, super::Symbol::Zero] + ); + assert_eq!(stripped.char_len(), 3); + assert_eq!( + stripped.to_packed_string().to_vec(), + vec![super::Symbol::Two, super::Symbol::One, super::Symbol::Zero] + ); + + let empty_prefix_owner = packed(&[]); + let empty_prefix = empty_prefix_owner.as_packed_str(); + assert!(receiver.strip_prefix(empty_prefix).unwrap() == receiver); + assert!(receiver.strip_prefix(receiver).unwrap().is_empty()); + assert!(empty_prefix.strip_prefix(empty_prefix).unwrap().is_empty()); + + let different_first = packed(&[1, 1]); + let different_middle = packed(&[0, 2]); + let different_last = packed(&[0, 0]); + let oversized = packed(&[0, 1, 2, 1, 0, 2]); + assert!( + receiver + .strip_prefix(different_first.as_packed_str()) + .is_none() + ); + assert!( + receiver + .strip_prefix(different_middle.as_packed_str()) + .is_none() + ); + assert!( + receiver + .strip_prefix(different_last.as_packed_str()) + .is_none() + ); + assert!(receiver.strip_prefix(oversized.as_packed_str()).is_none()); + assert!( + empty_prefix + .strip_prefix(packed(&[0]).as_packed_str()) + .is_none() + ); + + let offset_owner = packed(&[0, 1, 2, 1, 0]); + let offset_receiver = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_prefix = packed(&[1, 2]); + let offset_stripped = offset_receiver + .strip_prefix(offset_prefix.as_packed_str()) + .unwrap(); + assert_eq!( + offset_stripped.iter().collect::>(), + vec![super::Symbol::One] + ); + assert!( + offset_receiver + .strip_prefix(offset_receiver.slice_from(offset_receiver.char_len())) + .unwrap() + == offset_receiver + ); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary + .as_packed_str() + .strip_prefix(binary.as_packed_str().slice_until(1)) + .unwrap() + .iter() + .collect::>(), + vec![super::PackedSymbol::One, super::PackedSymbol::One] + ); + + let bytes = packed_as::(&[255, 3, 0], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!( + bytes + .as_packed_str() + .strip_prefix(bytes.as_packed_str().slice_until(1)) + .unwrap() + .iter() + .collect::>(), + vec![super::SparseByte::Middle, super::SparseByte::Zero] + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_receiver = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_stripped = oct_receiver + .strip_prefix(oct_receiver.slice_until(2)) + .unwrap(); + assert_eq!( + oct_stripped + .iter() + .map(|character| character.code()) + .collect::>(), + vec![6, 7] + ); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_receiver = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + let wide_stripped = wide_receiver + .strip_prefix(wide_receiver.slice_until(2)) + .unwrap(); + assert_eq!( + wide_stripped + .iter() + .map(|character| character.code()) + .collect::>(), + vec![110, 121] + ); + + assert_eq!(offset_receiver.char_len(), 3); + assert_eq!(offset_receiver.get(0), Some(super::Symbol::One)); + assert_eq!(offset_receiver.get(2), Some(super::Symbol::One)); +} + +#[test] +fn packed_string_strip_suffix_returns_the_remaining_owner() { + let receiver = packed(&[0, 1, 2, 1, 0]); + let suffix = packed(&[1, 0]); + assert_eq!( + receiver.strip_suffix(&suffix).unwrap().to_vec(), + vec![super::Symbol::Zero, super::Symbol::One, super::Symbol::Two] + ); + assert_eq!( + receiver.strip_suffix(&PackedString::::new()), + Some(receiver.clone()) + ); + assert!(receiver.strip_suffix(&receiver).unwrap().is_empty()); + assert!(receiver.strip_suffix(&packed(&[0, 1])).is_none()); + assert!( + receiver + .strip_suffix(&packed(&[1, 0, 1, 0, 1, 0])) + .is_none() + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_receiver = packed_as::(&oct_codes, oct); + let oct_suffix = packed_as::(&oct_codes[20..], oct); + let oct_stripped = oct_receiver.strip_suffix(&oct_suffix).unwrap(); + assert_eq!( + oct_stripped.to_vec(), + oct_codes[..20].iter().copied().map(oct).collect::>() + ); + assert_eq!(oct_stripped.bits().bit_len(), 20 * 3); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_receiver = packed_as::(&wide_codes, wide); + let wide_suffix = packed_as::(&wide_codes[8..], wide); + let wide_stripped = wide_receiver.strip_suffix(&wide_suffix).unwrap(); + assert_eq!( + wide_stripped.to_vec(), + wide_codes[..8] + .iter() + .copied() + .map(wide) + .collect::>() + ); + assert_eq!(wide_stripped.bits().bit_len(), 8 * 7); +} + +#[test] +fn packed_str_strip_suffix_returns_the_remaining_character_view() { + let owner = packed(&[0, 1, 2, 1, 0]); + let receiver = owner.as_packed_str(); + let suffix = packed(&[1, 0]); + let stripped = receiver.strip_suffix(suffix.as_packed_str()).unwrap(); + assert_eq!( + stripped.iter().collect::>(), + vec![super::Symbol::Zero, super::Symbol::One, super::Symbol::Two] + ); + assert_eq!(stripped.char_len(), 3); + assert_eq!( + stripped.to_packed_string().to_vec(), + vec![super::Symbol::Zero, super::Symbol::One, super::Symbol::Two] + ); + + let empty_suffix_owner = packed(&[]); + let empty_suffix = empty_suffix_owner.as_packed_str(); + assert!(receiver.strip_suffix(empty_suffix).unwrap() == receiver); + assert!(receiver.strip_suffix(receiver).unwrap().is_empty()); + assert!(empty_suffix.strip_suffix(empty_suffix).unwrap().is_empty()); + + let different_first = packed(&[0, 2]); + let different_middle = packed(&[2, 0]); + let different_last = packed(&[1, 1]); + let oversized = packed(&[2, 1, 0, 1, 0, 2]); + assert!( + receiver + .strip_suffix(different_first.as_packed_str()) + .is_none() + ); + assert!( + receiver + .strip_suffix(different_middle.as_packed_str()) + .is_none() + ); + assert!( + receiver + .strip_suffix(different_last.as_packed_str()) + .is_none() + ); + assert!(receiver.strip_suffix(oversized.as_packed_str()).is_none()); + assert!( + empty_suffix + .strip_suffix(packed(&[0]).as_packed_str()) + .is_none() + ); + + let offset_owner = packed(&[0, 1, 2, 1, 0]); + let offset_receiver = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_suffix = packed(&[2, 1]); + let offset_stripped = offset_receiver + .strip_suffix(offset_suffix.as_packed_str()) + .unwrap(); + assert_eq!( + offset_stripped.iter().collect::>(), + vec![super::Symbol::One] + ); + assert!( + offset_receiver + .strip_suffix(offset_receiver.slice_until(0)) + .unwrap() + == offset_receiver + ); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary + .as_packed_str() + .strip_suffix(binary.as_packed_str().slice_from(1)) + .unwrap() + .iter() + .collect::>(), + vec![super::PackedSymbol::Zero] + ); + + let bytes = packed_as::(&[255, 3, 0], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!( + bytes + .as_packed_str() + .strip_suffix(bytes.as_packed_str().slice_from(1)) + .unwrap() + .iter() + .collect::>(), + vec![super::SparseByte::Maximum] + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_receiver = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_stripped = oct_receiver + .strip_suffix(oct_receiver.slice_from(2)) + .unwrap(); + assert_eq!( + oct_stripped + .iter() + .map(|character| character.code()) + .collect::>(), + vec![4, 5] + ); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_receiver = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + let wide_stripped = wide_receiver + .strip_suffix(wide_receiver.slice_from(2)) + .unwrap(); + assert_eq!( + wide_stripped + .iter() + .map(|character| character.code()) + .collect::>(), + vec![88, 99] + ); + + assert_eq!(offset_receiver.char_len(), 3); + assert_eq!(offset_receiver.get(0), Some(super::Symbol::One)); + assert_eq!(offset_receiver.get(2), Some(super::Symbol::One)); +} diff --git a/tests/packed/tests_for_matching/tests_for_search.rs b/tests/packed/tests_for_matching/tests_for_search.rs new file mode 100644 index 0000000..c995ff9 --- /dev/null +++ b/tests/packed/tests_for_matching/tests_for_search.rs @@ -0,0 +1,355 @@ +use super::*; + +#[test] +fn packed_str_contains_searches_character_aligned_windows() { + let haystack_owner = packed(&[0, 0, 0]); + let haystack = haystack_owner.as_packed_str(); + let repeated = packed(&[0, 0]); + let final_haystack_owner = packed(&[1, 2, 0]); + let final_haystack = final_haystack_owner.as_packed_str(); + let final_window = packed(&[2, 0]); + let different = packed(&[1, 2]); + let oversized = packed(&[0, 0, 0, 1]); + + assert!(haystack.contains(repeated.as_packed_str())); + assert!(final_haystack.contains(final_window.as_packed_str())); + assert!(!haystack.contains(different.as_packed_str())); + assert!(!haystack.contains(oversized.as_packed_str())); + assert!(haystack.contains(haystack.slice_until(0))); + + let empty_owner = packed(&[]); + let empty = empty_owner.as_packed_str(); + let singleton_owner = packed(&[0]); + assert!(empty.contains(empty)); + assert!(!empty.contains(singleton_owner.as_packed_str())); + + let offset_owner = packed(&[0, 1, 2, 1, 0]); + let offset_haystack = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_needle = offset_haystack.slice_from(1); + let offset_different_owner = packed(&[2, 2]); + let offset_different = offset_different_owner.as_packed_str(); + assert!(offset_haystack.contains(offset_needle)); + assert!(!offset_haystack.contains(offset_different)); + + let binary = packed_as::(&[0, 1, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert!( + binary + .as_packed_str() + .contains(binary.as_packed_str().slice_from(1)) + ); + + let bytes = packed_as::(&[0, 255], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + _ => unreachable!(), + }); + assert!( + bytes + .as_packed_str() + .contains(bytes.as_packed_str().slice_from(1)) + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_haystack = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + let oct_different_owner = packed_as::(&[5, 4], oct); + assert!(oct_haystack.contains(oct_haystack.slice_from(1))); + assert!(!oct_haystack.contains(oct_different_owner.as_packed_str())); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_haystack = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + let wide_different_owner = packed_as::(&[11, 0], wide); + assert!(wide_haystack.contains(wide_haystack.slice_from(1))); + assert!(!wide_haystack.contains(wide_different_owner.as_packed_str())); +} + +#[test] +fn packed_string_contains_searches_character_aligned_windows() { + let haystack = packed(&[0, 1, 0, 1, 2, 1]); + assert!(haystack.contains(&packed(&[0, 1, 2]))); + assert!(haystack.contains(&packed(&[1, 2, 1]))); + assert!(!haystack.contains(&packed(&[2, 0]))); + assert!(!haystack.contains(&packed(&[0, 1, 0, 1, 2, 1, 0]))); + assert!(haystack.contains(&PackedString::::new())); + + let empty = PackedString::::new(); + assert!(!empty.contains(&packed(&[0]))); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_haystack = packed_as::(&oct_codes, oct); + assert!(oct_haystack.contains(&packed_as::(&[4, 5, 6, 7], oct))); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_haystack = packed_as::(&wide_codes, wide); + assert!(wide_haystack.contains(&packed_as::(&[88, 99], wide))); +} + +#[test] +fn packed_string_find_returns_the_earliest_character_index() { + let haystack = packed(&[0, 1, 0, 1, 2, 1]); + assert_eq!(haystack.find(&packed(&[0, 1])), Some(0)); + assert_eq!(haystack.find(&packed(&[1, 2])), Some(3)); + assert_eq!(haystack.find(&packed(&[2, 0])), None); + assert_eq!(haystack.find(&packed(&[0, 1, 0, 1, 2, 1, 0])), None); + assert_eq!( + haystack.find(&PackedString::::new()), + Some(0) + ); + + let empty = PackedString::::new(); + assert_eq!(empty.find(&empty), Some(0)); + assert_eq!(empty.find(&packed(&[0])), None); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_haystack = packed_as::(&oct_codes, oct); + assert_eq!( + oct_haystack.find(&packed_as::(&[4, 5, 6], oct)), + Some(4) + ); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_haystack = packed_as::(&wide_codes, wide); + assert_eq!( + wide_haystack.find(&packed_as::(&[88, 99], wide)), + Some(8) + ); +} + +#[test] +fn packed_str_find_returns_the_earliest_character_index() { + let repeated_owner = packed(&[0, 0, 0]); + let repeated_needle = packed(&[0, 0]); + assert_eq!( + repeated_owner + .as_packed_str() + .find(repeated_needle.as_packed_str()), + Some(0) + ); + + let final_owner = packed(&[1, 2, 0]); + let final_needle = packed(&[2, 0]); + assert_eq!( + final_owner + .as_packed_str() + .find(final_needle.as_packed_str()), + Some(1) + ); + + let no_match = packed(&[1, 2]); + let no_match_needle = packed(&[0]); + let oversized = packed(&[0, 1, 2]); + assert_eq!( + no_match + .as_packed_str() + .find(no_match_needle.as_packed_str()), + None + ); + assert_eq!( + no_match.as_packed_str().find(oversized.as_packed_str()), + None + ); + + let empty_owner = packed(&[]); + let empty = empty_owner.as_packed_str(); + assert_eq!(empty.find(empty), Some(0)); + assert_eq!(final_owner.as_packed_str().find(empty), Some(0)); + + let offset_owner = packed(&[0, 1, 2, 1, 0]); + let offset_haystack = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_needle = packed(&[1, 2]); + assert_eq!(offset_haystack.find(offset_needle.as_packed_str()), Some(0)); + + let binary = packed_as::(&[1, 0, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + let binary_needle = packed_as::(&[1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary.as_packed_str().find(binary_needle.as_packed_str()), + Some(0) + ); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + let byte_needle = packed_as::(&[255, 3], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!( + bytes.as_packed_str().find(byte_needle.as_packed_str()), + Some(1) + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_haystack = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert_eq!(oct_haystack.find(oct_haystack.slice_from(1)), Some(1)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_haystack = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + assert_eq!(wide_haystack.find(wide_haystack.slice_from(1)), Some(1)); + + assert_eq!(offset_haystack.char_len(), 3); + assert_eq!(offset_haystack.get(0), Some(super::Symbol::One)); + assert_eq!(offset_haystack.get(2), Some(super::Symbol::One)); +} + +#[test] +fn packed_str_rfind_returns_the_latest_character_index() { + let repeated_owner = packed(&[0, 0, 0]); + let repeated_needle = packed(&[0, 0]); + assert_eq!( + repeated_owner + .as_packed_str() + .rfind(repeated_needle.as_packed_str()), + Some(1) + ); + + let final_owner = packed(&[1, 2, 0]); + let final_needle = packed(&[2, 0]); + assert_eq!( + final_owner + .as_packed_str() + .rfind(final_needle.as_packed_str()), + Some(1) + ); + + let no_match = packed(&[1, 2]); + let no_match_needle = packed(&[0]); + let oversized = packed(&[0, 1, 2]); + assert_eq!( + no_match + .as_packed_str() + .rfind(no_match_needle.as_packed_str()), + None + ); + assert_eq!( + no_match.as_packed_str().rfind(oversized.as_packed_str()), + None + ); + + let empty_owner = packed(&[]); + let empty = empty_owner.as_packed_str(); + assert_eq!(empty.rfind(empty), Some(0)); + assert_eq!(final_owner.as_packed_str().rfind(empty), Some(3)); + + let offset_owner = packed(&[0, 1, 2, 1, 0]); + let offset_haystack = offset_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 3).unwrap()); + let offset_needle = packed(&[1]); + assert_eq!( + offset_haystack.rfind(offset_needle.as_packed_str()), + Some(2) + ); + + let binary = packed_as::(&[1, 0, 1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + let binary_needle = packed_as::(&[1], |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }); + assert_eq!( + binary.as_packed_str().rfind(binary_needle.as_packed_str()), + Some(2) + ); + + let bytes = packed_as::(&[0, 255, 3], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + let byte_needle = packed_as::(&[255, 3], |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + 3 => super::SparseByte::Middle, + _ => unreachable!(), + }); + assert_eq!( + bytes.as_packed_str().rfind(byte_needle.as_packed_str()), + Some(1) + ); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_owner = packed_as::(&oct_codes, oct); + let oct_haystack = oct_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(20, 4).unwrap()); + assert_eq!(oct_haystack.rfind(oct_haystack.slice_from(1)), Some(1)); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_owner = packed_as::(&wide_codes, wide); + let wide_haystack = wide_owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(8, 4).unwrap()); + assert_eq!(wide_haystack.rfind(wide_haystack.slice_from(1)), Some(1)); + + assert_eq!(offset_haystack.char_len(), 3); + assert_eq!(offset_haystack.get(0), Some(super::Symbol::One)); + assert_eq!(offset_haystack.get(2), Some(super::Symbol::One)); +} + +#[test] +fn packed_string_rfind_returns_the_latest_character_index() { + let haystack = packed(&[0, 1, 0, 1, 2, 1]); + assert_eq!(haystack.rfind(&packed(&[0, 1])), Some(2)); + assert_eq!(haystack.rfind(&packed(&[1, 2])), Some(3)); + assert_eq!(haystack.rfind(&packed(&[2, 0])), None); + assert_eq!(haystack.rfind(&packed(&[0, 1, 0, 1, 2, 1, 0])), None); + assert_eq!( + haystack.rfind(&PackedString::::new()), + Some(6) + ); + + let empty = PackedString::::new(); + assert_eq!(empty.rfind(&empty), Some(0)); + assert_eq!(empty.rfind(&packed(&[0])), None); + + let oct_codes: Vec = (0..24).map(|index| index as u8 % 8).collect(); + let oct_haystack = packed_as::(&oct_codes, oct); + assert_eq!( + oct_haystack.rfind(&packed_as::(&[4, 5, 6], oct)), + Some(20) + ); + + let wide_codes: Vec = (0..16).map(|index| (index * 11) as u8 % 128).collect(); + let wide_haystack = packed_as::(&wide_codes, wide); + assert_eq!( + wide_haystack.rfind(&packed_as::(&[88, 99], wide)), + Some(8) + ); +} diff --git a/tests/packed/tests_for_matching/tests_for_vec_search_semantics.rs b/tests/packed/tests_for_matching/tests_for_vec_search_semantics.rs new file mode 100644 index 0000000..0083c90 --- /dev/null +++ b/tests/packed/tests_for_matching/tests_for_vec_search_semantics.rs @@ -0,0 +1,22 @@ +use super::{matches_at, packed}; +use proptest::prelude::*; + +proptest! { +#[test] + fn packed_matching_matches_vec_sliding_windows( + haystack in prop::collection::vec(0u8..=2, 0..=32), + needle in prop::collection::vec(0u8..=2, 0..=16), + ) { + let haystack_string = packed(&haystack); + let needle_string = packed(&needle); + let haystack_view = haystack_string.as_packed_str(); + let needle_view = needle_string.as_packed_str(); + let max_start = haystack.len().saturating_sub(needle.len()); + let expected_find = (0..=max_start).find(|&start| matches_at(&haystack, start, &needle)); + let expected_rfind = (0..=max_start).rev().find(|&start| matches_at(&haystack, start, &needle)); + + prop_assert_eq!(haystack_view.find(needle_view), expected_find); + prop_assert_eq!(haystack_view.rfind(needle_view), expected_rfind); + prop_assert_eq!(haystack_view.contains(needle_view), expected_find.is_some()); + } +} diff --git a/tests/packed/tests_for_ord.rs b/tests/packed/tests_for_ord.rs new file mode 100644 index 0000000..7a965d0 --- /dev/null +++ b/tests/packed/tests_for_ord.rs @@ -0,0 +1,253 @@ +use super::{Oct, PackedString, Symbol, WideCode, oct, packed, packed_as, wide}; +use bit_string::traits::PackedChar; +use core::cmp::Ordering; +use int_intervals::UsizeCO; +use proptest::prelude::*; + +fn assert_code_order( + left: &[u8], + right: &[u8], + decode: fn(u8) -> C, + expected: Ordering, +) where + C: PackedChar, +{ + let left = packed_as(left, decode); + let right = packed_as(right, decode); + assert_eq!(left.cmp_string(&right), expected); + assert_eq!(left.cmp(&right), expected); + assert_eq!(left.as_packed_str().cmp(&right.as_packed_str()), expected); +} + +fn assert_partial_order( + left: &[u8], + right: &[u8], + decode: fn(u8) -> C, + expected: Ordering, +) where + C: PackedChar, +{ + let left = packed_as(left, decode); + let right = packed_as(right, decode); + let left = left.as_packed_str(); + let right = right.as_packed_str(); + assert_eq!(left.partial_cmp(&right), Some(expected)); + assert_eq!(left.partial_cmp(&right), Some(left.cmp(&right))); + assert_eq!(right.partial_cmp(&left), Some(expected.reverse())); +} + +#[test] +fn ordering_uses_packed_code_values() { + let one = PackedString::::from_chars([Symbol::One]); + let two = PackedString::::from_chars([Symbol::Two]); + assert!(one < two); + assert!(one.as_packed_str() < two.as_packed_str()); +} + +#[test] +fn packed_order_compares_numeric_codes_before_length() { + assert_code_order::(&[1], &[2], super::symbol, Ordering::Less); + assert_code_order::(&[2], &[1], super::symbol, Ordering::Greater); + assert_code_order::(&[2], &[2, 0], super::symbol, Ordering::Less); + assert_code_order::(&[0, 2, 0], &[0, 1, 2], super::symbol, Ordering::Greater); + assert_code_order::(&[1, 0], &[1, 2], super::symbol, Ordering::Less); + assert_code_order::(&[1, 2], &[1, 2], super::symbol, Ordering::Equal); + + assert_code_order::( + &[0, 1], + &[1, 0], + |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }, + Ordering::Less, + ); + assert_code_order::( + &[0], + &[255], + |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + _ => unreachable!(), + }, + Ordering::Less, + ); + + let oct_left = vec![0; 22]; + let mut oct_right = oct_left.clone(); + oct_right[21] = 7; + assert_code_order::(&oct_left, &oct_right, oct, Ordering::Less); + + let wide_left = vec![0; 10]; + let mut wide_right = wide_left.clone(); + wide_right[9] = 127; + assert_code_order::(&wide_left, &wide_right, wide, Ordering::Less); + + let owner = PackedString::::from_chars([ + Symbol::Zero, + Symbol::One, + Symbol::Two, + Symbol::One, + ]); + let other = + PackedString::::from_chars([Symbol::Two, Symbol::One, Symbol::Two, Symbol::One]); + let owner_view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + let other_view = other + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert_eq!(owner_view.cmp(&other_view), Ordering::Equal); + assert_eq!(owner_view.char_len(), 2); + assert_eq!(other_view.char_len(), 2); +} + +#[test] +fn packed_partial_order_is_total_and_matches_ord() { + assert_partial_order::(&[], &[], super::symbol, Ordering::Equal); + assert_partial_order::(&[2], &[2, 0], super::symbol, Ordering::Less); + assert_partial_order::(&[1], &[2], super::symbol, Ordering::Less); + assert_partial_order::(&[2], &[1], super::symbol, Ordering::Greater); + + assert_partial_order::( + &[0], + &[1], + |code| match code { + 0 => super::PackedSymbol::Zero, + 1 => super::PackedSymbol::One, + _ => unreachable!(), + }, + Ordering::Less, + ); + assert_partial_order::( + &[255], + &[0], + |code| match code { + 0 => super::SparseByte::Zero, + 255 => super::SparseByte::Maximum, + _ => unreachable!(), + }, + Ordering::Greater, + ); + + let oct_left = vec![0; 22]; + let mut oct_right = oct_left.clone(); + oct_right[21] = 7; + assert_partial_order::(&oct_left, &oct_right, oct, Ordering::Less); + + let wide_left = vec![0; 10]; + let mut wide_right = wide_left.clone(); + wide_right[9] = 127; + assert_partial_order::(&wide_left, &wide_right, wide, Ordering::Less); + + let owner = PackedString::::from_chars([ + Symbol::Zero, + Symbol::One, + Symbol::Two, + Symbol::One, + ]); + let equal_view = owner + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + let other = + PackedString::::from_chars([Symbol::Two, Symbol::One, Symbol::Two, Symbol::One]); + let other_view = other + .as_packed_str() + .slice(UsizeCO::checked_from_start_len(1, 2).unwrap()); + assert_eq!(equal_view.partial_cmp(&other_view), Some(Ordering::Equal)); + assert_eq!(other_view.partial_cmp(&equal_view), Some(Ordering::Equal)); +} + +#[test] +fn packed_ord_cmp_is_total_and_matches_partial_order() { + let less = PackedString::::from_chars([Symbol::One]); + let greater = PackedString::::from_chars([Symbol::Two]); + let equal = PackedString::::from_chars([Symbol::One]); + let extension = PackedString::::from_chars([Symbol::One, Symbol::Zero]); + + assert_eq!( + less.as_packed_str().cmp(&greater.as_packed_str()), + Ordering::Less + ); + assert_eq!( + greater.as_packed_str().cmp(&less.as_packed_str()), + Ordering::Greater + ); + assert_eq!( + less.as_packed_str().cmp(&equal.as_packed_str()), + Ordering::Equal + ); + assert_eq!( + less.as_packed_str().cmp(&extension.as_packed_str()), + Ordering::Less + ); + assert_eq!( + less.as_packed_str().partial_cmp(&extension.as_packed_str()), + Some(Ordering::Less) + ); + + let oct_left = vec![0; 22]; + let mut oct_right = oct_left.clone(); + oct_right[21] = 7; + let oct_left = packed_as::(&oct_left, oct); + let oct_right = packed_as::(&oct_right, oct); + assert_eq!( + oct_left.as_packed_str().cmp(&oct_right.as_packed_str()), + Ordering::Less + ); + + let wide_left = vec![0; 10]; + let mut wide_right = wide_left.clone(); + wide_right[9] = 127; + let wide_left = packed_as::(&wide_left, wide); + let wide_right = packed_as::(&wide_right, wide); + assert_eq!( + wide_left.as_packed_str().cmp(&wide_right.as_packed_str()), + Ordering::Less + ); +} + +proptest! { + #[test] + fn packed_order_matches_code_sequence_oracle( + left in prop::collection::vec(0u8..=2, 0..=32), + right in prop::collection::vec(0u8..=2, 0..=32), + ) { + let left_string = packed(&left); + let right_string = packed(&right); + let expected = left.cmp(&right); + + prop_assert_eq!(left_string.cmp(&right_string), expected); + prop_assert_eq!( + left_string.as_packed_str().cmp(&right_string.as_packed_str()), + expected, + ); + } + + #[test] + fn packed_order_matches_code_sequence_oracle_across_word_boundaries( + left3 in prop::collection::vec(0u8..=7, 22..=128), + right3 in prop::collection::vec(0u8..=7, 22..=128), + left7 in prop::collection::vec(0u8..=127, 10..=128), + right7 in prop::collection::vec(0u8..=127, 10..=128), + ) { + let left3_string = packed_as::(&left3, oct); + let right3_string = packed_as::(&right3, oct); + let expected3 = left3.cmp(&right3); + prop_assert_eq!(left3_string.cmp(&right3_string), expected3); + prop_assert_eq!( + left3_string.as_packed_str().cmp(&right3_string.as_packed_str()), + expected3, + ); + + let left7_string = packed_as::(&left7, wide); + let right7_string = packed_as::(&right7, wide); + let expected7 = left7.cmp(&right7); + prop_assert_eq!(left7_string.cmp(&right7_string), expected7); + prop_assert_eq!( + left7_string.as_packed_str().cmp(&right7_string.as_packed_str()), + expected7, + ); + } +} diff --git a/tests/packed_char.rs b/tests/packed_char.rs deleted file mode 100644 index 9ae43ce..0000000 --- a/tests/packed_char.rs +++ /dev/null @@ -1,461 +0,0 @@ -use bit_string::{BitString, PackedString, packed, traits::PackedChar}; -use int_intervals::UsizeCO; -use proptest::prelude::*; - -#[packed(bits = 1)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum PackedSymbol { - Zero = 0, - One = 1, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[packed(bits = 2)] -enum Symbol { - Zero = 0, - One = 1, - Two = 2, -} - -#[packed(bits = 3)] -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -enum Oct { - V0 = 0, - V1 = 1, - V2 = 2, - V3 = 3, - V4 = 4, - V5 = 5, - V6 = 6, - V7 = 7, -} - -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -struct WideCode(u8); - -impl PackedChar<7> for WideCode { - fn code(self) -> u8 { - self.0 - } - - fn from_code(code: u8) -> Option { - (code < 128).then_some(Self(code)) - } -} - -fn symbol(code: u8) -> Symbol { - match code { - 0 => Symbol::Zero, - 1 => Symbol::One, - 2 => Symbol::Two, - _ => unreachable!(), - } -} - -fn oct(code: u8) -> Oct { - match code { - 0 => Oct::V0, - 1 => Oct::V1, - 2 => Oct::V2, - 3 => Oct::V3, - 4 => Oct::V4, - 5 => Oct::V5, - 6 => Oct::V6, - 7 => Oct::V7, - _ => unreachable!(), - } -} - -fn wide(code: u8) -> WideCode { - WideCode(code) -} - -fn packed(codes: &[u8]) -> PackedString { - PackedString::from_chars(codes.iter().copied().map(symbol)) -} - -fn packed_as(codes: &[u8], decode: fn(u8) -> C) -> PackedString -where - C: PackedChar, -{ - PackedString::from_chars(codes.iter().copied().map(decode)) -} - -fn matches_at(haystack: &[u8], start: usize, needle: &[u8]) -> bool { - haystack - .get(start..start.saturating_add(needle.len())) - .is_some_and(|window| window == needle) -} - -fn assert_access_slice_order( - left: &[u8], - right: &[u8], - start: usize, - len: usize, - decode: fn(u8) -> C, -) where - C: PackedChar + core::fmt::Debug, -{ - let string = packed_as(left, decode); - assert_eq!(string.char_len(), left.len()); - let view = string.as_packed_str(); - for index in 0..=left.len() + 1 { - assert_eq!(string.get(index), left.get(index).copied().map(decode)); - assert_eq!(view.get(index), left.get(index).copied().map(decode)); - } - - let oracle_start = start.min(left.len()); - let oracle_end = start.saturating_add(len).min(left.len()).max(oracle_start); - let slice = string.slice(UsizeCO::checked_from_start_len(start, len).unwrap()); - assert_eq!( - slice.to_vec(), - left[oracle_start..oracle_end] - .iter() - .copied() - .map(decode) - .collect::>() - ); - - let right_string = packed_as(right, decode); - assert_eq!(string.cmp(&right_string), left.cmp(&right)); - assert_eq!( - string.as_packed_str().cmp(&right_string.as_packed_str()), - left.cmp(&right) - ); -} - -fn assert_matching(haystack: &[u8], needle: &[u8], decode: fn(u8) -> C) -where - C: PackedChar, -{ - let haystack_string = packed_as(haystack, decode); - let needle_string = packed_as(needle, decode); - let haystack_view = haystack_string.as_packed_str(); - let needle_view = needle_string.as_packed_str(); - let max_start = haystack.len().saturating_sub(needle.len()); - let expected_find = (0..=max_start).find(|&start| matches_at(haystack, start, needle)); - let expected_rfind = (0..=max_start) - .rev() - .find(|&start| matches_at(haystack, start, needle)); - - assert_eq!(haystack_view.find(needle_view), expected_find); - assert_eq!(haystack_view.rfind(needle_view), expected_rfind); - assert_eq!(haystack_view.contains(needle_view), expected_find.is_some()); -} - -fn assert_edits( - initial: &[u8], - replacement: &[u8], - insert_index: usize, - remove_index: usize, - replace_start: usize, - inserted_code: u8, - decode: fn(u8) -> C, -) where - C: PackedChar + core::fmt::Debug, -{ - let mut string = packed_as(initial, decode); - let mut oracle = initial.to_vec(); - - let oracle_insert_index = insert_index.min(oracle.len()); - string.insert(insert_index, decode(inserted_code)); - oracle.insert(oracle_insert_index, inserted_code); - - if !oracle.is_empty() { - let remove_index = remove_index.min(oracle.len() - 1); - assert_eq!( - string.remove(remove_index).code(), - oracle.remove(remove_index) - ); - } - - let oracle_replace_start = replace_start.min(oracle.len()); - let replace_end = oracle_replace_start - .saturating_add(replacement.len()) - .min(oracle.len()); - string.replace_assign(replace_start, &packed_as(replacement, decode)); - oracle.splice( - oracle_replace_start..replace_end, - replacement.iter().copied(), - ); - assert_eq!( - string - .to_vec() - .iter() - .copied() - .map(|value| value.code()) - .collect::>(), - oracle - ); - - let drain_len = insert_index.max(1); - let oracle_drain_start = remove_index.min(oracle.len()); - let oracle_drain_end = remove_index - .saturating_add(drain_len) - .min(oracle.len()) - .max(oracle_drain_start); - string.drain_interval_assign(UsizeCO::checked_from_start_len(remove_index, drain_len).unwrap()); - oracle.drain(oracle_drain_start..oracle_drain_end); - assert_eq!( - string - .to_vec() - .iter() - .copied() - .map(|value| value.code()) - .collect::>(), - oracle - ); -} - -proptest! { - #[test] - fn packed_access_slice_and_order_match_vec_oracles( - left in prop::collection::vec(0u8..=2, 0..=32), - right in prop::collection::vec(0u8..=2, 0..=32), - start in 0usize..40, - len in 1usize..40, - ) { - let string = packed(&left); - prop_assert_eq!(string.char_len(), left.len()); - let view = string.as_packed_str(); - for index in 0..=left.len() + 1 { - prop_assert_eq!(string.get(index), left.get(index).copied().map(symbol)); - prop_assert_eq!(view.get(index), left.get(index).copied().map(symbol)); - } - - let oracle_start = start.min(left.len()); - let oracle_end = start.saturating_add(len).min(left.len()).max(oracle_start); - let slice = string.slice(UsizeCO::checked_from_start_len(start, len).unwrap()); - prop_assert_eq!(slice.to_vec(), left[oracle_start..oracle_end].iter().copied().map(symbol).collect::>()); - - let right_string = packed(&right); - prop_assert_eq!(string.cmp(&right_string), left.cmp(&right)); - prop_assert_eq!(string.as_packed_str().cmp(&right_string.as_packed_str()), left.cmp(&right)); - } - - #[test] - fn packed_matching_matches_vec_sliding_windows( - haystack in prop::collection::vec(0u8..=2, 0..=32), - needle in prop::collection::vec(0u8..=2, 0..=16), - ) { - let haystack_string = packed(&haystack); - let needle_string = packed(&needle); - let haystack_view = haystack_string.as_packed_str(); - let needle_view = needle_string.as_packed_str(); - let max_start = haystack.len().saturating_sub(needle.len()); - let expected_find = (0..=max_start).find(|&start| matches_at(&haystack, start, &needle)); - let expected_rfind = (0..=max_start).rev().find(|&start| matches_at(&haystack, start, &needle)); - - prop_assert_eq!(haystack_view.find(needle_view), expected_find); - prop_assert_eq!(haystack_view.rfind(needle_view), expected_rfind); - prop_assert_eq!(haystack_view.contains(needle_view), expected_find.is_some()); - } - - #[test] - fn packed_edits_match_vec_splice_semantics( - initial in prop::collection::vec(0u8..=2, 0..=24), - replacement in prop::collection::vec(0u8..=2, 0..=12), - insert_index in 0usize..32, - remove_index in 0usize..32, - replace_start in 0usize..32, - ) { - let mut string = packed(&initial); - let mut oracle = initial.clone(); - - let oracle_insert_index = insert_index.min(oracle.len()); - let inserted = symbol(2); - string.insert(insert_index, inserted); - oracle.insert(oracle_insert_index, 2); - - if !oracle.is_empty() { - let remove_index = remove_index.min(oracle.len() - 1); - prop_assert_eq!(string.remove(remove_index).code(), oracle.remove(remove_index)); - } - - let oracle_replace_start = replace_start.min(oracle.len()); - let replace_end = oracle_replace_start - .saturating_add(replacement.len()) - .min(oracle.len()); - string.replace_assign(replace_start, &packed(&replacement)); - oracle.splice(oracle_replace_start..replace_end, replacement.iter().copied()); - prop_assert_eq!( - string.to_vec().iter().copied().map(|value| value.code()).collect::>(), - oracle.clone(), - ); - - let drain_len = insert_index.max(1); - let oracle_drain_start = remove_index.min(oracle.len()); - let oracle_drain_end = remove_index - .saturating_add(drain_len) - .min(oracle.len()) - .max(oracle_drain_start); - string.drain_interval_assign( - UsizeCO::checked_from_start_len(remove_index, drain_len).unwrap(), - ); - oracle.drain(oracle_drain_start..oracle_drain_end); - prop_assert_eq!( - string.to_vec().iter().copied().map(|value| value.code()).collect::>(), - oracle.clone(), - ); - } -} - -proptest! { - #[test] - fn three_and_seven_bit_access_slice_and_order_cross_word_boundaries( - left3 in prop::collection::vec(0u8..=7, 22..=128), - right3 in prop::collection::vec(0u8..=7, 22..=128), - left7 in prop::collection::vec(0u8..=127, 10..=128), - right7 in prop::collection::vec(0u8..=127, 10..=128), - start in 0usize..160, - len in 1usize..80, - ) { - assert_access_slice_order::(&left3, &right3, start, len, oct); - assert_access_slice_order::(&left7, &right7, start, len, wide); - } - - #[test] - fn three_and_seven_bit_matching_crosses_word_boundaries( - haystack3 in prop::collection::vec(0u8..=7, 22..=128), - needle3 in prop::collection::vec(0u8..=7, 0..=32), - haystack7 in prop::collection::vec(0u8..=127, 10..=128), - needle7 in prop::collection::vec(0u8..=127, 0..=16), - ) { - assert_matching::(&haystack3, &needle3, oct); - assert_matching::(&haystack7, &needle7, wide); - } - - #[test] - fn three_and_seven_bit_edits_cross_word_boundaries( - initial3 in prop::collection::vec(0u8..=7, 22..=128), - replacement3 in prop::collection::vec(0u8..=7, 0..=64), - initial7 in prop::collection::vec(0u8..=127, 10..=128), - replacement7 in prop::collection::vec(0u8..=127, 0..=32), - insert_index in 0usize..160, - remove_index in 0usize..160, - replace_start in 0usize..160, - ) { - assert_edits::( - &initial3, - &replacement3, - insert_index, - remove_index, - replace_start, - 2, - oct, - ); - assert_edits::( - &initial7, - &replacement7, - insert_index, - remove_index, - replace_start, - 2, - wide, - ); - } -} - -#[test] -fn packed_out_of_bounds_intervals_preserve_empty_semantics() { - let string = packed(&[0, 1, 2]); - let out_of_bounds = UsizeCO::checked_from_start_len(99, 1).unwrap(); - - assert!(string.slice(out_of_bounds).is_empty()); - - let mut drained = string.clone(); - drained.drain_interval_assign(out_of_bounds); - assert_eq!(drained, string); -} - -#[test] -fn packed_attribute_encoding_round_trips_through_packed_string() { - let string = PackedString::::from_chars([Symbol::Two, Symbol::Zero, Symbol::One]); - assert_eq!(string.get(0), Some(Symbol::Two)); - assert_eq!(string.get(1), Some(Symbol::Zero)); - assert_eq!(string.get(2), Some(Symbol::One)); -} - -#[derive(Clone, Copy, PartialEq, Eq)] -struct ManualSymbol; - -impl PackedChar<1> for ManualSymbol { - fn code(self) -> u8 { - 0 - } - - fn from_code(code: u8) -> Option { - (code == 0).then_some(Self) - } -} - -#[test] -fn manual_packed_char_implementations_remain_supported() { - assert_eq!( - PackedString::::from_chars([ManualSymbol]).char_len(), - 1 - ); -} - -#[test] -fn attribute_macro_generates_packed_char_impl() { - let string = - PackedString::::from_chars([PackedSymbol::One, PackedSymbol::Zero]); - assert_eq!(string.get(0), Some(PackedSymbol::One)); -} - -#[test] -fn packed_views_and_editing_remain_character_aligned() { - let mut string = PackedString::::from_chars([ - Symbol::Zero, - Symbol::One, - Symbol::Two, - Symbol::One, - ]); - - let view = string.as_packed_str(); - assert_eq!(view.char_len(), 4); - assert_eq!( - view.slice(UsizeCO::checked_from_start_len(1, 2).unwrap()) - .get(0), - Some(Symbol::One) - ); - assert_eq!(view.find(view.slice_until(1)), Some(0)); - assert!(view.contains(view.slice(UsizeCO::checked_from_start_len(1, 1).unwrap()))); - - let haystack_string = - PackedString::::from_chars([Symbol::Zero, Symbol::Zero, Symbol::One]); - let needle_string = PackedString::::from_chars([Symbol::Two]); - let haystack = haystack_string.as_packed_str(); - let needle = needle_string.as_packed_str(); - assert!(!haystack.contains(needle)); - assert_eq!(haystack.find(needle), None); - assert_eq!(haystack.rfind(needle), None); - assert!(!haystack.matches_at(1, needle)); - - string.insert(2, Symbol::Zero); - assert_eq!(string.remove(2), Symbol::Zero); - assert_eq!( - string.reverse().to_vec(), - vec![Symbol::One, Symbol::Two, Symbol::One, Symbol::Zero] - ); - string.retain(|symbol| symbol != Symbol::Two); - assert_eq!( - string.to_vec(), - vec![Symbol::Zero, Symbol::One, Symbol::One] - ); -} - -#[test] -fn from_bits_rejects_misaligned_and_unknown_codes() { - assert!(PackedString::::from_bits(BitString::from_iter([true])).is_none()); - assert!(PackedString::::from_bits(BitString::from_iter([true, true])).is_none()); -} - -#[test] -fn ordering_uses_packed_code_values() { - let one = PackedString::::from_chars([Symbol::One]); - let two = PackedString::::from_chars([Symbol::Two]); - assert!(one < two); - assert!(one.as_packed_str() < two.as_packed_str()); -}