use std::borrow::Cow;
use rand::Rng;
use num;
use strategy::*;
use test_runner::*;
type CharRange = (char, char);
pub const DEFAULT_SPECIAL_CHARS: &[char] = &[
'/', '\\', '$', '.', '*', '{', '\'', '"', '`', ':',
'?', '%', '=', '&', '<',
'\x00', '\t', '\r', '\n', '\x0B', '\x1B', '\x7F',
'¥',
'Ѩ',
'\u{FEFF}', '\u{FFFD}', '🕴',
];
pub const DEFAULT_PREFERRED_RANGES: &[CharRange] = &[
(' ', '~'), (' ', '~'), (' ', '~'), (' ', '~'), (' ', '~'),
('\u{0040}', '\u{00ff}'),
];
pub fn select_char<R : Rng>(rnd: &mut R,
special: &[char],
preferred: &[CharRange],
ranges: &[CharRange]) -> char {
let (base, offset) = select_range_index(rnd, special, preferred, ranges);
::std::char::from_u32(base + offset).expect("bad character selected")
}
fn select_range_index<R : Rng>(rnd: &mut R,
special: &[char],
preferred: &[CharRange],
ranges: &[CharRange])
-> (u32, u32) {
fn in_range(ranges: &[CharRange], ch: char) -> Option<(u32, u32)> {
ranges.iter().find(|&&(lo, hi)| ch >= lo && ch <= hi).map(
|&(lo, _)| (lo as u32, ch as u32 - lo as u32))
}
if !special.is_empty() && rnd.gen() {
let s = special[rnd.gen_range(0, special.len())];
if let Some(ret) = in_range(ranges, s) { return ret; }
}
if !preferred.is_empty() && rnd.gen() {
let (lo, hi) = preferred[rnd.gen_range(0, preferred.len())];
if let Some(ch) = ::std::char::from_u32(
rnd.gen_range(lo as u32, hi as u32 + 1))
{
if let Some(ret) = in_range(ranges, ch) { return ret; }
}
}
for _ in 0..65_536 {
let (lo, hi) = ranges[rnd.gen_range(0, ranges.len())];
if let Some(ch) = ::std::char::from_u32(
rnd.gen_range(lo as u32, hi as u32 + 1))
{ return (lo as u32, ch as u32 - lo as u32); }
}
(ranges[0].0 as u32, 0)
}
#[derive(Debug, Clone)]
pub struct CharStrategy<'a> {
special: Cow<'a, [char]>,
preferred: Cow<'a, [CharRange]>,
ranges: Cow<'a, [CharRange]>,
}
impl<'a> CharStrategy<'a> {
pub fn new(special: Cow<'a, [char]>,
preferred: Cow<'a, [CharRange]>,
ranges: Cow<'a, [CharRange]>) -> Self {
CharStrategy {
special: special,
preferred: preferred,
ranges: ranges,
}
}
pub fn new_borrowed(special: &'a [char],
preferred: &'a [CharRange],
ranges: &'a [CharRange]) -> Self {
CharStrategy::new(
Cow::Borrowed(special),
Cow::Borrowed(preferred),
Cow::Borrowed(ranges),
)
}
}
const WHOLE_RANGE: &[CharRange] = &[
('\x00', ::std::char::MAX)
];
pub fn any() -> CharStrategy<'static> {
CharStrategy {
special: Cow::Borrowed(DEFAULT_SPECIAL_CHARS),
preferred: Cow::Borrowed(DEFAULT_PREFERRED_RANGES),
ranges: Cow::Borrowed(WHOLE_RANGE),
}
}
pub fn range(start: char, end: char) -> CharStrategy<'static> {
CharStrategy {
special: Cow::Borrowed(DEFAULT_SPECIAL_CHARS),
preferred: Cow::Borrowed(DEFAULT_PREFERRED_RANGES),
ranges: Cow::Owned(vec![(start, end)]),
}
}
pub fn ranges(ranges: Cow<[CharRange]>) -> CharStrategy {
CharStrategy {
special: Cow::Borrowed(DEFAULT_SPECIAL_CHARS),
preferred: Cow::Borrowed(DEFAULT_PREFERRED_RANGES),
ranges: ranges,
}
}
#[derive(Debug, Clone, Copy)]
pub struct CharValueTree {
value: num::u32::BinarySearch,
}
impl<'a> Strategy for CharStrategy<'a> {
type Value = CharValueTree;
fn new_value(&self, runner: &mut TestRunner) -> NewTree<Self> {
let (base, offset) = select_range_index(
runner.rng(), &self.special, &self.preferred, &self.ranges);
let start = base + offset;
let bottom = if start >= '¡' as u32 && base < '¡' as u32 {
'¡' as u32
} else if start >= 'a' as u32 && base < 'a' as u32 {
'a' as u32
} else if start >= 'A' as u32 && base < 'A' as u32 {
'A' as u32
} else if start >= '0' as u32 && base < '0' as u32 {
'0' as u32
} else if start >= ' ' as u32 && base < ' ' as u32 {
' ' as u32
} else {
base
};
Ok(CharValueTree {
value: num::u32::BinarySearch::new_above(bottom, start)
})
}
}
impl CharValueTree {
fn reposition(&mut self) {
while ::std::char::from_u32(self.value.current()).is_none() {
if !self.value.complicate() {
panic!("Converged to non-char value");
}
}
}
}
impl ValueTree for CharValueTree {
type Value = char;
fn current(&self) -> char {
::std::char::from_u32(self.value.current()).expect(
"Generated non-char value")
}
fn simplify(&mut self) -> bool {
if self.value.simplify() {
self.reposition();
true
} else {
false
}
}
fn complicate(&mut self) -> bool {
if self.value.complicate() {
self.reposition();
true
} else {
false
}
}
}
#[cfg(test)]
mod test {
use std::cmp::{min, max};
use super::*;
use collection;
#[test]
fn stays_in_range() {
let meta_input = collection::vec(
(0..::std::char::MAX as u32,
0..::std::char::MAX as u32),
1..5);
TestRunner::default().run(
&meta_input, |input_ranges| {
let input = ranges(Cow::Owned(input_ranges.iter().map(
|&(lo, hi)| ::std::char::from_u32(lo).and_then(
|lo| ::std::char::from_u32(hi).map(
|hi| (min(lo, hi), max(lo, hi))))
.ok_or_else(|| TestCaseError::reject("non-char")))
.collect::<Result<Vec<CharRange>,_>>()?));
let mut runner = TestRunner::default();
for _ in 0..256 {
let mut value = input.new_value(&mut runner).unwrap();
loop {
let ch = value.current() as u32;
assert!(input_ranges.iter().any(
|&(lo, hi)| ch >= min(lo, hi) &&
ch <= max(lo, hi)));
if !value.simplify() { break; }
}
}
Ok(())
}).unwrap()
}
#[test]
fn applies_desired_bias() {
let mut men_in_business_suits_levitating = 0;
let mut ascii_printable = 0;
let mut runner = TestRunner::default();
for _ in 0..1024 {
let ch = any().new_value(&mut runner).unwrap().current();
if '🕴' == ch {
men_in_business_suits_levitating += 1;
} else if ch >= ' ' && ch <= '~' {
ascii_printable += 1;
}
}
assert!(ascii_printable >= 256);
assert!(men_in_business_suits_levitating >= 1);
}
#[test]
fn doesnt_shrink_to_ascii_control() {
let mut accepted = 0;
let mut runner = TestRunner::default();
for _ in 0..256 {
let mut value = any().new_value(&mut runner).unwrap();
if value.current() <= ' ' { continue; }
while value.simplify() { }
assert!(value.current() >= ' ');
accepted += 1;
}
assert!(accepted >= 200);
}
#[test]
fn test_sanity() {
check_strategy_sanity(any(), Some(CheckStrategySanityOptions {
strict_complicate_after_simplify: false,
.. CheckStrategySanityOptions::default()
}));
}
}