use turso_core::Value; use crate::{ generation::{ArbitraryFrom, GenerationContext}, model::table::{ColumnType, SimValue}, }; pub struct LTValue(pub SimValue); impl ArbitraryFrom<(&SimValue, ColumnType)> for LTValue { fn arbitrary_from( rng: &mut R, _context: &C, (value, _col_type): (&SimValue, ColumnType), ) -> Self { let new_value = match &value.0 { Value::Integer(i) => Value::Integer(rng.random_range(i64::MIN..*i - 1)), Value::Float(f) => Value::Float(f - rng.random_range(0.0..1e10)), value @ Value::Text(..) => { // Either shorten the string, or make at least one character smaller and mutate the rest let mut t = value.to_string(); if rng.random_bool(0.01) { t.pop(); Value::build_text(t) } else { Value::build_text(mutate_string(&t, rng, MutationType::Decrement)) } } Value::Blob(b) => { // Either shorten the blob, or make at least one byte smaller and mutate the rest let mut b = b.clone(); if rng.random_bool(0.01) { b.pop(); Value::Blob(b) } else { let index = rng.random_range(0..b.len()); b[index] -= 1; // Mutate the rest of the blob for val in b.iter_mut().skip(index + 1) { *val = rng.random_range(0..=255); } Value::Blob(b) } } // A value with storage class NULL is considered less than any other value (including another value with storage class NULL) Value::Null => Value::Null, }; Self(SimValue(new_value)) } } pub struct GTValue(pub SimValue); impl ArbitraryFrom<(&SimValue, ColumnType)> for GTValue { fn arbitrary_from( rng: &mut R, context: &C, (value, col_type): (&SimValue, ColumnType), ) -> Self { let new_value = match &value.0 { Value::Integer(i) => Value::Integer(rng.random_range(*i..i64::MAX)), Value::Float(f) => Value::Float(rng.random_range(*f..1e10)), value @ Value::Text(..) => { // Either lengthen the string, or make at least one character smaller and mutate the rest let mut t = value.to_string(); if rng.random_bool(0.01) { if rng.random_bool(0.5) { t.push(rng.random_range(UPPERCASE_A..=UPPERCASE_Z) as u8 as char); } else { t.push(rng.random_range(LOWERCASE_A..=LOWERCASE_Z) as u8 as char); } Value::build_text(t) } else { Value::build_text(mutate_string(&t, rng, MutationType::Increment)) } } Value::Blob(b) => { // Either lengthen the blob, or make at least one byte smaller and mutate the rest let mut b = b.clone(); if rng.random_bool(0.01) { b.push(rng.random_range(0..=255)); Value::Blob(b) } else { let index = rng.random_range(0..b.len()); b[index] += 1; // Mutate the rest of the blob for val in b.iter_mut().skip(index + 1) { *val = rng.random_range(0..=255); } Value::Blob(b) } } Value::Null => { // Any value is greater than NULL, except NULL SimValue::arbitrary_from(rng, context, col_type).0 } }; Self(SimValue(new_value)) } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] enum MutationType { Decrement, Increment, } const UPPERCASE_A: u32 = 'A' as u32; const UPPERCASE_Z: u32 = 'Z' as u32; const LOWERCASE_A: u32 = 'a' as u32; const LOWERCASE_Z: u32 = 'z' as u32; fn mutate_string( t: &str, rng: &mut R, mutation_type: MutationType, ) -> String { let mut chars = t.chars().map(|c| c as u32).collect::>(); let mut index; let mut max_loops = 100; loop { index = rng.random_range(0..chars.len()); if chars[index] > UPPERCASE_A && chars[index] < UPPERCASE_Z || chars[index] > LOWERCASE_A && chars[index] < LOWERCASE_Z { break; } max_loops -= 1; if max_loops == 0 { panic!("Failed to find a printable character to decrement"); } } if mutation_type == MutationType::Decrement { chars[index] -= 1; } else { chars[index] += 1; } // Mutate the rest of the string with printable ASCII characters for val in chars.iter_mut().skip(index + 1) { if rng.random_bool(0.5) { *val = rng.random_range(UPPERCASE_A..=UPPERCASE_Z); } else { *val = rng.random_range(LOWERCASE_A..=LOWERCASE_Z); } } chars .into_iter() .map(|c| char::from_u32(c).unwrap()) .collect::() } #[cfg(test)] mod tests { use anarchist_readable_name_generator_lib::readable_name; use super::*; #[test] fn test_mutate_string_fuzz() { let mut rng = rand::rng(); for _ in 0..1000 { let mut t = readable_name(); while !t.is_ascii() { t = readable_name(); } let t2 = mutate_string(&t, &mut rng, MutationType::Decrement); assert!(t2.is_ascii(), "{}", t); assert!(t2 < t); } for _ in 0..1000 { let mut t = readable_name(); while !t.is_ascii() { t = readable_name(); } let t2 = mutate_string(&t, &mut rng, MutationType::Increment); assert!(t2.is_ascii(), "{}", t); assert!(t2 > t); } } }