use super::*;
use quickcheck::{TestResult, quickcheck};
fn any_timebase((num, den): (u32, u32)) -> Timebase {
const MAX: u32 = i32::MAX as u32;
Timebase::new((num % (MAX + 1)) as i32, nz((den % MAX + 1) as i32))
}
fn target_timebase((num, den): (u32, u32)) -> Timebase {
const MAX: u32 = i32::MAX as u32;
Timebase::new((num % MAX + 1) as i32, nz((den % MAX + 1) as i32))
}
fn coarse_timebase((num, den): (u32, u32)) -> Timebase {
Timebase::new((num % 3) as i32, nz((den % 3 + 1) as i32))
}
fn exact_quotient(pts: i64, from: Timebase, to: Timebase) -> (i128, i128) {
(
(pts as i128) * (from.num() as i128) * (to.den().get() as i128),
(from.den().get() as i128) * (to.num() as i128),
)
}
fn fold(name: &str, upper: bool) -> String {
name
.chars()
.map(|c| {
if upper {
c.to_ascii_uppercase()
} else {
c.to_ascii_lowercase()
}
})
.collect()
}
fn hash_of<T: Hash>(v: &T) -> u64 {
let mut h = std::collections::hash_map::DefaultHasher::new();
v.hash(&mut h);
h.finish()
}
quickcheck! {
fn rescale_into_the_same_timebase_is_the_identity(pts: i64, tb: (u32, u32)) -> bool {
let tb = target_timebase(tb);
tb.checked_rescale(pts, tb) == Some(pts) && tb.saturating_rescale(pts, tb) == pts
}
fn rescale_lands_within_half_a_tick(pts: i64, from: (u32, u32), to: (u32, u32)) -> TestResult {
let from = any_timebase(from);
let to = target_timebase(to);
let Some(q) = from.checked_rescale(pts, to) else {
return TestResult::discard();
};
let (n, d) = exact_quotient(pts, from, to);
TestResult::from_bool(2 * (n - (q as i128) * d).abs() <= d)
}
fn rescale_breaks_ties_away_from_zero(pts: i64) -> bool {
let half_seconds = Timebase::new(1, nz(2));
let pts = pts | 1;
let away = ((pts as i128) + (pts.signum() as i128)) / 2;
half_seconds.checked_rescale(pts, Timebase::SECONDS) == Some(away as i64)
}
fn rescale_preserves_semantic_order(a: (i8, u32, u32), b: (i8, u32, u32), to: (u32, u32)) -> bool {
let x = Timestamp::new(a.0 as i64, any_timebase((a.1, a.2)));
let y = Timestamp::new(b.0 as i64, any_timebase((b.1, b.2)));
let to = target_timebase(to);
let (rx, ry) = (x.rescale_to(to).pts(), y.rescale_to(to).pts());
match x.cmp_semantic(&y) {
Ordering::Less => rx <= ry,
Ordering::Greater => rx >= ry,
Ordering::Equal => rx == ry,
}
}
fn instant_semantic_order_is_transitive(a: (i8, u32, u32), b: (i8, u32, u32), c: (i8, u32, u32)) -> bool {
let at = |(pts, num, den): (i8, u32, u32)| Timestamp::new(pts as i64, coarse_timebase((num, den)));
let (x, y, z) = (at(a), at(b), at(c));
!(x.cmp_semantic(&y).is_le() && y.cmp_semantic(&z).is_le()) || x.cmp_semantic(&z).is_le()
}
fn every_instant_in_a_degenerate_timebase_is_instant_zero(a: (i64, u32), b: (i64, u32), tb: (u32, u32)) -> bool {
let nowhere = |(pts, den): (i64, u32)| Timestamp::new(pts, Timebase::new(0, nz((den % 4 + 1) as i32)));
let (x, y) = (nowhere(a), nowhere(b));
let origin = Timestamp::new(0, any_timebase(tb));
x == y && x == origin && hash_of(&x) == hash_of(&y) && hash_of(&x) == hash_of(&origin)
}
fn duration_to_pts_is_a_rescale_out_of_nanos(secs: u32, nanos: u32, tb: (u32, u32)) -> bool {
let d = Duration::new(secs as u64, nanos % 1_000_000_000);
let tb = any_timebase(tb);
tb.checked_duration_to_pts(d) == Timebase::NANOS.checked_rescale(d.as_nanos() as i64, tb)
}
fn pts_to_duration_inverts_on_whole_nanosecond_ticks(pts: u32, which: usize) -> bool {
const WHOLE_NANOSECOND_TICKS: &[Timebase] = &[
Timebase::SECONDS,
Timebase::MILLIS,
Timebase::MICROS,
Timebase::NANOS,
Timebase::FILM_24,
Timebase::PAL_25,
Timebase::HZ_48K,
];
let tb = WHOLE_NANOSECOND_TICKS[which % WHOLE_NANOSECOND_TICKS.len()];
let pts = pts as i64;
tb.checked_pts_to_duration(pts).and_then(|d| tb.checked_duration_to_pts(d)) == Some(pts)
}
fn the_rescale_rungs_agree(pts: i64, from: (u32, u32), to: (u32, u32)) -> bool {
let (from, to) = (any_timebase(from), target_timebase(to));
match from.checked_rescale(pts, to) {
Some(q) => from.saturating_rescale(pts, to) == q,
None => true,
}
}
fn the_duration_to_pts_rungs_agree(secs: u32, nanos: u32, tb: (u32, u32)) -> bool {
let d = Duration::new(secs as u64, nanos % 1_000_000_000);
let tb = any_timebase(tb);
match tb.checked_duration_to_pts(d) {
Some(q) => tb.saturating_duration_to_pts(d) == q,
None => true,
}
}
fn the_pts_to_duration_rungs_agree(pts: i64, tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
match tb.checked_pts_to_duration(pts) {
Some(q) => tb.saturating_pts_to_duration(pts) == q,
None => true,
}
}
fn reduce_canonicalizes_without_moving_the_value(tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
let reduced = tb.reduce();
reduced == tb
&& reduced.is_reduced()
&& format!("{:?}", reduced.reduce()) == format!("{reduced:?}")
&& hash_of(&reduced) == hash_of(&tb)
}
fn the_name_table_reads_both_ways(tb: (u32, u32), upper: bool) -> bool {
let tb = any_timebase(tb);
match tb.well_known_name() {
Some(name) => Timebase::from_name(name) == Some(tb) && Timebase::from_name(&fold(name, upper)) == Some(tb),
None => true,
}
}
fn checked_recip_is_its_own_inverse(tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
match tb.checked_recip().and_then(Timebase::checked_recip) {
Some(back) => format!("{back:?}") == format!("{tb:?}"),
None => tb.num() == 0,
}
}
fn negating_a_span_twice_returns_it(ticks: i64, tb: (u32, u32)) -> bool {
let span = SignedDuration::new(ticks, any_timebase(tb));
match span.checked_neg().and_then(SignedDuration::checked_neg) {
Some(back) => back == span,
None => ticks == i64::MIN,
}
}
fn abs_is_the_magnitude_of_a_span(ticks: i64, tb: (u32, u32)) -> bool {
let span = SignedDuration::new(ticks, any_timebase(tb));
match span.checked_abs() {
Some(magnitude) => {
!magnitude.is_negative()
&& (magnitude == span || Some(magnitude) == span.checked_neg())
&& magnitude.checked_abs() == Some(magnitude)
}
None => ticks == i64::MIN,
}
}
fn adding_a_span_and_subtracting_it_returns_the_first(a: i64, b: i64, tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
let (x, y) = (SignedDuration::new(a, tb), SignedDuration::new(b, tb));
match x.checked_add(y) {
Some(sum) => sum.checked_sub(y) == Some(x),
None => true,
}
}
fn spans_in_one_timebase_add_as_i64s(a: i64, b: i64, tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
let (x, y) = (SignedDuration::new(a, tb), SignedDuration::new(b, tb));
x.checked_add(y).map(|sum| sum.ticks()) == a.checked_add(b)
&& x.saturating_add(y).ticks() == a.saturating_add(b)
&& x.checked_sub(y).map(|d| d.ticks()) == a.checked_sub(b)
&& x.saturating_sub(y).ticks() == a.saturating_sub(b)
}
fn rescaling_spans_preserves_semantic_order(a: (i8, u32, u32), b: (i8, u32, u32), to: (u32, u32)) -> bool {
let x = SignedDuration::new(a.0 as i64, any_timebase((a.1, a.2)));
let y = SignedDuration::new(b.0 as i64, any_timebase((b.1, b.2)));
let to = target_timebase(to);
let (rx, ry) = (x.rescale_to(to).ticks(), y.rescale_to(to).ticks());
match x.cmp_semantic(&y) {
Ordering::Less => rx <= ry,
Ordering::Greater => rx >= ry,
Ordering::Equal => rx == ry,
}
}
fn span_semantic_order_is_transitive(a: (i8, u32, u32), b: (i8, u32, u32), c: (i8, u32, u32)) -> bool {
let span =
|(t, num, den): (i8, u32, u32)| SignedDuration::new(t as i64, coarse_timebase((num, den)));
let (x, y, z) = (span(a), span(b), span(c));
!(x.cmp_semantic(&y).is_le() && y.cmp_semantic(&z).is_le()) || x.cmp_semantic(&z).is_le()
}
fn every_span_in_a_degenerate_timebase_measures_zero(a: (i64, u32), b: (i64, u32), tb: (u32, u32)) -> bool {
let nowhere = |(ticks, den): (i64, u32)| SignedDuration::new(ticks, Timebase::new(0, nz((den % 4 + 1) as i32)));
let (x, y) = (nowhere(a), nowhere(b));
x.cmp_semantic(&y).is_eq() && x.cmp_semantic(&SignedDuration::new(0, any_timebase(tb))).is_eq()
}
fn a_rate_is_its_timebase_read_backwards(tb: (u32, u32)) -> bool {
let rational = any_timebase(tb);
let rate = Rate::fps(rational.num(), rational.den());
match rate.checked_to_timebase().and_then(Rate::checked_from_timebase) {
Some(back) => format!("{back:?}") == format!("{rate:?}"),
None => rate.num() == 0,
}
}
fn the_rate_name_table_reads_both_ways(tb: (u32, u32), upper: bool) -> bool {
let rational = any_timebase(tb);
let rate = Rate::fps(rational.num(), rational.den());
match rate.well_known_name() {
Some(name) => Rate::from_name(name) == Some(rate) && Rate::from_name(&fold(name, upper)) == Some(rate),
None => true,
}
}
fn whole_seconds_of_frames_are_whole_seconds(rate: u16, secs: u16) -> bool {
let rate = (rate % 1000) as i64 + 1;
let frames = rate * (secs as i64);
Rate::hz(rate as i32).checked_frames_to_duration(frames)
== Some(Duration::from_secs(secs as u64))
}
fn the_frames_to_duration_rungs_agree(frames: i64, tb: (u32, u32)) -> bool {
let rational = target_timebase(tb);
let rate = Rate::fps(rational.num(), rational.den());
match rate.checked_frames_to_duration(frames) {
Some(d) => rate.saturating_frames_to_duration(frames) == d,
None => true,
}
}
fn a_span_parses_back_from_its_rendering(ticks: i64, tb: (u32, u32)) -> bool {
let span = SignedDuration::new(ticks, any_timebase(tb));
format!("{span}").parse::<SignedDuration>().map(|parsed| format!("{parsed:?}"))
== Ok(format!("{span:?}"))
}
fn a_rate_parses_back_from_its_rendering(tb: (u32, u32)) -> bool {
let rational = any_timebase(tb);
let rate = Rate::fps(rational.num(), rational.den());
format!("{rate}").parse::<Rate>().map(|parsed| format!("{parsed:?}"))
== Ok(format!("{rate:?}"))
}
fn rendering_a_parsed_rate_settles_after_one_pass(tb: (u32, u32), upper: bool) -> bool {
let rational = any_timebase(tb);
let rate = Rate::fps(rational.num(), rational.den());
let written = match rate.well_known_name() {
Some(name) => fold(name, upper),
None => format!("{rate}"),
};
match written.parse::<Rate>() {
Ok(once) => match format!("{once}").parse::<Rate>() {
Ok(twice) => once == rate && format!("{once}") == format!("{twice}"),
Err(_) => false,
},
Err(_) => false,
}
}
fn a_shift_and_the_span_it_moved_by_are_inverses(pts: i64, ticks: i64, tb: (u32, u32)) -> bool {
let tb = any_timebase(tb);
let ts = Timestamp::new(pts, tb);
let span = SignedDuration::new(ticks, tb);
match ts.checked_add_signed(span) {
Some(shifted) => shifted.checked_signed_duration_since(&ts) == Some(span),
None => true,
}
}
}