diff --git a/sstv/command.py b/sstv/command.py index 0e4de3c..42a5e1f 100644 --- a/sstv/command.py +++ b/sstv/command.py @@ -1,7 +1,7 @@ """Parsing arguments and starting program from command line""" import argparse -from sys import exit +from sys import argv, exit from PIL import Image from soundfile import available_formats as available_audio_formats @@ -25,13 +25,16 @@ examples: Start decoding SSTV signal at 50.5 seconds into the audio $ sstv -d audio.ogg -s 50.50""" - def __init__(self): + def __init__(self, shell_args=None): """Handle command line arguments""" self._audio_file = None self._output_file = None - self.args = self.parse_args() + if shell_args is None: + self.args = self.parse_args(argv[1:]) + else: + self.args = self.parse_args(shell_args) def __enter__(self): return self @@ -62,8 +65,6 @@ examples: default=0.0, dest="skip") parser.add_argument("-V", "--version", action="version", version=version) - parser.add_argument("-v", "--verbose", action="count", default=1, - help="increase output to the terminal") parser.add_argument("--list-modes", action="store_true", dest="list_modes", help="list supported SSTV modes") @@ -75,11 +76,11 @@ examples: help="list supported image file formats") return parser - def parse_args(self): + def parse_args(self, shell_args): """Parse command line arguments""" parser = self.init_args() - args = parser.parse_args() + args = parser.parse_args(shell_args) self._audio_file = args.audio_file self._output_file = args.output_file diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/data/m1.ogg b/test/data/m1.ogg new file mode 100644 index 0000000..78164b1 Binary files /dev/null and b/test/data/m1.ogg differ diff --git a/test/test_command.py b/test/test_command.py new file mode 100644 index 0000000..1a46bcf --- /dev/null +++ b/test/test_command.py @@ -0,0 +1,64 @@ +"""Test cases for the CLI code""" + +import sys +import unittest +from io import StringIO + +from sstv.command import SSTVCommand + + +class SSTVCommandTestCase(unittest.TestCase): + """Test SSTVCommand class""" + + def setUp(self): + """Capture standard input/error using StringIO instance""" + sys.stdout = StringIO() + sys.stderr = StringIO() + + def tearDown(self): + """Reset standard output/error""" + sys.stdout = sys.__stdout__ + sys.stderr = sys.__stderr__ + + def test_arg_parser_output(self): + """Test --list-modes flag outputs correctly""" + with self.assertRaises(SystemExit): + SSTVCommand(["--list-modes"]) + modes = "Supported modes: Robot 36, Robot 72, Martin 2, Martin 1, Scottie 2, Scottie 1, Scottie DX" + self.assertEqual(sys.stdout.getvalue().strip(), modes, "List of modes not equal") + + def test_arg_parser_decode_error(self): + """Test decode flag with no input""" + with self.assertRaises(SystemExit): + SSTVCommand(["-d"]) + self.assertIn("expected one argument", sys.stderr.getvalue().strip(), + "Wrong argument error message not present in output") + + with self.assertRaises(SystemExit): + SSTVCommand(["--decode"]) + self.assertIn("expected one argument", sys.stderr.getvalue().strip(), + "'Wrong argument' error message not present in output") + + with self.assertRaises(SystemExit): + SSTVCommand(["-d", "./test/data/abc123"]) + self.assertIn("No such file or directory", sys.stderr.getvalue().strip(), + "'No file' error message not present in output") + + def test_arg_parser_decode_success(self): + """Test decode flag with no input""" + args = SSTVCommand(["-d", "./test/data/m1.ogg"]).args + self.assertTrue(hasattr(args, "audio_file"), + "audio_file attribute not set") + self.assertEqual(args.audio_file.name, "./test/data/m1.ogg", + "Audio file name not set correctly") + self.assertTrue(hasattr(args, "skip"), + "skip attribute not set") + self.assertEqual(args.skip, 0.0, + "skip value not set to default value") + + def test_arg_parser_decode_set_skip(self): + args = SSTVCommand(["-d", "./test/data/m1.ogg", "-s", "15.50"]).args + self.assertTrue(hasattr(args, "skip"), + "skip attribute not set") + self.assertEqual(args.skip, 15.5, + "skip value not set correctly") diff --git a/test/test_decoder.py b/test/test_decoder.py new file mode 100644 index 0000000..7547c92 --- /dev/null +++ b/test/test_decoder.py @@ -0,0 +1,28 @@ +"""Test cases for the decoder code""" + +import sys +import unittest + +from sstv.decode import barycentric_peak_interp, calc_lum, SSTVDecoder + + +class SSTVDecoderTestCase(unittest.TestCase): + """Test SSTVDecoder class""" + + def test_calc_lum(self): + self.assertEqual(calc_lum(1450), 0) + self.assertEqual(calc_lum(2350), 255) + self.assertEqual(calc_lum(1758.1531), 82) + + def test_barycentric_peak_interp(self): + bins = [100, 50, 0, 25, 50, 75, 100, 200, 150, 100] + # Left neighbour is higher, so result must be smaller/equal + self.assertLess(barycentric_peak_interp(bins, 9), 9) + # Right neighbour is smaller and no left, so it must be smaller + self.assertLessEqual(barycentric_peak_interp(bins, 0), 0) + # Right neighbour is larger than left, so x must increase + self.assertGreaterEqual(barycentric_peak_interp(bins, 7), 7) + + bins = [1, 2, 2, 2, 1] + # Centre 2 surrounded by 2s should result in no change + self.assertEqual(barycentric_peak_interp(bins, 2), 2)