#[cfg(test)]
mod test {
use crate::{rates, Framerate, Timecode, TimecodeParseError};
use rstest::rstest;
use std::fmt::{Debug, Display};
use std::ops::{Div, DivAssign, Mul, MulAssign, Rem, RemAssign};
struct ComparisonCase {
tc1: Timecode,
tc2: Timecode,
eq: bool,
lt: bool,
}
#[rstest]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
tc2: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
eq: true,
lt: false,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
tc2: Timecode::with_frames("00:59:59:24", rates::F24).unwrap(),
eq: true,
lt: false,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
tc2: Timecode::with_frames("02:00:00:00", rates::F24).unwrap(),
eq: false,
lt: true,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
tc2: Timecode::with_frames("01:00:00:01", rates::F24).unwrap(),
eq: false,
lt: true,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
tc2: Timecode::with_frames("00:59:59:23", rates::F24).unwrap(),
eq: false,
lt: false,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
tc2: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
eq: true,
lt: false,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
tc2: Timecode::with_frames("01:00:00:01", rates::F23_98).unwrap(),
eq: false,
lt: true,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("00:00:00:00", rates::F23_98).unwrap(),
tc2: Timecode::with_frames("02:00:00:01", rates::F23_98).unwrap(),
eq: false,
lt: true,
})]
#[case(ComparisonCase{
tc1: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
tc2: Timecode::with_frames("01:00:00:01", rates::F24).unwrap(),
eq: false,
lt: false,
})]
fn test_comparison(#[case] case: ComparisonCase) {
assert_eq!(
case.eq,
case.tc1 == case.tc2,
"{} == {}",
case.tc1,
case.tc2
);
assert_eq!(
case.eq,
case.tc2 == case.tc1,
"{} == {} (flipped)",
case.tc2,
case.tc1
);
assert_eq!(case.lt, case.tc1 < case.tc2, "{} < {}", case.tc1, case.tc2);
let mut expected = !case.lt && !case.eq;
assert_eq!(
expected,
case.tc2 < case.tc1,
"{} < {} (flipped)",
case.tc2,
case.tc1
);
expected = case.lt || case.eq;
assert_eq!(
expected,
case.tc1 <= case.tc2,
"{} <= {}",
case.tc1,
case.tc2
);
expected = !case.lt || case.eq;
assert_eq!(
expected,
case.tc2 <= case.tc1,
"{} <= {} (flipped)",
case.tc2,
case.tc1
);
expected = !case.eq && !case.lt;
assert_eq!(expected, case.tc1 > case.tc2, "{} > {}", case.tc1, case.tc2);
expected = case.lt;
assert_eq!(
expected,
case.tc2 > case.tc1,
"{} > {} (flipped)",
case.tc2,
case.tc1
);
expected = case.eq || !case.lt;
assert_eq!(
expected,
case.tc1 >= case.tc2,
"{} >= {}",
case.tc1,
case.tc2
);
expected = case.eq || case.lt;
assert_eq!(
expected,
case.tc2 >= case.tc1,
"{} >= {} (flipped)",
case.tc2,
case.tc1
);
}
struct SortCase {
tcs_in: Vec<Timecode>,
tcs_out: Vec<Timecode>,
}
#[rstest]
#[case(SortCase {
tcs_in: vec![
Timecode::with_frames("00:01:00:00", rates::F23_98).unwrap(),
Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
Timecode::with_frames("00:00:10:00", rates::F23_98).unwrap(),
],
tcs_out: vec![
Timecode::with_frames("00:00:10:00", rates::F23_98).unwrap(),
Timecode::with_frames("00:01:00:00", rates::F23_98).unwrap(),
Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
],
})]
fn test_sort_timecodes(#[case] mut case: SortCase) {
case.tcs_in.sort();
assert_eq!(case.tcs_out, case.tcs_in, "timecodes sorted correctly.")
}
struct ArithmeticCase {
tc1: String,
tc2: String,
expected: String,
}
#[rstest]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "01:00:00:00".to_string(),
expected: "02:00:00:00".to_string(),
})]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "00:00:00:01".to_string(),
expected: "01:00:00:01".to_string(),
})]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "-00:30:00:00".to_string(),
expected: "00:30:00:00".to_string(),
})]
fn test_add(#[case] case: ArithmeticCase) -> Result<(), TimecodeParseError> {
let tc1 = Timecode::with_frames(case.tc1, rates::F24)?;
let tc2 = Timecode::with_frames(case.tc2, rates::F24)?;
let expected = Timecode::with_frames(case.expected, rates::F24)?;
assert_eq!(expected, tc1 + tc2, "{} + {} == {}", tc1, tc2, expected);
let mut tc = tc1;
tc += tc2;
assert_eq!(expected, tc, "{} += {} == {}", tc1, tc2, expected);
Ok(())
}
#[rstest]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "01:00:00:00".to_string(),
expected: "00:00:00:00".to_string(),
})]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "00:00:00:01".to_string(),
expected: "00:59:59:23".to_string(),
})]
#[case(ArithmeticCase{
tc1: "01:00:00:00".to_string(),
tc2: "-00:30:00:00".to_string(),
expected: "01:30:00:00".to_string(),
})]
fn test_subtract(#[case] case: ArithmeticCase) -> Result<(), TimecodeParseError> {
let tc1 = Timecode::with_frames(case.tc1, rates::F24)?;
let tc2 = Timecode::with_frames(case.tc2, rates::F24)?;
let expected = Timecode::with_frames(case.expected, rates::F24)?;
assert_eq!(expected, tc1 - tc2, "{} - {} == {}", tc1, tc2, expected);
let mut tc = tc1;
tc -= tc2;
assert_eq!(expected, tc, "{} -= {} == {}", tc1, tc2, expected);
Ok(())
}
struct MultiplyCase<T>
where
Timecode: Mul<T>,
{
tc: Timecode,
multiplier: T,
expected: <Timecode as Mul<T>>::Output,
}
#[rstest]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 2,
expected: Timecode::with_frames("02:00:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 2.0,
expected: Timecode::with_frames("02:00:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 1.5,
expected: Timecode::with_frames("01:30:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 0.5,
expected: Timecode::with_frames("00:30:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 0.0,
expected: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24,).unwrap(),
multiplier: 0,
expected: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
multiplier: 10,
expected: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
})]
#[case(MultiplyCase{
tc: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
multiplier: 10.0,
expected: Timecode::with_frames("00:00:00:00", rates::F24,).unwrap(),
})]
fn test_multiply<T>(#[case] case: MultiplyCase<T>)
where
Timecode: Mul<T>,
T: Mul<Timecode> + Copy + Debug + Display,
<Timecode as Mul<T>>::Output: PartialEq<<Timecode as Mul<T>>::Output>
+ PartialEq<<T as Mul<Timecode>>::Output>
+ PartialEq<Timecode>
+ Debug
+ Display
+ Copy,
<T as Mul<Timecode>>::Output: Debug,
Timecode: MulAssign<T>,
{
let result = case.tc * case.multiplier;
assert_eq!(case.expected, result, "{} x {}", case.tc, case.multiplier);
let result = case.multiplier * case.tc;
assert_eq!(
case.expected, result,
"{} x {} (flipped)",
case.multiplier, case.tc
);
let mut tc = case.tc;
tc *= case.multiplier;
assert_eq!(
case.expected, tc,
"{} x {} multiply assign",
case.tc, case.multiplier
);
}
struct DivRemCase<T>
where
Timecode: Div<T>,
Timecode: Rem<T>,
{
tc: Timecode,
divisor: T,
expected_div: <Timecode as Div<T>>::Output,
expected_rem: <Timecode as Rem<T>>::Output,
}
#[rstest]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
divisor: 2,
expected_div: Timecode::with_frames("00:30:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
divisor: 2.0,
expected_div: Timecode::with_frames("00:30:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
divisor: 2,
expected_div: Timecode::with_frames("00:30:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
divisor: 2.0,
expected_div: Timecode::with_frames("00:30:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:01", rates::F24).unwrap(),
divisor: 2,
expected_div: Timecode::with_frames("00:30:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:01", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:01", rates::F24).unwrap(),
divisor: 2.0,
expected_div: Timecode::with_frames("00:30:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:01", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:01", rates::F23_98).unwrap(),
divisor: 2,
expected_div: Timecode::with_frames("00:30:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:01", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:01", rates::F23_98).unwrap(),
divisor: 2.0,
expected_div: Timecode::with_frames("00:30:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:01", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
divisor: 4,
expected_div: Timecode::with_frames("00:15:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
divisor: 4.0,
expected_div: Timecode::with_frames("00:15:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
divisor: 4,
expected_div: Timecode::with_frames("00:15:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:00", rates::F23_98).unwrap(),
divisor: 4.0,
expected_div: Timecode::with_frames("00:15:00:00", rates::F23_98).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:00", rates::F23_98).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:03", rates::F24).unwrap(),
divisor: 4,
expected_div: Timecode::with_frames("00:15:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:03", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:03", rates::F24).unwrap(),
divisor: 4.0,
expected_div: Timecode::with_frames("00:15:00:00", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:03", rates::F24).unwrap(),
})]
#[case(DivRemCase{
tc: Timecode::with_frames("01:00:00:4", rates::F24).unwrap(),
divisor: 1.5,
expected_div: Timecode::with_frames("00:40:00:02", rates::F24).unwrap(),
expected_rem: Timecode::with_frames("00:00:00:01", rates::F24).unwrap(),
})]
fn test_divrem<T>(#[case] case: DivRemCase<T>)
where
T: Display + Debug + Copy,
Timecode: Div<T>,
Timecode: DivAssign<T>,
Timecode: Rem<T>,
Timecode: RemAssign<T>,
<Timecode as Div<T>>::Output:
PartialEq<<Timecode as Div<T>>::Output> + PartialEq<Timecode> + Debug + Display + Copy,
<Timecode as Rem<T>>::Output:
PartialEq<<Timecode as Rem<T>>::Output> + PartialEq<Timecode> + Debug + Display + Copy,
{
let result_div = case.tc / case.divisor;
assert_eq!(
case.expected_div, result_div,
" {} / {:?} = {}",
case.tc, case.divisor, case.expected_div
);
let mut tc = case.tc;
tc /= case.divisor;
assert_eq!(
case.expected_div, tc,
" {} /= {:?} = {}",
case.tc, case.divisor, case.expected_div
);
let result_rem = case.tc % case.divisor;
assert_eq!(
case.expected_rem, result_rem,
" {} % {:?} = {}",
case.tc, case.divisor, case.expected_rem
);
tc = case.tc;
tc %= case.divisor;
assert_eq!(
case.expected_rem, tc,
" {} %= {:?} = {}",
case.tc, case.divisor, case.expected_rem
);
}
#[test]
fn test_negative() {
assert_eq!(
-Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
Timecode::with_frames("-01:00:00:00", rates::F24).unwrap(),
"neg positive",
);
assert_eq!(
-Timecode::with_frames("-01:00:00:00", rates::F24).unwrap(),
Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
"neg negative",
)
}
#[test]
fn test_abs() {
assert_eq!(
Timecode::with_frames("01:00:00:00", rates::F24)
.unwrap()
.abs(),
Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
"abs positive",
);
assert_eq!(
Timecode::with_frames("-01:00:00:00", rates::F24)
.unwrap()
.abs(),
Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
"abs negative",
)
}
struct RebaseCase {
tc_in: Timecode,
new_rate: Framerate,
expected: Timecode,
}
#[rstest]
#[case(RebaseCase{
tc_in: Timecode::with_frames("01:00:00:00", rates::F24).unwrap(),
new_rate: rates::F48,
expected: Timecode::with_frames("00:30:00:00", rates::F48).unwrap(),
})]
fn test_rebase(#[case] case: RebaseCase) {
let rebased = case.tc_in.rebase(case.new_rate);
assert_eq!(case.expected, rebased, "rebased value")
}
}