summary history branches tags files
commit:98b4ae867e80cfcfda9b54ee7582054cb952b0df
author:Trevor Bentley
committer:Trevor Bentley
date:Wed Aug 28 22:40:48 2024 +0200
parents:0f011ac5dbd7d6230463a8c2e4cd5eb9488bf937
add some tests
diff --git a/src/main.rs b/src/main.rs
line changes: +293/-17
index ac78dde..6860115
--- a/src/main.rs
+++ b/src/main.rs
@@ -30,7 +30,7 @@ fn frac_range(s: &str) -> Result<u32, String> {
 
 /// fstop-print-calc calculates exposure times relative to a base time
 /// using fractions of f-stops.
-#[derive(Parser, Debug)]
+#[derive(Parser, Debug, Default)]
 struct CalcArgs {
     /// Starting time to calculate relative to.
     time: f64,
@@ -344,27 +344,31 @@ fn fstop_table_print_row(col_steps: &Vec<Fraction>, table: &Vec<FstopEntry>, cli
     }
 }
 
+fn parse_stop_string(base: f64, stop: &str) -> FstopEntry {
+    // parse string manually:
+    let (sign_str, fraction) = stop.split_at(1);
+    let sign: f64 = match sign_str {
+        "+" => { 1. },
+        "-" => { -1. },
+        _ => panic!("Stop missing +/- sign.  Format must be +X/Y or -X/Y."),
+    };
+    let slash_offs = fraction.find("/").expect("Stop missing fractional slash.  Format must be +X/Y or -X/Y.");
+    let (num, denom) = fraction.split_at(slash_offs);
+    let denom: &str = &denom[1..];
+    let _ = frac_range(denom).expect(&format!("Stop denominator not in range 1-{}.  Format must be +X/Y or -X/Y.", MAX_DENOMINATOR));
+    let num: f64 = num.parse::<u32>().expect("Stop numerator not an integer.  Format must be +X/Y or -X/Y.") as f64;
+    let denom: f64 = denom.parse::<u32>().expect("Stop numerator not an integer.  Format must be +X/Y or -X/Y.") as f64;
+
+    // calculate
+    FstopEntry::new(base, sign * num, denom)
+}
+
 fn main() {
     let cli: CalcArgs = CalcArgs::parse();
 
     match &cli.stop {
         Some(stop) => {
-            // parse string manually:
-            let (sign_str, fraction) = stop.split_at(1);
-            let sign: f64 = match sign_str {
-                "+" => { 1. },
-                "-" => { -1. },
-                _ => panic!("Stop missing +/- sign.  Format must be +X/Y or -X/Y."),
-            };
-            let slash_offs = fraction.find("/").expect("Stop missing fractional slash.  Format must be +X/Y or -X/Y.");
-            let (num, denom) = fraction.split_at(slash_offs);
-            let denom: &str = &denom[1..];
-            let _ = frac_range(denom).expect(&format!("Stop denominator not in range 1-{}.  Format must be +X/Y or -X/Y.", MAX_DENOMINATOR));
-            let num: f64 = num.parse::<u32>().expect("Stop numerator not an integer.  Format must be +X/Y or -X/Y.") as f64;
-            let denom: f64 = denom.parse::<u32>().expect("Stop numerator not an integer.  Format must be +X/Y or -X/Y.") as f64;
-
-            // calculate and print result
-            let result = FstopEntry::new(cli.time, sign * num, denom);
+            let result = parse_stop_string(cli.time, stop);
             println!("{}: {}", result.offset_str(0, false), result.str(cli.precision(), cli.relative))
         },
         _ => {
@@ -379,3 +383,275 @@ fn main() {
         },
     }
 }
+
+#[cfg(test)]
+mod tests {
+    // Note this useful idiom: importing names from outer (for mod tests) scope.
+    use super::*;
+
+    macro_rules! assert_almost {
+        ($x:expr, $y:expr) => {
+            if ($x - $y).abs() > ($x * 0.0005).abs() {
+                panic!("assertion failed: left nearly right\n - left: {}\n - right: {}\n - diff: {} > {}",
+                       $x, $y,
+                       ($x - $y).abs(),
+                       ($x * 0.0005).abs());
+            }
+        }
+    }
+
+    #[test]
+    fn test_stops_16() {
+        let b16_p2_3 = parse_stop_string(16.0, "+2/3");
+        assert_eq!(b16_p2_3.base, 16.0);
+        assert_eq!(b16_p2_3.frac.numerator, 2.0);
+        assert_eq!(b16_p2_3.frac.num(), 2.0);
+        assert_eq!(b16_p2_3.frac.denominator, 3.0);
+        assert_eq!(b16_p2_3.frac.sign, 1.0);
+        assert_almost!(b16_p2_3.abs(), 25.4);
+
+        let b16_n2_3 = parse_stop_string(16.0, "-2/3");
+        assert_eq!(b16_n2_3.base, 16.0);
+        assert_eq!(b16_n2_3.frac.numerator, 2.0);
+        assert_eq!(b16_n2_3.frac.num(), -2.0);
+        assert_eq!(b16_n2_3.frac.denominator, 3.0);
+        assert_eq!(b16_n2_3.frac.sign, -1.0);
+        assert_almost!(b16_n2_3.abs(), 10.08);
+
+        let b16_p1_4 = parse_stop_string(16.0, "+1/4");
+        assert_eq!(b16_p1_4.base, 16.0);
+        assert_eq!(b16_p1_4.frac.numerator, 1.0);
+        assert_eq!(b16_p1_4.frac.num(), 1.0);
+        assert_eq!(b16_p1_4.frac.denominator, 4.0);
+        assert_eq!(b16_p1_4.frac.sign, 1.0);
+        assert_almost!(b16_p1_4.abs(), 19.03);
+
+        let b16_n1_4 = parse_stop_string(16.0, "-1/4");
+        assert_eq!(b16_n1_4.base, 16.0);
+        assert_eq!(b16_n1_4.frac.numerator, 1.0);
+        assert_eq!(b16_n1_4.frac.num(), -1.0);
+        assert_eq!(b16_n1_4.frac.denominator, 4.0);
+        assert_eq!(b16_n1_4.frac.sign, -1.0);
+        assert_almost!(b16_n1_4.abs(), 13.45);
+
+        let b16_p3_3 = parse_stop_string(16.0, "+3/3");
+        assert_eq!(b16_p3_3.base, 16.0);
+        assert_eq!(b16_p3_3.frac.numerator, 3.0);
+        assert_eq!(b16_p3_3.frac.num(), 3.0);
+        assert_eq!(b16_p3_3.frac.denominator, 3.0);
+        assert_eq!(b16_p3_3.frac.sign, 1.0);
+        assert_almost!(b16_p3_3.abs(), 32.0);
+
+        let b16_n3_3 = parse_stop_string(16.0, "-3/3");
+        assert_eq!(b16_n3_3.base, 16.0);
+        assert_eq!(b16_n3_3.frac.numerator, 3.0);
+        assert_eq!(b16_n3_3.frac.num(), -3.0);
+        assert_eq!(b16_n3_3.frac.denominator, 3.0);
+        assert_eq!(b16_n3_3.frac.sign, -1.0);
+        assert_almost!(b16_n3_3.abs(), 8.0);
+
+        let b16_p5_4 = parse_stop_string(16.0, "+5/4");
+        assert_eq!(b16_p5_4.base, 16.0);
+        assert_eq!(b16_p5_4.frac.numerator, 5.0);
+        assert_eq!(b16_p5_4.frac.num(), 5.0);
+        assert_eq!(b16_p5_4.frac.denominator, 4.0);
+        assert_eq!(b16_p5_4.frac.sign, 1.0);
+        assert_almost!(b16_p5_4.abs(), 38.05);
+
+        let b16_n5_4 = parse_stop_string(16.0, "-5/4");
+        assert_eq!(b16_n5_4.base, 16.0);
+        assert_eq!(b16_n5_4.frac.numerator, 5.0);
+        assert_eq!(b16_n5_4.frac.num(), -5.0);
+        assert_eq!(b16_n5_4.frac.denominator, 4.0);
+        assert_eq!(b16_n5_4.frac.sign, -1.0);
+        assert_almost!(b16_n5_4.abs(), 6.73);
+    }
+
+    #[test]
+    fn test_table_16_3_x2() {
+        let mut cli: CalcArgs = Default::default();
+        cli.time = 16.0;
+        cli.stops = Some(2);
+        cli.fraction = vec!(3);
+        let (col_steps, row_steps, table) = fstop_table(&cli);
+
+        assert_eq!(col_steps.len(), 13);
+        let expected = vec!(
+            Fraction::new(-2., 1.),
+            Fraction::new(-5., 3.),
+            Fraction::new(-4., 3.),
+            Fraction::new(-1., 1.),
+            Fraction::new(-2., 3.),
+            Fraction::new(-1., 3.),
+            Fraction::new(0., 1.),
+            Fraction::new(1., 3.),
+            Fraction::new(2., 3.),
+            Fraction::new(1., 1.),
+            Fraction::new(4., 3.),
+            Fraction::new(5., 3.),
+            Fraction::new(2., 1.),
+        );
+        for (idx, step) in col_steps.iter().enumerate() {
+            assert_eq!(step.num(), expected[idx].num());
+            assert_eq!(step.denom(), expected[idx].denom());
+        }
+
+        assert_eq!(row_steps.len(), 1);
+
+        let expected = vec!(
+            4.0,
+            5.04,
+            6.35,
+            8.0,
+            10.08,
+            12.70,
+            16.0,
+            20.16,
+            25.40,
+            32.0,
+            40.31,
+            50.80,
+            64.0,
+            );
+        for (idx, step) in table.iter().enumerate() {
+            assert_almost!(step.abs(), expected[idx]);
+            assert_almost!(step.rel(), expected[idx] - 16.0);
+        }
+    }
+
+    #[test]
+    fn test_table_16_4_x2() {
+        let mut cli: CalcArgs = Default::default();
+        cli.time = 16.0;
+        cli.stops = Some(2);
+        cli.fraction = vec!(4);
+        let (col_steps, row_steps, table) = fstop_table(&cli);
+
+        assert_eq!(col_steps.len(), 17);
+        let expected = vec!(
+            Fraction::new(-2., 1.),
+            Fraction::new(-7., 4.),
+            Fraction::new(-6., 4.),
+            Fraction::new(-5., 4.),
+            Fraction::new(-1., 1.),
+            Fraction::new(-3., 4.),
+            Fraction::new(-2., 4.),
+            Fraction::new(-1., 4.),
+            Fraction::new(0., 1.),
+            Fraction::new(1., 4.),
+            Fraction::new(2., 4.),
+            Fraction::new(3., 4.),
+            Fraction::new(1., 1.),
+            Fraction::new(5., 4.),
+            Fraction::new(6., 4.),
+            Fraction::new(7., 4.),
+            Fraction::new(2., 1.),
+        );
+        for (idx, step) in col_steps.iter().enumerate() {
+            assert_eq!(step.num(), expected[idx].num());
+            assert_eq!(step.denom(), expected[idx].denom());
+        }
+
+        assert_eq!(row_steps.len(), 1);
+
+        let expected = vec!(
+            4.0,
+            4.756,
+            5.656,
+            6.73,
+            8.0,
+            9.513,
+            11.314,
+            13.454,
+            16.0,
+            19.027,
+            22.627,
+            26.909,
+            32.0,
+            38.05,
+            45.254,
+            53.817,
+            64.0,
+            );
+        for (idx, step) in table.iter().enumerate() {
+            assert_almost!(step.abs(), expected[idx]);
+            assert_almost!(step.rel(), expected[idx] - 16.0);
+        }
+    }
+
+    #[test]
+    fn test_table_16_3_4_x2() {
+        let mut cli: CalcArgs = Default::default();
+        cli.time = 16.0;
+        cli.stops = Some(2);
+        cli.fraction = vec!(3, 4);
+        let (col_steps, row_steps, table) = fstop_table(&cli);
+
+        assert_eq!(col_steps.len(), 25);
+        let expected = vec!(
+            Fraction::new(-2., 1.),
+            Fraction::new(-7., 4.),
+            Fraction::new(-5., 3.),
+            Fraction::new(-6., 4.),
+            Fraction::new(-4., 3.),
+            Fraction::new(-5., 4.),
+            Fraction::new(-1., 1.),
+            Fraction::new(-3., 4.),
+            Fraction::new(-2., 3.),
+            Fraction::new(-2., 4.),
+            Fraction::new(-1., 3.),
+            Fraction::new(-1., 4.),
+            Fraction::new(0., 1.),
+            Fraction::new(1., 4.),
+            Fraction::new(1., 3.),
+            Fraction::new(2., 4.),
+            Fraction::new(2., 3.),
+            Fraction::new(3., 4.),
+            Fraction::new(1., 1.),
+            Fraction::new(5., 4.),
+            Fraction::new(4., 3.),
+            Fraction::new(6., 4.),
+            Fraction::new(5., 3.),
+            Fraction::new(7., 4.),
+            Fraction::new(2., 1.),
+        );
+        for (idx, step) in col_steps.iter().enumerate() {
+            assert_eq!(step.num(), expected[idx].num());
+            assert_eq!(step.denom(), expected[idx].denom());
+        }
+
+        assert_eq!(row_steps.len(), 1);
+
+        let expected = vec!(
+            4.0,
+            4.756,
+            5.04,
+            5.656,
+            6.35,
+            6.73,
+            8.0,
+            9.513,
+            10.08,
+            11.314,
+            12.70,
+            13.454,
+            16.0,
+            19.027,
+            20.159,
+            22.627,
+            25.40,
+            26.909,
+            32.0,
+            38.05,
+            40.317,
+            45.254,
+            50.8,
+            53.817,
+            64.0,
+            );
+        for (idx, step) in table.iter().enumerate() {
+            assert_almost!(step.abs(), expected[idx]);
+            assert_almost!(step.rel(), expected[idx] - 16.0);
+        }
+    }
+}