361 lines
10 KiB
Rust
361 lines
10 KiB
Rust
use crate::borrow::ToOwned;
|
|
use crate::rc::Rc;
|
|
use crate::string::ToString;
|
|
use crate::test_helpers::test_rng;
|
|
use crate::vec::Vec;
|
|
|
|
use core::cell::Cell;
|
|
use core::cmp::Ordering::{self, Equal, Greater, Less};
|
|
use core::convert::identity;
|
|
use core::fmt;
|
|
use core::mem;
|
|
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
|
|
use rand::{distributions::Standard, prelude::*, Rng, RngCore};
|
|
use std::panic;
|
|
|
|
macro_rules! do_test {
|
|
($input:ident, $func:ident) => {
|
|
let len = $input.len();
|
|
|
|
// Work out the total number of comparisons required to sort
|
|
// this array...
|
|
let mut count = 0usize;
|
|
$input.to_owned().$func(|a, b| {
|
|
count += 1;
|
|
a.cmp(b)
|
|
});
|
|
|
|
// ... and then panic on each and every single one.
|
|
for panic_countdown in 0..count {
|
|
// Refresh the counters.
|
|
VERSIONS.store(0, Relaxed);
|
|
for i in 0..len {
|
|
DROP_COUNTS[i].store(0, Relaxed);
|
|
}
|
|
|
|
let v = $input.to_owned();
|
|
let _ = std::panic::catch_unwind(move || {
|
|
let mut v = v;
|
|
let mut panic_countdown = panic_countdown;
|
|
v.$func(|a, b| {
|
|
if panic_countdown == 0 {
|
|
SILENCE_PANIC.with(|s| s.set(true));
|
|
panic!();
|
|
}
|
|
panic_countdown -= 1;
|
|
a.cmp(b)
|
|
})
|
|
});
|
|
|
|
// Check that the number of things dropped is exactly
|
|
// what we expect (i.e., the contents of `v`).
|
|
for (i, c) in DROP_COUNTS.iter().enumerate().take(len) {
|
|
let count = c.load(Relaxed);
|
|
assert!(count == 1, "found drop count == {} for i == {}, len == {}", count, i, len);
|
|
}
|
|
|
|
// Check that the most recent versions of values were dropped.
|
|
assert_eq!(VERSIONS.load(Relaxed), 0);
|
|
}
|
|
};
|
|
}
|
|
|
|
const MAX_LEN: usize = 80;
|
|
|
|
static DROP_COUNTS: [AtomicUsize; MAX_LEN] = [
|
|
// FIXME(RFC 1109): AtomicUsize is not Copy.
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
AtomicUsize::new(0),
|
|
];
|
|
|
|
static VERSIONS: AtomicUsize = AtomicUsize::new(0);
|
|
|
|
#[derive(Clone, Eq)]
|
|
struct DropCounter {
|
|
x: u32,
|
|
id: usize,
|
|
version: Cell<usize>,
|
|
}
|
|
|
|
impl PartialEq for DropCounter {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.partial_cmp(other) == Some(Ordering::Equal)
|
|
}
|
|
}
|
|
|
|
impl PartialOrd for DropCounter {
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
self.version.set(self.version.get() + 1);
|
|
other.version.set(other.version.get() + 1);
|
|
VERSIONS.fetch_add(2, Relaxed);
|
|
self.x.partial_cmp(&other.x)
|
|
}
|
|
}
|
|
|
|
impl Ord for DropCounter {
|
|
fn cmp(&self, other: &Self) -> Ordering {
|
|
self.partial_cmp(other).unwrap()
|
|
}
|
|
}
|
|
|
|
impl Drop for DropCounter {
|
|
fn drop(&mut self) {
|
|
DROP_COUNTS[self.id].fetch_add(1, Relaxed);
|
|
VERSIONS.fetch_sub(self.version.get(), Relaxed);
|
|
}
|
|
}
|
|
|
|
std::thread_local!(static SILENCE_PANIC: Cell<bool> = Cell::new(false));
|
|
|
|
#[test]
|
|
#[cfg_attr(target_os = "emscripten", ignore)] // no threads
|
|
#[cfg_attr(not(panic = "unwind"), ignore = "test requires unwinding support")]
|
|
fn panic_safe() {
|
|
panic::update_hook(move |prev, info| {
|
|
if !SILENCE_PANIC.with(|s| s.get()) {
|
|
prev(info);
|
|
}
|
|
});
|
|
|
|
let mut rng = test_rng();
|
|
|
|
// Miri is too slow (but still need to `chain` to make the types match)
|
|
let lens = if cfg!(miri) { (1..10).chain(0..0) } else { (1..20).chain(70..MAX_LEN) };
|
|
let moduli: &[u32] = if cfg!(miri) { &[5] } else { &[5, 20, 50] };
|
|
|
|
for len in lens {
|
|
for &modulus in moduli {
|
|
for &has_runs in &[false, true] {
|
|
let mut input = (0..len)
|
|
.map(|id| DropCounter {
|
|
x: rng.next_u32() % modulus,
|
|
id: id,
|
|
version: Cell::new(0),
|
|
})
|
|
.collect::<Vec<_>>();
|
|
|
|
if has_runs {
|
|
for c in &mut input {
|
|
c.x = c.id as u32;
|
|
}
|
|
|
|
for _ in 0..5 {
|
|
let a = rng.gen::<usize>() % len;
|
|
let b = rng.gen::<usize>() % len;
|
|
if a < b {
|
|
input[a..b].reverse();
|
|
} else {
|
|
input.swap(a, b);
|
|
}
|
|
}
|
|
}
|
|
|
|
do_test!(input, sort_by);
|
|
do_test!(input, sort_unstable_by);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set default panic hook again.
|
|
drop(panic::take_hook());
|
|
}
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore)] // Miri is too slow
|
|
fn test_sort() {
|
|
let mut rng = test_rng();
|
|
|
|
for len in (2..25).chain(500..510) {
|
|
for &modulus in &[5, 10, 100, 1000] {
|
|
for _ in 0..10 {
|
|
let orig: Vec<_> = (&mut rng)
|
|
.sample_iter::<i32, _>(&Standard)
|
|
.map(|x| x % modulus)
|
|
.take(len)
|
|
.collect();
|
|
|
|
// Sort in default order.
|
|
let mut v = orig.clone();
|
|
v.sort();
|
|
assert!(v.windows(2).all(|w| w[0] <= w[1]));
|
|
|
|
// Sort in ascending order.
|
|
let mut v = orig.clone();
|
|
v.sort_by(|a, b| a.cmp(b));
|
|
assert!(v.windows(2).all(|w| w[0] <= w[1]));
|
|
|
|
// Sort in descending order.
|
|
let mut v = orig.clone();
|
|
v.sort_by(|a, b| b.cmp(a));
|
|
assert!(v.windows(2).all(|w| w[0] >= w[1]));
|
|
|
|
// Sort in lexicographic order.
|
|
let mut v1 = orig.clone();
|
|
let mut v2 = orig.clone();
|
|
v1.sort_by_key(|x| x.to_string());
|
|
v2.sort_by_cached_key(|x| x.to_string());
|
|
assert!(v1.windows(2).all(|w| w[0].to_string() <= w[1].to_string()));
|
|
assert!(v1 == v2);
|
|
|
|
// Sort with many pre-sorted runs.
|
|
let mut v = orig.clone();
|
|
v.sort();
|
|
v.reverse();
|
|
for _ in 0..5 {
|
|
let a = rng.gen::<usize>() % len;
|
|
let b = rng.gen::<usize>() % len;
|
|
if a < b {
|
|
v[a..b].reverse();
|
|
} else {
|
|
v.swap(a, b);
|
|
}
|
|
}
|
|
v.sort();
|
|
assert!(v.windows(2).all(|w| w[0] <= w[1]));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort using a completely random comparison function.
|
|
// This will reorder the elements *somehow*, but won't panic.
|
|
let mut v = [0; 500];
|
|
for i in 0..v.len() {
|
|
v[i] = i as i32;
|
|
}
|
|
v.sort_by(|_, _| *[Less, Equal, Greater].choose(&mut rng).unwrap());
|
|
v.sort();
|
|
for i in 0..v.len() {
|
|
assert_eq!(v[i], i as i32);
|
|
}
|
|
|
|
// Should not panic.
|
|
[0i32; 0].sort();
|
|
[(); 10].sort();
|
|
[(); 100].sort();
|
|
|
|
let mut v = [0xDEADBEEFu64];
|
|
v.sort();
|
|
assert!(v == [0xDEADBEEF]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_sort_stability() {
|
|
// Miri is too slow
|
|
let large_range = if cfg!(miri) { 0..0 } else { 500..510 };
|
|
let rounds = if cfg!(miri) { 1 } else { 10 };
|
|
|
|
let mut rng = test_rng();
|
|
for len in (2..25).chain(large_range) {
|
|
for _ in 0..rounds {
|
|
let mut counts = [0; 10];
|
|
|
|
// create a vector like [(6, 1), (5, 1), (6, 2), ...],
|
|
// where the first item of each tuple is random, but
|
|
// the second item represents which occurrence of that
|
|
// number this element is, i.e., the second elements
|
|
// will occur in sorted order.
|
|
let orig: Vec<_> = (0..len)
|
|
.map(|_| {
|
|
let n = rng.gen::<usize>() % 10;
|
|
counts[n] += 1;
|
|
(n, counts[n])
|
|
})
|
|
.collect();
|
|
|
|
let mut v = orig.clone();
|
|
// Only sort on the first element, so an unstable sort
|
|
// may mix up the counts.
|
|
v.sort_by(|&(a, _), &(b, _)| a.cmp(&b));
|
|
|
|
// This comparison includes the count (the second item
|
|
// of the tuple), so elements with equal first items
|
|
// will need to be ordered with increasing
|
|
// counts... i.e., exactly asserting that this sort is
|
|
// stable.
|
|
assert!(v.windows(2).all(|w| w[0] <= w[1]));
|
|
|
|
let mut v = orig.clone();
|
|
v.sort_by_cached_key(|&(x, _)| x);
|
|
assert!(v.windows(2).all(|w| w[0] <= w[1]));
|
|
}
|
|
}
|
|
}
|