From e99416456afd4aa8bde42016826f9a345291cbf3 Mon Sep 17 00:00:00 2001 From: Matthew Poletiek Date: Tue, 8 Dec 2020 21:03:16 -0600 Subject: Initial Commit --- tests/unit/__init__.py | 0 tests/unit/base.py | 50 +++++ tests/unit/test_bitwise.py | 341 ++++++++++++++++++++++++++++++++ tests/unit/test_chirp_common.py | 415 +++++++++++++++++++++++++++++++++++++++ tests/unit/test_directory.py | 69 +++++++ tests/unit/test_icom_clone.py | 117 +++++++++++ tests/unit/test_import_logic.py | 373 +++++++++++++++++++++++++++++++++++ tests/unit/test_mappingmodel.py | 283 ++++++++++++++++++++++++++ tests/unit/test_memedit_edits.py | 82 ++++++++ tests/unit/test_platform.py | 62 ++++++ tests/unit/test_repeaterbook.py | 28 +++ tests/unit/test_settings.py | 140 +++++++++++++ tests/unit/test_shiftdialog.py | 112 +++++++++++ tests/unit/test_utils.py | 21 ++ tests/unit/test_yaesu_clone.py | 41 ++++ 15 files changed, 2134 insertions(+) create mode 100644 tests/unit/__init__.py create mode 100644 tests/unit/base.py create mode 100644 tests/unit/test_bitwise.py create mode 100644 tests/unit/test_chirp_common.py create mode 100644 tests/unit/test_directory.py create mode 100644 tests/unit/test_icom_clone.py create mode 100644 tests/unit/test_import_logic.py create mode 100644 tests/unit/test_mappingmodel.py create mode 100644 tests/unit/test_memedit_edits.py create mode 100644 tests/unit/test_platform.py create mode 100644 tests/unit/test_repeaterbook.py create mode 100644 tests/unit/test_settings.py create mode 100644 tests/unit/test_shiftdialog.py create mode 100644 tests/unit/test_utils.py create mode 100644 tests/unit/test_yaesu_clone.py (limited to 'tests/unit') diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/base.py b/tests/unit/base.py new file mode 100644 index 0000000..1e112ce --- /dev/null +++ b/tests/unit/base.py @@ -0,0 +1,50 @@ +import sys +import unittest + +import mock + +try: + import mox +except ImportError: + from mox3 import mox + +import warnings +warnings.simplefilter('ignore', Warning) + + +class BaseTest(unittest.TestCase): + def setUp(self): + __builtins__['_'] = lambda s: s + self.mox = mox.Mox() + + def tearDown(self): + self.mox.UnsetStubs() + self.mox.VerifyAll() + + +pygtk_mocks = ('gtk', 'pango', 'gobject') +pygtk_base_classes = ('gobject.GObject', 'gtk.HBox', 'gtk.Dialog') + + +class DummyBase(object): + def __init__(self, *a, **k): + # gtk.Dialog + self.vbox = mock.MagicMock() + + # gtk.Dialog + def set_position(self, pos): + pass + + +def mock_gtk(): + for module in pygtk_mocks: + sys.modules[module] = mock.MagicMock() + + for path in pygtk_base_classes: + module, base_class = path.split('.') + setattr(sys.modules[module], base_class, DummyBase) + + +def unmock_gtk(): + for module in pygtk_mocks: + del sys.modules[module] diff --git a/tests/unit/test_bitwise.py b/tests/unit/test_bitwise.py new file mode 100644 index 0000000..2b674e8 --- /dev/null +++ b/tests/unit/test_bitwise.py @@ -0,0 +1,341 @@ +# Copyright 2013 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from builtins import bytes + +import struct +import unittest + +import six + +from chirp import bitwise +from chirp import memmap + + +class BaseTest(unittest.TestCase): + def _compare_structure(self, obj, primitive): + for key, value in primitive.items(): + if isinstance(value, dict): + self._compare_structure(getattr(obj, key), value) + else: + self.assertEqual(type(value)(getattr(obj, key)), value) + + +class TestMemoryMapCoherence(BaseTest): + def test_byte_char_coherence(self): + charmmap = memmap.MemoryMap('00') + # This will to a get_byte_compatible() from chars + obj = bitwise.parse('char foo[2];', charmmap) + self.assertEqual('00', str(obj.foo)) + obj.foo = '11' + # The above assignment happens on the byte-compatible mmap, + # make sure it is still visible in the charmmap we know about. + # This confirms that get_byte_compatible() links the backing + # store of the original mmap to the new one. + self.assertEqual('11', charmmap.get_packed()) + + +class TestBitwiseBaseIntTypes(BaseTest): + def _test_type(self, datatype, _data, value): + data = memmap.MemoryMapBytes(bytes(_data)) + obj = bitwise.parse("%s foo;" % datatype, data) + self.assertEqual(int(obj.foo), value) + self.assertEqual(obj.foo.size(), len(data) * 8) + + obj.foo = 0 + self.assertEqual(int(obj.foo), 0) + self.assertEqual(data.get_packed(), (b"\x00" * (obj.size() // 8))) + + obj.foo = value + self.assertEqual(int(obj.foo), value) + self.assertEqual(data.get_packed(), _data) + + obj.foo = 7 + # Compare against the equivalent real division so we get consistent + # results on py2 and py3 + self.assertEqual(7 // 2, obj.foo // 2) + self.assertEqual(7 / 2, obj.foo / 2) + self.assertEqual(7 / 2.0, obj.foo / 2.0) + + def test_type_u8(self): + self._test_type("u8", b"\x80", 128) + + def test_type_u16(self): + self._test_type("u16", b"\x01\x00", 256) + + def test_type_u24(self): + self._test_type("u24", b"\x80\x00\x00", 2**23) + + def test_type_u32(self): + self._test_type("u32", b"\x80\x00\x00\x00", 2**31) + + def test_type_ul16(self): + self._test_type("ul16", b"\x00\x01", 256) + + def test_type_ul24(self): + self._test_type("ul24", b"\x00\x00\x80", 2**23) + + def test_type_ul32(self): + self._test_type("ul32", b"\x00\x00\x00\x80", 2**31) + + def test_int_array(self): + data = memmap.MemoryMapBytes(bytes(b'\x00\x01\x02\x03')) + obj = bitwise.parse('u8 foo[4];', data) + for i in range(4): + self.assertEqual(i, obj.foo[i]) + obj.foo[i] = i * 2 + self.assertEqual(b'\x00\x02\x04\x06', data.get_packed()) + + def test_int_array(self): + data = memmap.MemoryMapBytes(bytes(b'\x00\x01\x02\x03')) + obj = bitwise.parse('u8 foo[4];', data) + for i in range(4): + self.assertEqual(i, obj.foo[i]) + obj.foo[i] = i * 2 + self.assertEqual(b'\x00\x02\x04\x06', data.get_packed()) + + +class TestBitfieldTypes(BaseTest): + def test_bitfield_u8(self): + defn = "u8 foo:4, bar:4;" + data = memmap.MemoryMapBytes(bytes(b"\x12")) + obj = bitwise.parse(defn, data) + self.assertEqual(obj.foo, 1) + self.assertEqual(obj.bar, 2) + self.assertEqual(obj.foo.size(), 4) + self.assertEqual(obj.bar.size(), 4) + obj.foo = 0x8 + obj.bar = 0x1 + self.assertEqual(data.get_packed(), b"\x81") + + def _test_bitfield_16(self, variant, data): + defn = "u%s16 foo:4, bar:8, baz:4;" % variant + data = memmap.MemoryMapBytes(bytes(data)) + obj = bitwise.parse(defn, data) + self.assertEqual(int(obj.foo), 1) + self.assertEqual(int(obj.bar), 0x23) + self.assertEqual(int(obj.baz), 4) + self.assertEqual(obj.foo.size(), 4) + self.assertEqual(obj.bar.size(), 8) + self.assertEqual(obj.baz.size(), 4) + obj.foo = 0x2 + obj.bar = 0x11 + obj.baz = 0x3 + if variant == "l": + self.assertEqual(data.get_packed(), b"\x13\x21") + else: + self.assertEqual(data.get_packed(), b"\x21\x13") + + def test_bitfield_u16(self): + self._test_bitfield_16("", b"\x12\x34") + + def test_bitfield_ul16(self): + self._test_bitfield_16('l', b"\x34\x12") + + def _test_bitfield_24(self, variant, data): + defn = "u%s24 foo:12, bar:6, baz:6;" % variant + data = memmap.MemoryMapBytes(bytes(data)) + obj = bitwise.parse(defn, data) + self.assertEqual(int(obj.foo), 4) + self.assertEqual(int(obj.bar), 3) + self.assertEqual(int(obj.baz), 2) + self.assertEqual(obj.foo.size(), 12) + self.assertEqual(obj.bar.size(), 6) + self.assertEqual(obj.baz.size(), 6) + obj.foo = 1 + obj.bar = 2 + obj.baz = 3 + if variant == 'l': + self.assertEqual(data.get_packed(), b"\x83\x10\x00") + else: + self.assertEqual(data.get_packed(), b"\x00\x10\x83") + + def test_bitfield_u24(self): + self._test_bitfield_24("", b"\x00\x40\xC2") + + def test_bitfield_ul24(self): + self._test_bitfield_24("l", b"\xC2\x40\x00") + + +class TestBitType(BaseTest): + def test_bit_array(self): + defn = "bit foo[24];" + data = memmap.MemoryMapBytes(bytes(b"\x00\x80\x01")) + obj = bitwise.parse(defn, data) + for i, v in [(0, False), (8, True), (23, True)]: + self.assertEqual(bool(obj.foo[i]), v) + for i in range(0, 24): + obj.foo[i] = i % 2 + self.assertEqual(data.get_packed(), b"\x55\x55\x55") + + def test_bit_array_fail(self): + self.assertRaises(ValueError, bitwise.parse, "bit foo[23];", b"000") + + +class TestBitwiseBCDTypes(BaseTest): + def _test_def(self, definition, name, _data, value): + data = memmap.MemoryMapBytes(bytes(_data)) + obj = bitwise.parse(definition, data) + self.assertEqual(int(getattr(obj, name)), value) + self.assertEqual(getattr(obj, name).size(), len(_data) * 8) + setattr(obj, name, 0) + self.assertEqual(data.get_packed(), (b"\x00" * len(_data))) + setattr(obj, name, 42) + if definition.startswith("b"): + expected = (len(_data) == 2 and b"\x00" or b"") + b"\x42" + else: + expected = b"\x42" + (len(_data) == 2 and b"\x00" or b"") + raw = data.get_packed() + self.assertEqual(raw, expected) + + def test_bbcd(self): + self._test_def("bbcd foo;", "foo", b"\x12", 12) + + def test_lbcd(self): + self._test_def("lbcd foo;", "foo", b"\x12", 12) + + def test_bbcd_array(self): + self._test_def("bbcd foo[2];", "foo", b"\x12\x34", 1234) + + def test_lbcd_array(self): + self._test_def("lbcd foo[2];", "foo", b"\x12\x34", 3412) + + +class TestBitwiseCharTypes(BaseTest): + def test_char(self): + data = memmap.MemoryMapBytes(bytes(b"c")) + obj = bitwise.parse("char foo;", data) + self.assertEqual(str(obj.foo), "c") + self.assertEqual(obj.foo.size(), 8) + obj.foo = "d" + self.assertEqual(data.get_packed(), b"d") + + def test_string(self): + data = memmap.MemoryMapBytes(bytes(b"foobar")) + obj = bitwise.parse("char foo[6];", data) + self.assertEqual(str(obj.foo), "foobar") + self.assertEqual(obj.foo.size(), 8 * 6) + obj.foo = "bazfoo" + self.assertEqual(data.get_packed(), b"bazfoo") + + def test_string_invalid_chars(self): + data = memmap.MemoryMapBytes(bytes(b"\xFFoobar1")) + obj = bitwise.parse("struct {char foo[7];} bar;", data) + + if six.PY3: + expected = '\xffoobar1' + else: + expected = '\\xffoobar1' + + self.assertIn(expected, repr(obj.bar)) + + def test_string_wrong_length(self): + data = memmap.MemoryMapBytes(bytes(b"foobar")) + obj = bitwise.parse("char foo[6];", data) + self.assertRaises(ValueError, setattr, obj, "foo", "bazfo") + self.assertRaises(ValueError, setattr, obj, "foo", "bazfooo") + + def test_string_with_various_input_types(self): + data = memmap.MemoryMapBytes(bytes(b"foobar")) + obj = bitwise.parse("char foo[6];", data) + self.assertEqual('foobar', str(obj.foo)) + self.assertEqual(6, len(b'barfoo')) + obj.foo = b'barfoo' + self.assertEqual('barfoo', str(obj.foo)) + obj.foo = [ord(c) for c in 'fffbbb'] + self.assertEqual('fffbbb', str(obj.foo)) + + def test_string_get_raw(self): + data = memmap.MemoryMapBytes(bytes(b"foobar")) + obj = bitwise.parse("char foo[6];", data) + self.assertEqual('foobar', obj.foo.get_raw()) + self.assertEqual(b'foobar', obj.foo.get_raw(asbytes=True)) + + +class TestBitwiseStructTypes(BaseTest): + def _test_def(self, definition, data, primitive): + obj = bitwise.parse(definition, data) + self._compare_structure(obj, primitive) + self.assertEqual(obj.size(), len(data) * 8) + + def test_struct_one_element(self): + defn = "struct { u8 bar; } foo;" + value = {"foo": {"bar": 128}} + self._test_def(defn, b"\x80", value) + + def test_struct_two_elements(self): + defn = "struct { u8 bar; u16 baz; } foo;" + value = {"foo": {"bar": 128, "baz": 256}} + self._test_def(defn, b"\x80\x01\x00", value) + + def test_struct_writes(self): + data = memmap.MemoryMapBytes(bytes(b"..")) + defn = "struct { u8 bar; u8 baz; } foo;" + obj = bitwise.parse(defn, data) + obj.foo.bar = 0x12 + obj.foo.baz = 0x34 + self.assertEqual(data.get_packed(), b"\x12\x34") + + def test_struct_get_raw(self): + data = memmap.MemoryMapBytes(bytes(b"..")) + defn = "struct { u8 bar; u8 baz; } foo;" + obj = bitwise.parse(defn, data) + self.assertEqual('..', obj.get_raw()) + self.assertEqual(b'..', obj.get_raw(asbytes=True)) + + def test_struct_get_raw_small(self): + data = memmap.MemoryMapBytes(bytes(b".")) + defn = "struct { u8 bar; } foo;" + obj = bitwise.parse(defn, data) + self.assertEqual('.', obj.get_raw()) + self.assertEqual(b'.', obj.get_raw(asbytes=True)) + + +class TestBitwiseSeek(BaseTest): + def test_seekto(self): + defn = "#seekto 4; char foo;" + obj = bitwise.parse(defn, b"abcdZ") + self.assertEqual(str(obj.foo), "Z") + + def test_seek(self): + defn = "char foo; #seek 3; char bar;" + obj = bitwise.parse(defn, b"AbcdZ") + self.assertEqual(str(obj.foo), "A") + self.assertEqual(str(obj.bar), "Z") + + +class TestBitwiseErrors(BaseTest): + def test_missing_semicolon(self): + self.assertRaises(SyntaxError, bitwise.parse, "u8 foo", "") + + +class TestBitwiseComments(BaseTest): + def test_comment_inline_cppstyle(self): + obj = bitwise.parse('u8 foo; // test', b'\x10') + self.assertEqual(16, obj.foo) + + def test_comment_cppstyle(self): + obj = bitwise.parse('// Test this\nu8 foo;', b'\x10') + self.assertEqual(16, obj.foo) + + +class TestBitwiseStringEncoders(BaseTest): + def test_encode_bytes(self): + self.assertEqual(b'foobar\x00', + bitwise.string_straight_encode('foobar\x00')) + + def test_decode_bytes(self): + self.assertEqual('foobar\x00', + bitwise.string_straight_decode(b'foobar\x00')) diff --git a/tests/unit/test_chirp_common.py b/tests/unit/test_chirp_common.py new file mode 100644 index 0000000..400c469 --- /dev/null +++ b/tests/unit/test_chirp_common.py @@ -0,0 +1,415 @@ +import base64 +import json +import os +import tempfile + +import mock + +from tests.unit import base +from chirp import CHIRP_VERSION +from chirp import chirp_common +from chirp import errors + + +class TestUtilityFunctions(base.BaseTest): + def test_parse_freq_whole(self): + self.assertEqual(chirp_common.parse_freq("146.520000"), 146520000) + self.assertEqual(chirp_common.parse_freq("146.5200"), 146520000) + self.assertEqual(chirp_common.parse_freq("146.52"), 146520000) + self.assertEqual(chirp_common.parse_freq("146"), 146000000) + self.assertEqual(chirp_common.parse_freq("1250"), 1250000000) + self.assertEqual(chirp_common.parse_freq("123456789"), + 123456789000000) + + def test_parse_freq_decimal(self): + self.assertEqual(chirp_common.parse_freq("1.0"), 1000000) + self.assertEqual(chirp_common.parse_freq("1.000000"), 1000000) + self.assertEqual(chirp_common.parse_freq("1.1"), 1100000) + self.assertEqual(chirp_common.parse_freq("1.100"), 1100000) + self.assertEqual(chirp_common.parse_freq("0.6"), 600000) + self.assertEqual(chirp_common.parse_freq("0.600"), 600000) + self.assertEqual(chirp_common.parse_freq("0.060"), 60000) + self.assertEqual(chirp_common.parse_freq(".6"), 600000) + + def test_parse_freq_whitespace(self): + self.assertEqual(chirp_common.parse_freq("1 "), 1000000) + self.assertEqual(chirp_common.parse_freq(" 1"), 1000000) + self.assertEqual(chirp_common.parse_freq(" 1 "), 1000000) + + self.assertEqual(chirp_common.parse_freq("1.0 "), 1000000) + self.assertEqual(chirp_common.parse_freq(" 1.0"), 1000000) + self.assertEqual(chirp_common.parse_freq(" 1.0 "), 1000000) + self.assertEqual(chirp_common.parse_freq(""), 0) + self.assertEqual(chirp_common.parse_freq(" "), 0) + + def test_parse_freq_bad(self): + self.assertRaises(ValueError, chirp_common.parse_freq, "a") + self.assertRaises(ValueError, chirp_common.parse_freq, "1.a") + self.assertRaises(ValueError, chirp_common.parse_freq, "a.b") + self.assertRaises(ValueError, chirp_common.parse_freq, + "1.0000001") + + def test_format_freq(self): + self.assertEqual(chirp_common.format_freq(146520000), "146.520000") + self.assertEqual(chirp_common.format_freq(54000000), "54.000000") + self.assertEqual(chirp_common.format_freq(1800000), "1.800000") + self.assertEqual(chirp_common.format_freq(1), "0.000001") + self.assertEqual(chirp_common.format_freq(1250000000), "1250.000000") + + @mock.patch('chirp.CHIRP_VERSION', new='daily-20151021') + def test_compare_version_to_current(self): + self.assertTrue(chirp_common.is_version_newer('daily-20180101')) + self.assertFalse(chirp_common.is_version_newer('daily-20140101')) + self.assertFalse(chirp_common.is_version_newer('0.3.0')) + self.assertFalse(chirp_common.is_version_newer('0.3.0dev')) + + @mock.patch('chirp.CHIRP_VERSION', new='0.3.0dev') + def test_compare_version_to_current_dev(self): + self.assertTrue(chirp_common.is_version_newer('daily-20180101')) + + def test_from_Hz(self): + # FIXME: These are wrong! Adding them here purely to test the + # python3 conversion, but they should be fixed. + self.assertEqual(140, chirp_common.from_GHz(14000000001)) + self.assertEqual(140, chirp_common.from_MHz(14000001)) + self.assertEqual(140, chirp_common.from_kHz(14001)) + + +class TestSplitTone(base.BaseTest): + def _test_split_tone_decode(self, tx, rx, **vals): + mem = chirp_common.Memory() + chirp_common.split_tone_decode(mem, tx, rx) + for key, value in list(vals.items()): + self.assertEqual(getattr(mem, key), value) + + def test_split_tone_decode_none(self): + self._test_split_tone_decode((None, None, None), + (None, None, None), + tmode='') + + def test_split_tone_decode_tone(self): + self._test_split_tone_decode(('Tone', 100.0, None), + ('', 0, None), + tmode='Tone', + rtone=100.0) + + def test_split_tone_decode_tsql(self): + self._test_split_tone_decode(('Tone', 100.0, None), + ('Tone', 100.0, None), + tmode='TSQL', + ctone=100.0) + + def test_split_tone_decode_dtcs(self): + self._test_split_tone_decode(('DTCS', 23, None), + ('DTCS', 23, None), + tmode='DTCS', + dtcs=23) + + def test_split_tone_decode_cross_tone_tone(self): + self._test_split_tone_decode(('Tone', 100.0, None), + ('Tone', 123.0, None), + tmode='Cross', + cross_mode='Tone->Tone', + rtone=100.0, + ctone=123.0) + + def test_split_tone_decode_cross_tone_dtcs(self): + self._test_split_tone_decode(('Tone', 100.0, None), + ('DTCS', 32, 'R'), + tmode='Cross', + cross_mode='Tone->DTCS', + rtone=100.0, + rx_dtcs=32, + dtcs_polarity='NR') + + def test_split_tone_decode_cross_dtcs_tone(self): + self._test_split_tone_decode(('DTCS', 32, 'R'), + ('Tone', 100.0, None), + tmode='Cross', + cross_mode='DTCS->Tone', + ctone=100.0, + dtcs=32, + dtcs_polarity='RN') + + def test_split_tone_decode_cross_dtcs_dtcs(self): + self._test_split_tone_decode(('DTCS', 32, 'R'), + ('DTCS', 25, 'R'), + tmode='Cross', + cross_mode='DTCS->DTCS', + dtcs=32, + rx_dtcs=25, + dtcs_polarity='RR') + + def test_split_tone_decode_cross_none_dtcs(self): + self._test_split_tone_decode((None, None, None), + ('DTCS', 25, 'R'), + tmode='Cross', + cross_mode='->DTCS', + rx_dtcs=25, + dtcs_polarity='NR') + + def test_split_tone_decode_cross_none_tone(self): + self._test_split_tone_decode((None, None, None), + ('Tone', 100.0, None), + tmode='Cross', + cross_mode='->Tone', + ctone=100.0) + + def _set_mem(self, **vals): + mem = chirp_common.Memory() + for key, value in list(vals.items()): + setattr(mem, key, value) + return chirp_common.split_tone_encode(mem) + + def split_tone_encode_test_none(self): + self.assertEqual(self._set_mem(tmode=''), + (('', None, None), + ('', None, None))) + + def split_tone_encode_test_tone(self): + self.assertEqual(self._set_mem(tmode='Tone', rtone=100.0), + (('Tone', 100.0, None), + ('', None, None))) + + def split_tone_encode_test_tsql(self): + self.assertEqual(self._set_mem(tmode='TSQL', ctone=100.0), + (('Tone', 100.0, None), + ('Tone', 100.0, None))) + + def split_tone_encode_test_dtcs(self): + self.assertEqual(self._set_mem(tmode='DTCS', dtcs=23, + dtcs_polarity='RN'), + (('DTCS', 23, 'R'), + ('DTCS', 23, 'N'))) + + def split_tone_encode_test_cross_tone_tone(self): + self.assertEqual(self._set_mem(tmode='Cross', cross_mode='Tone->Tone', + rtone=100.0, ctone=123.0), + (('Tone', 100.0, None), + ('Tone', 123.0, None))) + + def split_tone_encode_test_cross_tone_dtcs(self): + self.assertEqual(self._set_mem(tmode='Cross', cross_mode='Tone->DTCS', + rtone=100.0, rx_dtcs=25), + (('Tone', 100.0, None), + ('DTCS', 25, 'N'))) + + def split_tone_encode_test_cross_dtcs_tone(self): + self.assertEqual(self._set_mem(tmode='Cross', cross_mode='DTCS->Tone', + ctone=100.0, dtcs=25), + (('DTCS', 25, 'N'), + ('Tone', 100.0, None))) + + def split_tone_encode_test_cross_none_dtcs(self): + self.assertEqual(self._set_mem(tmode='Cross', cross_mode='->DTCS', + rx_dtcs=25), + (('', None, None), + ('DTCS', 25, 'N'))) + + def split_tone_encode_test_cross_none_tone(self): + self.assertEqual(self._set_mem(tmode='Cross', cross_mode='->Tone', + ctone=100.0), + (('', None, None), + ('Tone', 100.0, None))) + + +class TestStepFunctions(base.BaseTest): + _625 = [145856250, + 445856250, + 862731250, + 146118750, + ] + _125 = [145862500, + 445862500, + 862737500, + ] + _005 = [145005000, + 445005000, + 850005000, + ] + _025 = [145002500, + 445002500, + 850002500, + ] + + def test_is_fractional_step(self): + for freq in self._125 + self._625: + print(freq) + self.assertTrue(chirp_common.is_fractional_step(freq)) + + def test_is_6_25(self): + for freq in self._625: + self.assertTrue(chirp_common.is_6_25(freq)) + + def test_is_12_5(self): + for freq in self._125: + self.assertTrue(chirp_common.is_12_5(freq)) + + def test_is_5_0(self): + for freq in self._005: + self.assertTrue(chirp_common.is_5_0(freq)) + + def test_is_2_5(self): + for freq in self._025: + self.assertTrue(chirp_common.is_2_5(freq)) + + def test_required_step(self): + steps = {2.5: self._025, + 5.0: self._005, + 6.25: self._625, + 12.5: self._125, + } + for step, freqs in list(steps.items()): + for freq in freqs: + self.assertEqual(step, chirp_common.required_step(freq)) + + def test_required_step_fail(self): + self.assertRaises(errors.InvalidDataError, + chirp_common.required_step, + 146520500) + + def test_fix_rounded_step_250(self): + self.assertEqual(146106250, + chirp_common.fix_rounded_step(146106000)) + + def test_fix_rounded_step_500(self): + self.assertEqual(146112500, + chirp_common.fix_rounded_step(146112000)) + + def test_fix_rounded_step_750(self): + self.assertEqual(146118750, + chirp_common.fix_rounded_step(146118000)) + + +class TestImageMetadata(base.BaseTest): + def test_make_metadata(self): + class TestRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + + raw_metadata = TestRadio._make_metadata() + metadata = json.loads(base64.b64decode(raw_metadata).decode()) + expected = { + 'vendor': 'Dan', + 'model': 'Foomaster 9000', + 'variant': 'R', + 'rclass': 'TestRadio', + 'chirp_version': CHIRP_VERSION, + } + self.assertEqual(expected, metadata) + + def test_strip_metadata(self): + class TestRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + + raw_metadata = TestRadio._make_metadata() + raw_data = (b'foooooooooooooooooooooo' + TestRadio.MAGIC + + TestRadio._make_metadata()) + data, metadata = chirp_common.FileBackedRadio._strip_metadata(raw_data) + self.assertEqual(b'foooooooooooooooooooooo', data) + expected = { + 'vendor': 'Dan', + 'model': 'Foomaster 9000', + 'variant': 'R', + 'rclass': 'TestRadio', + 'chirp_version': CHIRP_VERSION, + } + self.assertEqual(expected, metadata) + + def test_load_mmap_no_metadata(self): + f = tempfile.NamedTemporaryFile() + f.write(b'thisisrawdata') + f.flush() + + with mock.patch('chirp.memmap.MemoryMap') as mock_mmap: + chirp_common.FileBackedRadio(None).load_mmap(f.name) + mock_mmap.assert_called_once_with(b'thisisrawdata') + + def test_load_mmap_bad_metadata(self): + f = tempfile.NamedTemporaryFile() + f.write(b'thisisrawdata') + f.write(chirp_common.FileBackedRadio.MAGIC + b'bad') + f.flush() + + with mock.patch('chirp.memmap.MemoryMap') as mock_mmap: + chirp_common.FileBackedRadio(None).load_mmap(f.name) + mock_mmap.assert_called_once_with(b'thisisrawdata') + + def test_save_mmap_includes_metadata(self): + # Make sure that a file saved with a .img extension includes + # the metadata blob + class TestRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + + with tempfile.NamedTemporaryFile(suffix='.Img') as f: + fn = f.name + r = TestRadio(None) + r._mmap = mock.Mock() + r._mmap.get_byte_compatible.return_value.get_packed.return_value = ( + b'thisisrawdata') + r.save_mmap(fn) + with open(fn, 'rb') as f: + filedata = f.read() + os.remove(fn) + data, metadata = chirp_common.FileBackedRadio._strip_metadata(filedata) + self.assertEqual(b'thisisrawdata', data) + expected = { + 'vendor': 'Dan', + 'model': 'Foomaster 9000', + 'variant': 'R', + 'rclass': 'TestRadio', + 'chirp_version': CHIRP_VERSION, + } + self.assertEqual(expected, metadata) + + def test_save_mmap_no_metadata_not_img_file(self): + # Make sure that if we save without a .img extension we do + # not include the metadata blob + class TestRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + + with tempfile.NamedTemporaryFile(suffix='.txt') as f: + fn = f.name + r = TestRadio(None) + r._mmap = mock.Mock() + r._mmap.get_byte_compatible.return_value.get_packed.return_value = ( + b'thisisrawdata') + r.save_mmap(fn) + with open(fn, 'rb') as f: + filedata = f.read() + os.remove(fn) + data, metadata = chirp_common.FileBackedRadio._strip_metadata(filedata) + self.assertEqual(b'thisisrawdata', data) + self.assertEqual({}, metadata) + + def test_load_mmap_saves_metadata_on_radio(self): + class TestRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + + with tempfile.NamedTemporaryFile(suffix='.img') as f: + fn = f.name + r = TestRadio(None) + r._mmap = mock.Mock() + r._mmap.get_byte_compatible.return_value.get_packed.return_value = ( + b'thisisrawdata') + r.save_mmap(fn) + + newr = TestRadio(None) + newr.load_mmap(fn) + expected = { + 'vendor': 'Dan', + 'model': 'Foomaster 9000', + 'variant': 'R', + 'rclass': 'TestRadio', + 'chirp_version': CHIRP_VERSION, + } + self.assertEqual(expected, newr.metadata) diff --git a/tests/unit/test_directory.py b/tests/unit/test_directory.py new file mode 100644 index 0000000..d48bb48 --- /dev/null +++ b/tests/unit/test_directory.py @@ -0,0 +1,69 @@ +import base64 +import json +import tempfile + +from tests.unit import base +from chirp import chirp_common +from chirp import directory + + +class TestDirectory(base.BaseTest): + def setUp(self): + super(TestDirectory, self).setUp() + + directory.enable_reregistrations() + + class FakeAlias(chirp_common.Alias): + VENDOR = 'Taylor' + MODEL = 'Barmaster 2000' + VARIANT = 'A' + + @directory.register + class FakeRadio(chirp_common.FileBackedRadio): + VENDOR = 'Dan' + MODEL = 'Foomaster 9000' + VARIANT = 'R' + ALIASES = [FakeAlias] + + @classmethod + def match_model(cls, file_data, image_file): + return file_data == b'thisisrawdata' + + self.test_class = FakeRadio + + def _test_detect_finds_our_class(self, tempfn): + radio = directory.get_radio_by_image(tempfn) + self.assertTrue(isinstance(radio, self.test_class)) + return radio + + def test_detect_with_no_metadata(self): + with tempfile.NamedTemporaryFile() as f: + f.write(b'thisisrawdata') + f.flush() + self._test_detect_finds_our_class(f.name) + + def test_detect_with_metadata_base_class(self): + with tempfile.NamedTemporaryFile() as f: + f.write(b'thisisrawdata') + f.write(self.test_class.MAGIC + b'-') + f.write(self.test_class._make_metadata()) + f.flush() + self._test_detect_finds_our_class(f.name) + + def test_detect_with_metadata_alias_class(self): + with tempfile.NamedTemporaryFile() as f: + f.write(b'thisisrawdata') + f.write(self.test_class.MAGIC + b'-') + FakeAlias = self.test_class.ALIASES[0] + fake_metadata = base64.b64encode(json.dumps( + {'vendor': FakeAlias.VENDOR, + 'model': FakeAlias.MODEL, + 'variant': FakeAlias.VARIANT, + }).encode()) + f.write(fake_metadata) + f.flush() + radio = self._test_detect_finds_our_class(f.name) + self.assertEqual('Taylor', radio.VENDOR) + self.assertEqual('Barmaster 2000', radio.MODEL) + self.assertEqual('A', radio.VARIANT) + diff --git a/tests/unit/test_icom_clone.py b/tests/unit/test_icom_clone.py new file mode 100644 index 0000000..9ade954 --- /dev/null +++ b/tests/unit/test_icom_clone.py @@ -0,0 +1,117 @@ +# Copyright 2019 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from builtins import bytes + +import glob +import os +import logging +import unittest + +from chirp import directory +directory.safe_import_drivers() +from chirp.drivers import icf +from chirp import memmap +from tests import icom_clone_simulator + + +class BaseIcomCloneTest(): + def setUp(self): + self.radio = directory.get_radio(self.RADIO_IDENT)(None) + self.simulator = icom_clone_simulator.FakeIcomRadio(self.radio) + self.radio.set_pipe(self.simulator) + + def image_filename(self, filename): + tests_base = os.path.dirname(os.path.abspath(__file__)) + return os.path.join(tests_base, 'images', filename) + + def load_from_test_image(self, filename): + self.simulator.load_from_file(self.image_filename(filename)) + + def image_version(self, filename): + end = self.radio.get_memsize() + + with open(self.image_filename(filename), 'rb') as f: + data = f.read() + if data[-8:-6] == b'%02x' % self.radio.get_model()[0]: + return 2 + elif data[end-16:end] == b'IcomCloneFormat3': + return 3 + + + def test_sync_in(self): + test_file = self.IMAGE_FILE + self.load_from_test_image(test_file) + self.radio.sync_in() + + img_ver = self.image_version(test_file) + if img_ver == 2: + endstring = b''.join(b'%02x' % ord(c) + for c in self.radio._model[:2]) + self.assertEqual(endstring + b'0001', self.radio._mmap[-8:]) + elif img_ver == 3: + self.assertEqual(b'IcomCloneFormat3', self.radio._mmap[-16:]) + elif img_ver is None: + self.assertEqual(self.radio.get_memsize(), len(self.radio._mmap)) + + def test_sync_out(self): + self.radio._mmap = memmap.MemoryMapBytes( + bytes(b'\x00') * self.radio.get_memsize()) + self.radio._mmap[50] = bytes(b'abcdefgh') + self.radio.sync_out() + self.assertEqual(b'abcdefgh', self.simulator._memory[50:58]) + + +class TestRawRadioData(unittest.TestCase): + def test_get_payload(self): + radio = directory.get_radio('Icom_IC-2730A')(None) + payload = radio.get_payload(bytes(b'\x00\x10\xFE\x00'), True, True) + self.assertEqual(b'\x00\x10\xFF\x0E\x00\xF2', payload) + + payload = radio.get_payload(bytes(b'\x00\x10\xFE\x00'), True, False) + self.assertEqual(b'\x00\x10\xFF\x0E\x00', payload) + + def test_process_frame_payload(self): + radio = directory.get_radio('Icom_IC-2730A')(None) + data = radio.process_frame_payload(bytes(b'\x00\x10\xFF\x0E\x00')) + self.assertEqual(b'\x00\x10\xFE\x00', data) + + +class TestAdapterMeta(type): + def __new__(cls, name, parents, dct): + return super(TestAdapterMeta, cls).__new__(cls, name, parents, dct) + + +test_file_glob = os.path.join(os.path.dirname(os.path.abspath(__file__)), + '..', 'images', + 'Icom_*.img') +import sys +for image_file in glob.glob(test_file_glob): + base, _ext = os.path.splitext(os.path.basename(image_file)) + + try: + radio = directory.get_radio(base) + except Exception: + continue + + if issubclass(radio, icf.IcomRawCloneModeRadio): + # The simulator does not behave like a raw radio + continue + + class_name = 'Test_%s' % base + sys.modules[__name__].__dict__[class_name] = \ + TestAdapterMeta(class_name, + (BaseIcomCloneTest, unittest.TestCase), + dict(RADIO_IDENT=base, IMAGE_FILE=image_file)) diff --git a/tests/unit/test_import_logic.py b/tests/unit/test_import_logic.py new file mode 100644 index 0000000..ecd9aa9 --- /dev/null +++ b/tests/unit/test_import_logic.py @@ -0,0 +1,373 @@ +from tests.unit import base +from chirp import import_logic +from chirp import chirp_common +from chirp import errors + + +class FakeRadio(chirp_common.Radio): + def __init__(self, arg): + self.POWER_LEVELS = list([chirp_common.PowerLevel('lo', watts=5), + chirp_common.PowerLevel('hi', watts=50)]) + self.TMODES = list(['', 'Tone', 'TSQL']) + self.HAS_CTONE = True + self.HAS_RX_DTCS = False + self.MODES = list(['FM', 'AM', 'DV']) + self.DUPLEXES = list(['', '-', '+']) + + def filter_name(self, name): + return 'filtered-name' + + def get_features(self): + rf = chirp_common.RadioFeatures() + rf.valid_power_levels = self.POWER_LEVELS + rf.valid_tmodes = self.TMODES + rf.valid_modes = self.MODES + rf.valid_duplexes = self.DUPLEXES + rf.has_ctone = self.HAS_CTONE + rf.has_rx_dtcs = self.HAS_RX_DTCS + return rf + + +class FakeDstarRadio(FakeRadio, chirp_common.IcomDstarSupport): + pass + + +class DstarTests(base.BaseTest): + def _test_ensure_has_calls(self, mem, + ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls): + radio = FakeDstarRadio(None) + self.mox.StubOutWithMock(radio, 'get_urcall_list') + self.mox.StubOutWithMock(radio, 'get_repeater_call_list') + radio.get_urcall_list().AndReturn(ini_urcalls) + radio.get_repeater_call_list().AndReturn(ini_rptcalls) + self.mox.ReplayAll() + import_logic.ensure_has_calls(radio, mem) + self.assertEqual(sorted(ini_urcalls), sorted(exp_urcalls)) + self.assertEqual(sorted(ini_rptcalls), sorted(exp_rptcalls)) + + def test_ensure_has_calls_empty(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['', '', '', '', ''] + ini_rptcalls = ['', '', '', '', ''] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[0] = mem.dv_urcall + exp_rptcalls[0] = mem.dv_rpt1call + exp_rptcalls[1] = mem.dv_rpt2call + self._test_ensure_has_calls(mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + def test_ensure_has_calls_partial(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['FOO', 'BAR', '', '', ''] + ini_rptcalls = ['FOO', 'BAR', '', '', ''] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[2] = mem.dv_urcall + exp_rptcalls[2] = mem.dv_rpt1call + exp_rptcalls[3] = mem.dv_rpt2call + self._test_ensure_has_calls(mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + def test_ensure_has_calls_almost_full(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['FOO', 'BAR', 'BAZ', 'BAT', ''] + ini_rptcalls = ['FOO', 'BAR', 'BAZ', '', ''] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[4] = mem.dv_urcall + exp_rptcalls[3] = mem.dv_rpt1call + exp_rptcalls[4] = mem.dv_rpt2call + self._test_ensure_has_calls(mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + def test_ensure_has_calls_urcall_full(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['FOO', 'BAR', 'BAZ', 'BAT', 'BOOM'] + ini_rptcalls = ['FOO', 'BAR', 'BAZ', '', ''] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[4] = mem.dv_urcall + exp_rptcalls[3] = mem.dv_rpt1call + exp_rptcalls[4] = mem.dv_rpt2call + self.assertRaises(errors.RadioError, + self._test_ensure_has_calls, + mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + def test_ensure_has_calls_rptcall_full1(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['FOO', 'BAR', 'BAZ', 'BAT', ''] + ini_rptcalls = ['FOO', 'BAR', 'BAZ', 'BAT', ''] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[4] = mem.dv_urcall + exp_rptcalls[3] = mem.dv_rpt1call + exp_rptcalls[4] = mem.dv_rpt2call + self.assertRaises(errors.RadioError, + self._test_ensure_has_calls, + mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + def test_ensure_has_calls_rptcall_full2(self): + mem = chirp_common.DVMemory() + mem.dv_urcall = 'KK7DS' + mem.dv_rpt1call = 'KD7RFI B' + mem.dv_rpt2call = 'KD7RFI G' + ini_urcalls = ['FOO', 'BAR', 'BAZ', 'BAT', ''] + ini_rptcalls = ['FOO', 'BAR', 'BAZ', 'BAT', 'BOOM'] + exp_urcalls = list(ini_urcalls) + exp_rptcalls = list(ini_rptcalls) + exp_urcalls[4] = mem.dv_urcall + exp_rptcalls[3] = mem.dv_rpt1call + exp_rptcalls[4] = mem.dv_rpt2call + self.assertRaises(errors.RadioError, + self._test_ensure_has_calls, + mem, ini_urcalls, ini_rptcalls, + exp_urcalls, exp_rptcalls) + + +class ImportFieldTests(base.BaseTest): + def test_import_name(self): + mem = chirp_common.Memory() + mem.name = 'foo' + import_logic._import_name(FakeRadio(None), None, mem) + self.assertEqual(mem.name, 'filtered-name') + + def test_import_power_same(self): + radio = FakeRadio(None) + same_rf = radio.get_features() + mem = chirp_common.Memory() + mem.power = same_rf.valid_power_levels[0] + import_logic._import_power(radio, same_rf, mem) + self.assertEqual(mem.power, same_rf.valid_power_levels[0]) + + def test_import_power_no_src(self): + radio = FakeRadio(None) + src_rf = chirp_common.RadioFeatures() + mem = chirp_common.Memory() + mem.power = None + import_logic._import_power(radio, src_rf, mem) + self.assertEqual(mem.power, radio.POWER_LEVELS[0]) + + def test_import_power_no_dst(self): + radio = FakeRadio(None) + src_rf = radio.get_features() # Steal a copy before we stub out + self.mox.StubOutWithMock(radio, 'get_features') + radio.get_features().AndReturn(chirp_common.RadioFeatures()) + self.mox.ReplayAll() + mem = chirp_common.Memory() + mem.power = src_rf.valid_power_levels[0] + import_logic._import_power(radio, src_rf, mem) + self.assertEqual(mem.power, None) + + def test_import_power_closest(self): + radio = FakeRadio(None) + src_rf = chirp_common.RadioFeatures() + src_rf.valid_power_levels = [ + chirp_common.PowerLevel('foo', watts=7), + chirp_common.PowerLevel('bar', watts=51), + chirp_common.PowerLevel('baz', watts=1), + ] + mem = chirp_common.Memory() + mem.power = src_rf.valid_power_levels[0] + import_logic._import_power(radio, src_rf, mem) + self.assertEqual(mem.power, radio.POWER_LEVELS[0]) + + def test_import_tone_diffA_tsql(self): + radio = FakeRadio(None) + src_rf = chirp_common.RadioFeatures() + src_rf.has_ctone = False + mem = chirp_common.Memory() + mem.tmode = 'TSQL' + mem.rtone = 100.0 + import_logic._import_tone(radio, src_rf, mem) + self.assertEqual(mem.ctone, 100.0) + + def test_import_tone_diffB_tsql(self): + radio = FakeRadio(None) + radio.HAS_CTONE = False + src_rf = chirp_common.RadioFeatures() + src_rf.has_ctone = True + mem = chirp_common.Memory() + mem.tmode = 'TSQL' + mem.ctone = 100.0 + import_logic._import_tone(radio, src_rf, mem) + self.assertEqual(mem.rtone, 100.0) + + def test_import_dtcs_diffA_dtcs(self): + radio = FakeRadio(None) + src_rf = chirp_common.RadioFeatures() + src_rf.has_rx_dtcs = True + mem = chirp_common.Memory() + mem.tmode = 'DTCS' + mem.rx_dtcs = 32 + import_logic._import_dtcs(radio, src_rf, mem) + self.assertEqual(mem.dtcs, 32) + + def test_import_dtcs_diffB_dtcs(self): + radio = FakeRadio(None) + radio.HAS_RX_DTCS = True + src_rf = chirp_common.RadioFeatures() + src_rf.has_rx_dtcs = False + mem = chirp_common.Memory() + mem.tmode = 'DTCS' + mem.dtcs = 32 + import_logic._import_dtcs(radio, src_rf, mem) + self.assertEqual(mem.rx_dtcs, 32) + + def test_import_mode_valid_fm(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.mode = 'Auto' + mem.freq = 146000000 + import_logic._import_mode(radio, None, mem) + self.assertEqual(mem.mode, 'FM') + + def test_import_mode_valid_am(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.mode = 'Auto' + mem.freq = 18000000 + import_logic._import_mode(radio, None, mem) + self.assertEqual(mem.mode, 'AM') + + def test_import_mode_invalid(self): + radio = FakeRadio(None) + radio.MODES.remove('AM') + mem = chirp_common.Memory() + mem.mode = 'Auto' + mem.freq = 1800000 + self.assertRaises(import_logic.DestNotCompatible, + import_logic._import_mode, radio, None, mem) + + def test_import_duplex_vhf(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.freq = 146000000 + mem.offset = 146600000 + mem.duplex = 'split' + import_logic._import_duplex(radio, None, mem) + self.assertEqual(mem.duplex, '+') + self.assertEqual(mem.offset, 600000) + + def test_import_duplex_negative(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.freq = 146600000 + mem.offset = 146000000 + mem.duplex = 'split' + import_logic._import_duplex(radio, None, mem) + self.assertEqual(mem.duplex, '-') + self.assertEqual(mem.offset, 600000) + + def test_import_duplex_uhf(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.freq = 431000000 + mem.offset = 441000000 + mem.duplex = 'split' + import_logic._import_duplex(radio, None, mem) + self.assertEqual(mem.duplex, '+') + self.assertEqual(mem.offset, 10000000) + + def test_import_duplex_too_big_vhf(self): + radio = FakeRadio(None) + mem = chirp_common.Memory() + mem.freq = 146000000 + mem.offset = 246000000 + mem.duplex = 'split' + self.assertRaises(import_logic.DestNotCompatible, + import_logic._import_duplex, radio, None, mem) + + def test_import_mem(self, errors=[]): + radio = FakeRadio(None) + src_rf = chirp_common.RadioFeatures() + mem = chirp_common.Memory() + + self.mox.StubOutWithMock(mem, 'dupe') + self.mox.StubOutWithMock(import_logic, '_import_name') + self.mox.StubOutWithMock(import_logic, '_import_power') + self.mox.StubOutWithMock(import_logic, '_import_tone') + self.mox.StubOutWithMock(import_logic, '_import_dtcs') + self.mox.StubOutWithMock(import_logic, '_import_mode') + self.mox.StubOutWithMock(import_logic, '_import_duplex') + self.mox.StubOutWithMock(radio, 'validate_memory') + + mem.dupe().AndReturn(mem) + import_logic._import_name(radio, src_rf, mem) + import_logic._import_power(radio, src_rf, mem) + import_logic._import_tone(radio, src_rf, mem) + import_logic._import_dtcs(radio, src_rf, mem) + import_logic._import_mode(radio, src_rf, mem) + import_logic._import_duplex(radio, src_rf, mem) + radio.validate_memory(mem).AndReturn(errors) + + self.mox.ReplayAll() + + import_logic.import_mem(radio, src_rf, mem) + + def test_import_mem_with_warnings(self): + self.test_import_mem([chirp_common.ValidationWarning('Test')]) + + def test_import_mem_with_errors(self): + self.assertRaises(import_logic.DestNotCompatible, + self.test_import_mem, + [chirp_common.ValidationError('Test')]) + + def test_import_bank(self): + dst_mem = chirp_common.Memory() + dst_mem.number = 1 + src_mem = chirp_common.Memory() + src_mem.number = 2 + dst_radio = FakeRadio(None) + src_radio = FakeRadio(None) + dst_bm = chirp_common.BankModel(dst_radio) + src_bm = chirp_common.BankModel(src_radio) + + dst_banks = [chirp_common.Bank(dst_bm, 0, 'A'), + chirp_common.Bank(dst_bm, 1, 'B'), + chirp_common.Bank(dst_bm, 2, 'C'), + ] + src_banks = [chirp_common.Bank(src_bm, 1, '1'), + chirp_common.Bank(src_bm, 2, '2'), + chirp_common.Bank(src_bm, 3, '3'), + ] + + self.mox.StubOutWithMock(dst_radio, 'get_mapping_models') + self.mox.StubOutWithMock(src_radio, 'get_mapping_models') + self.mox.StubOutWithMock(dst_bm, 'get_mappings') + self.mox.StubOutWithMock(src_bm, 'get_mappings') + self.mox.StubOutWithMock(dst_bm, 'get_memory_mappings') + self.mox.StubOutWithMock(src_bm, 'get_memory_mappings') + self.mox.StubOutWithMock(dst_bm, 'remove_memory_from_mapping') + self.mox.StubOutWithMock(dst_bm, 'add_memory_to_mapping') + + dst_radio.get_mapping_models().AndReturn([dst_bm]) + dst_bm.get_mappings().AndReturn(dst_banks) + src_radio.get_mapping_models().AndReturn([src_bm]) + src_bm.get_mappings().AndReturn(src_banks) + src_bm.get_memory_mappings(src_mem).AndReturn([src_banks[0]]) + dst_bm.get_memory_mappings(dst_mem).AndReturn([dst_banks[1]]) + dst_bm.remove_memory_from_mapping(dst_mem, dst_banks[1]) + dst_bm.add_memory_to_mapping(dst_mem, dst_banks[0]) + + self.mox.ReplayAll() + + import_logic.import_bank(dst_radio, src_radio, dst_mem, src_mem) diff --git a/tests/unit/test_mappingmodel.py b/tests/unit/test_mappingmodel.py new file mode 100644 index 0000000..2f263d7 --- /dev/null +++ b/tests/unit/test_mappingmodel.py @@ -0,0 +1,283 @@ +# Copyright 2013 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from tests.unit import base +from chirp import chirp_common +from chirp.drivers import icf + + +class TestBaseMapping(base.BaseTest): + CLS = chirp_common.MemoryMapping + + def test_mapping(self): + model = chirp_common.MappingModel(None, 'Foo') + mapping = self.CLS(model, 1, 'Foo') + self.assertEqual(str(mapping), 'Foo') + self.assertEqual(mapping.get_name(), 'Foo') + self.assertEqual(mapping.get_index(), 1) + self.assertEqual(repr(mapping), '%s-1' % self.CLS.__name__) + self.assertEqual(mapping._model, model) + + def test_mapping_eq(self): + mapping1 = self.CLS(None, 1, 'Foo') + mapping2 = self.CLS(None, 1, 'Bar') + mapping3 = self.CLS(None, 2, 'Foo') + + self.assertEqual(mapping1, mapping2) + self.assertNotEqual(mapping1, mapping3) + + +class TestBaseBank(TestBaseMapping): + CLS = chirp_common.Bank + + +class _TestBaseClass(base.BaseTest): + ARGS = tuple() + + def setUp(self): + super(_TestBaseClass, self).setUp() + self.model = self.CLS(*self.ARGS) + + def _test_base(self, method, *args): + self.assertRaises(NotImplementedError, + getattr(self.model, method), *args) + + +class TestBaseMappingModel(_TestBaseClass): + CLS = chirp_common.MappingModel + ARGS = tuple([None, 'Foo']) + + def test_base_class(self): + methods = [('get_num_mappings', ()), + ('get_mappings', ()), + ('add_memory_to_mapping', (None, None)), + ('remove_memory_from_mapping', (None, None)), + ('get_mapping_memories', (None,)), + ('get_memory_mappings', (None,)), + ] + for method, args in methods: + self._test_base(method, *args) + + def test_get_name(self): + self.assertEqual(self.model.get_name(), 'Foo') + + +class TestBaseBankModel(TestBaseMappingModel): + ARGS = tuple([None]) + CLS = chirp_common.BankModel + + def test_get_name(self): + self.assertEqual(self.model.get_name(), 'Banks') + + +class TestBaseMappingModelIndexInterface(_TestBaseClass): + CLS = chirp_common.MappingModelIndexInterface + + def test_base_class(self): + methods = [('get_index_bounds', ()), + ('get_memory_index', (None, None)), + ('set_memory_index', (None, None, None)), + ('get_next_mapping_index', (None,)), + ] + for method, args in methods: + self._test_base(method, *args) + + +class TestIcomBanks(TestBaseMapping): + def test_icom_bank(self): + bank = icf.IcomBank(None, 1, 'Foo') + # IcomBank has an index attribute used by IcomBankModel + self.assertTrue(hasattr(bank, 'index')) + + +class TestIcomBankModel(base.BaseTest): + CLS = icf.IcomBankModel + + def _get_rf(self): + rf = chirp_common.RadioFeatures() + rf.memory_bounds = (1, 10) + return rf + + def setUp(self): + super(TestIcomBankModel, self).setUp() + + class FakeRadio(icf.IcomCloneModeRadio): + _num_banks = 10 + _bank_index_bounds = (0, 10) + + def get_features(the_radio): + return self._get_rf() + + def _set_bank(self, number, index): + pass + + def _get_bank(self, number): + pass + + def _get_bank_index(self, number): + pass + + def _set_bank_index(self, number, index): + pass + + def get_memory(self, number): + pass + + self._radio = FakeRadio(None) + self._model = self.CLS(self._radio) + self.mox.StubOutWithMock(self._radio, '_set_bank') + self.mox.StubOutWithMock(self._radio, '_get_bank') + self.mox.StubOutWithMock(self._radio, '_set_bank_index') + self.mox.StubOutWithMock(self._radio, '_get_bank_index') + self.mox.StubOutWithMock(self._radio, 'get_memory') + + def test_get_num_mappings(self): + self.assertEqual(self._model.get_num_mappings(), 10) + + def test_get_mappings(self): + banks = self._model.get_mappings() + self.assertEqual(len(banks), 10) + i = 0 + for bank in banks: + index = chr(ord("A") + i) + self.assertEqual(bank.get_index(), index) + self.assertEqual(bank.get_name(), 'BANK-%s' % index) + self.assertEqual(bank.index, i) + i += 1 + + def test_add_memory_to_mapping(self): + mem = chirp_common.Memory() + mem.number = 5 + banks = self._model.get_mappings() + bank = banks[2] + self._radio._set_bank(5, 2) + self.mox.ReplayAll() + self._model.add_memory_to_mapping(mem, bank) + + def _setup_test_remove_memory_from_mapping(self, curbank): + mem = chirp_common.Memory() + mem.number = 5 + banks = self._model.get_mappings() + bank = banks[2] + self._radio._get_bank(5).AndReturn(curbank) + if curbank == 2: + self._radio._set_bank(5, None) + self.mox.ReplayAll() + return mem, bank + + def test_remove_memory_from_mapping(self): + mem, bank = self._setup_test_remove_memory_from_mapping(2) + self._model.remove_memory_from_mapping(mem, bank) + + def test_remove_memory_from_mapping_wrong_bank(self): + mem, bank = self._setup_test_remove_memory_from_mapping(3) + self.assertRaises(Exception, + self._model.remove_memory_from_mapping, mem, bank) + + def test_remove_memory_from_mapping_no_bank(self): + mem, bank = self._setup_test_remove_memory_from_mapping(None) + self.assertRaises(Exception, + self._model.remove_memory_from_mapping, mem, bank) + + def test_get_mapping_memories(self): + banks = self._model.get_mappings() + expected = [] + for i in range(1, 10): + should_include = bool(i % 2) + self._radio._get_bank(i).AndReturn( + should_include and banks[1].index or None) + if should_include: + self._radio.get_memory(i).AndReturn(i) + expected.append(i) + self.mox.ReplayAll() + members = self._model.get_mapping_memories(banks[1]) + self.assertEqual(members, expected) + + def test_get_memory_mappings(self): + banks = self._model.get_mappings() + mem1 = chirp_common.Memory() + mem1.number = 5 + mem2 = chirp_common.Memory() + mem2.number = 6 + self._radio._get_bank(mem1.number).AndReturn(2) + self._radio._get_bank(mem2.number).AndReturn(None) + self.mox.ReplayAll() + self.assertEqual(self._model.get_memory_mappings(mem1)[0], banks[2]) + self.assertEqual(self._model.get_memory_mappings(mem2), []) + + +class TestIcomIndexedBankModel(TestIcomBankModel): + CLS = icf.IcomIndexedBankModel + + def _get_rf(self): + rf = super(TestIcomIndexedBankModel, self)._get_rf() + rf.has_bank_index = True + return rf + + def test_get_index_bounds(self): + self.assertEqual(self._model.get_index_bounds(), (0, 10)) + + def test_get_memory_index(self): + mem = chirp_common.Memory() + mem.number = 5 + self._radio._get_bank_index(mem.number).AndReturn(1) + self.mox.ReplayAll() + self.assertEqual(self._model.get_memory_index(mem, None), 1) + + def test_set_memory_index(self): + mem = chirp_common.Memory() + mem.number = 5 + banks = self._model.get_mappings() + self.mox.StubOutWithMock(self._model, 'get_memory_mappings') + self._model.get_memory_mappings(mem).AndReturn([banks[3]]) + self._radio._set_bank_index(mem.number, 1) + self.mox.ReplayAll() + self._model.set_memory_index(mem, banks[3], 1) + + def test_set_memory_index_bad_bank(self): + mem = chirp_common.Memory() + mem.number = 5 + banks = self._model.get_mappings() + self.mox.StubOutWithMock(self._model, 'get_memory_mappings') + self._model.get_memory_mappings(mem).AndReturn([banks[4]]) + self.mox.ReplayAll() + self.assertRaises(Exception, + self._model.set_memory_index, mem, banks[3], 1) + + def test_set_memory_index_bad_index(self): + mem = chirp_common.Memory() + mem.number = 5 + banks = self._model.get_mappings() + self.mox.StubOutWithMock(self._model, 'get_memory_mappings') + self._model.get_memory_mappings(mem).AndReturn([banks[3]]) + self.mox.ReplayAll() + self.assertRaises(Exception, + self._model.set_memory_index, mem, banks[3], 99) + + def test_get_next_mapping_index(self): + banks = self._model.get_mappings() + for i in range(*self._radio.get_features().memory_bounds): + self._radio._get_bank(i).AndReturn((i % 2) and banks[1].index) + if bool(i % 2): + self._radio._get_bank_index(i).AndReturn(i) + idx = 0 + for i in range(*self._radio.get_features().memory_bounds): + self._radio._get_bank(i).AndReturn((i % 2) and banks[2].index) + if i % 2: + self._radio._get_bank_index(i).AndReturn(idx) + idx += 1 + self.mox.ReplayAll() + self.assertEqual(self._model.get_next_mapping_index(banks[1]), 0) + self.assertEqual(self._model.get_next_mapping_index(banks[2]), 5) diff --git a/tests/unit/test_memedit_edits.py b/tests/unit/test_memedit_edits.py new file mode 100644 index 0000000..fdf971c --- /dev/null +++ b/tests/unit/test_memedit_edits.py @@ -0,0 +1,82 @@ +try: + import mox +except ImportError: + from mox3 import mox +from tests.unit import base + +__builtins__["_"] = lambda s: s + +memedit = None + + +class TestEdits(base.BaseTest): + def setUp(self): + global memedit + super(TestEdits, self).setUp() + base.mock_gtk() + from chirp.ui import memedit as memedit_module + memedit = memedit_module + + def tearDown(self): + super(TestEdits, self).tearDown() + base.unmock_gtk() + + def _test_tone_column_change(self, col, + ini_tmode='', ini_cmode='', + exp_tmode=None, exp_cmode=None): + editor = self.mox.CreateMock(memedit.MemoryEditor) + editor._config = self.mox.CreateMockAnything() + editor._config.get_bool("no_smart_tmode").AndReturn(False) + editor.col = lambda x: x + editor.store = self.mox.CreateMockAnything() + editor.store.get_iter('path').AndReturn('iter') + editor.store.get('iter', 'Tone Mode', 'Cross Mode').AndReturn( + (ini_tmode, ini_cmode)) + if exp_tmode: + editor.store.set('iter', 'Tone Mode', exp_tmode) + if exp_cmode and col != 'Cross Mode': + editor.store.set('iter', 'Cross Mode', exp_cmode) + self.mox.ReplayAll() + memedit.MemoryEditor.ed_tone_field(editor, None, 'path', None, col) + + def _test_auto_tone_mode(self, col, exp_tmode, exp_cmode): + cross_exp_cmode = (exp_tmode == "Cross" and exp_cmode or None) + + # No tmode -> expected tmode, maybe requires cross mode change + self._test_tone_column_change(col, exp_tmode=exp_tmode, + exp_cmode=cross_exp_cmode) + + # Expected tmode does not re-set tmode, may change cmode + self._test_tone_column_change(col, ini_tmode=exp_tmode, + exp_cmode=cross_exp_cmode) + + # Invalid tmode -> expected, may change cmode + self._test_tone_column_change(col, ini_tmode="foo", + exp_tmode=exp_tmode, + exp_cmode=cross_exp_cmode) + + # Expected cmode does not re-set cmode + self._test_tone_column_change(col, ini_tmode="Cross", + ini_cmode=exp_cmode) + + # Invalid cmode -> expected + self._test_tone_column_change(col, ini_tmode="Cross", + ini_cmode="foo", exp_cmode=exp_cmode) + + def test_auto_tone_mode_tone(self): + self._test_auto_tone_mode('Tone', 'Tone', 'Tone->Tone') + + def test_auto_tone_mode_tsql(self): + self._test_auto_tone_mode('ToneSql', 'TSQL', 'Tone->Tone') + + def test_auto_tone_mode_dtcs(self): + self._test_auto_tone_mode('DTCS Code', 'DTCS', 'DTCS->') + + def test_auto_tone_mode_dtcs_rx(self): + self._test_auto_tone_mode('DTCS Rx Code', 'Cross', '->DTCS') + + def test_auto_tone_mode_dtcs_pol(self): + self._test_auto_tone_mode('DTCS Pol', 'DTCS', 'DTCS->') + + def test_auto_tone_mode_cross(self): + self._test_auto_tone_mode('Cross Mode', 'Cross', 'Tone->Tone') diff --git a/tests/unit/test_platform.py b/tests/unit/test_platform.py new file mode 100644 index 0000000..394f894 --- /dev/null +++ b/tests/unit/test_platform.py @@ -0,0 +1,62 @@ +# Copyright 2013 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import unittest +try: + import mox +except ImportError: + from mox3 import mox +import os + +from tests.unit import base +from chirp import platform + + +class Win32PlatformTest(base.BaseTest): + def _test_init(self): + self.mox.StubOutWithMock(platform, 'comports') + self.mox.StubOutWithMock(os, 'mkdir') + self.mox.StubOutWithMock(os, 'getenv') + os.mkdir(mox.IgnoreArg()) + os.getenv("APPDATA").AndReturn("foo") + os.getenv("USERPROFILE").AndReturn("foo") + + def test_init(self): + self._test_init() + self.mox.ReplayAll() + platform.Win32Platform() + + def test_serial_ports_sorted(self): + self._test_init() + + fake_comports = [] + numbers = [1, 11, 2, 12, 7, 3, 123] + for i in numbers: + fake_comports.append(("COM%i" % i, None, None)) + + platform.comports().AndReturn(fake_comports) + self.mox.ReplayAll() + ports = platform.Win32Platform().list_serial_ports() + + correct_order = ["COM%i" % i for i in sorted(numbers)] + self.assertEqual(ports, correct_order) + + def test_serial_ports_bad_portnames(self): + self._test_init() + + platform.comports().AndReturn([('foo', None, None)]) + self.mox.ReplayAll() + ports = platform.Win32Platform().list_serial_ports() + self.assertEqual(ports, ['foo']) diff --git a/tests/unit/test_repeaterbook.py b/tests/unit/test_repeaterbook.py new file mode 100644 index 0000000..2563b3f --- /dev/null +++ b/tests/unit/test_repeaterbook.py @@ -0,0 +1,28 @@ +import tempfile +import unittest + +from chirp import chirp_common +from chirp.drivers import repeaterbook + + +class TestRepeaterBook(unittest.TestCase): + def _fetch_and_load(self, query): + fn = tempfile.mktemp('.csv') + chirp_common.urlretrieve(query, fn) + radio = repeaterbook.RBRadio(fn) + return fn, radio + + def test_political(self): + query = "http://www.repeaterbook.com/repeaters/downloads/chirp.php" + \ + "?func=default&state_id=%s&band=%s&freq=%%&band6=%%&loc=%%" + \ + "&county_id=%s&status_id=%%&features=%%&coverage=%%&use=%%" + query = query % ('41', '%%', '005') + self._fetch_and_load(query) + + def test_proximity(self): + loc = '97124' + band = '%%' + dist = '20' + query = "https://www.repeaterbook.com/repeaters/downloads/CHIRP/" \ + "app_direct.php?loc=%s&band=%s&dist=%s" % (loc, band, dist) + self._fetch_and_load(query) diff --git a/tests/unit/test_settings.py b/tests/unit/test_settings.py new file mode 100644 index 0000000..ccc4308 --- /dev/null +++ b/tests/unit/test_settings.py @@ -0,0 +1,140 @@ +# Copyright 2013 Dan Smith +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from tests.unit import base +from chirp import settings + + +class TestSettingValues(base.BaseTest): + def _set_and_test(self, rsv, *values): + for value in values: + rsv.set_value(value) + self.assertEqual(rsv.get_value(), value) + + def _set_and_catch(self, rsv, *values): + for value in values: + self.assertRaises(settings.InvalidValueError, + rsv.set_value, value) + + def test_radio_setting_value_integer(self): + value = settings.RadioSettingValueInteger(0, 10, 5) + self.assertEqual(value.get_value(), 5) + self._set_and_test(value, 1, 0, 10) + self._set_and_catch(value, -1, 11) + + def test_radio_setting_value_float(self): + value = settings.RadioSettingValueFloat(1.0, 10.5, 5.0) + self.assertEqual(value.get_value(), 5.0) + self._set_and_test(value, 2.5, 1.0, 10.5) + self._set_and_catch(value, 0.9, 10.6, -1.5) + + def test_radio_setting_value_boolean(self): + value = settings.RadioSettingValueBoolean(True) + self.assertTrue(value.get_value()) + self._set_and_test(value, True, False) + + def test_radio_setting_value_list(self): + opts = ["Abc", "Def", "Ghi"] + value = settings.RadioSettingValueList(opts, "Abc") + self.assertEqual(value.get_value(), "Abc") + self.assertEqual(int(value), 0) + self._set_and_test(value, "Def", "Ghi", "Abc") + self._set_and_catch(value, "Jkl", "Xyz") + self.assertEqual(value.get_options(), opts) + + def test_radio_setting_value_string(self): + value = settings.RadioSettingValueString(1, 5, "foo", autopad=False) + self.assertEqual(value.get_value(), "foo") + self.assertEqual(str(value), "foo") + self._set_and_test(value, "a", "abc", "abdef") + self._set_and_catch(value, "", "abcdefg") + + def test_validate_callback(self): + class TestException(Exception): + pass + + value = settings.RadioSettingValueString(0, 5, "foo", autopad=False) + + def test_validate(val): + if val == "bar": + raise TestException() + value.set_validate_callback(test_validate) + value.set_value("baz") + self.assertRaises(TestException, value.set_value, "bar") + + def test_changed(self): + value = settings.RadioSettingValueBoolean(False) + self.assertFalse(value.changed()) + value.set_value(False) + self.assertFalse(value.changed()) + value.set_value(True) + self.assertTrue(value.changed()) + + +class TestSettingContainers(base.BaseTest): + def test_radio_setting_group(self): + s1 = settings.RadioSetting("s1", "Setting 1") + s2 = settings.RadioSetting("s2", "Setting 2") + s3 = settings.RadioSetting("s3", "Setting 3") + group = settings.RadioSettingGroup("foo", "Foo Group", s1) + self.assertEqual(group.get_name(), "foo") + self.assertEqual(group.get_shortname(), "Foo Group") + self.assertEqual(group.values(), [s1]) + self.assertEqual(group.keys(), ["s1"]) + group.append(s2) + self.assertEqual(group.items(), [("s1", s1), ("s2", s2)]) + self.assertEqual(group["s1"], s1) + group["s3"] = s3 + self.assertEqual(group.values(), [s1, s2, s3]) + self.assertEqual(group.keys(), ["s1", "s2", "s3"]) + self.assertEqual([x for x in group], [s1, s2, s3]) + + def set_dupe(): + group["s3"] = s3 + self.assertRaises(KeyError, set_dupe) + + def test_radio_setting(self): + val = settings.RadioSettingValueBoolean(True) + rs = settings.RadioSetting("foo", "Foo", val) + self.assertEqual(rs.value, val) + rs.value = False + self.assertEqual(val.get_value(), False) + + def test_radio_setting_multi(self): + val1 = settings.RadioSettingValueBoolean(True) + val2 = settings.RadioSettingValueBoolean(False) + rs = settings.RadioSetting("foo", "Foo", val1, val2) + self.assertEqual(rs[0], val1) + self.assertEqual(rs[1], val2) + rs[0] = False + rs[1] = True + self.assertEqual(val1.get_value(), False) + self.assertEqual(val2.get_value(), True) + + def test_apply_callback(self): + class TestException(Exception): + pass + + rs = settings.RadioSetting("foo", "Foo") + self.assertFalse(rs.has_apply_callback()) + + def test_cb(setting, data1, data2): + self.assertEqual(setting, rs) + self.assertEqual(data1, "foo") + self.assertEqual(data2, "bar") + raise TestException() + rs.set_apply_callback(test_cb, "foo", "bar") + self.assertTrue(rs.has_apply_callback()) + self.assertRaises(TestException, rs.run_apply_callback) diff --git a/tests/unit/test_shiftdialog.py b/tests/unit/test_shiftdialog.py new file mode 100644 index 0000000..fe0b62d --- /dev/null +++ b/tests/unit/test_shiftdialog.py @@ -0,0 +1,112 @@ +import unittest +try: + import mox +except ImportError: + from mox3 import mox + +from tests.unit import base +from chirp import chirp_common +from chirp import errors + +shiftdialog = None + + +class FakeRadio(object): + def __init__(self, *memories): + self._mems = {} + for location in memories: + mem = chirp_common.Memory() + mem.number = location + self._mems[location] = mem + self._features = chirp_common.RadioFeatures() + + def get_features(self): + return self._features + + def get_memory(self, location): + try: + return self._mems[location] + except KeyError: + mem = chirp_common.Memory() + mem.number = location + mem.empty = True + return mem + + def set_memory(self, memory): + self._mems[memory.number] = memory + + def erase_memory(self, location): + del self._mems[location] + + +class FakeRadioThread(object): + def __init__(self, radio): + self.radio = radio + + def lock(self): + pass + + def unlock(self): + pass + + +class ShiftDialogTest(base.BaseTest): + def setUp(self): + global shiftdialog + super(ShiftDialogTest, self).setUp() + base.mock_gtk() + from chirp.ui import shiftdialog as shiftdialog_module + shiftdialog = shiftdialog_module + + def tearDown(self): + super(ShiftDialogTest, self).tearDown() + base.unmock_gtk() + + def _test_hole(self, fn, starting, arg, expected): + radio = FakeRadio(*tuple(starting)) + radio.get_features().memory_bounds = (0, 5) + sd = shiftdialog.ShiftDialog(FakeRadioThread(radio)) + + if isinstance(arg, tuple): + getattr(sd, fn)(*arg) + else: + getattr(sd, fn)(arg) + + self.assertEqual(expected, sorted(radio._mems.keys())) + self.assertEqual(expected, + sorted([mem.number for mem in radio._mems.values()])) + + def _test_delete_hole(self, starting, arg, expected): + self._test_hole('_delete_hole', starting, arg, expected) + + def _test_insert_hole(self, starting, pos, expected): + self._test_hole('_insert_hole', starting, pos, expected) + + def test_delete_hole_with_hole(self): + self._test_delete_hole([1, 2, 3, 5], + 2, + [1, 2, 5]) + + def test_delete_hole_without_hole(self): + self._test_delete_hole([1, 2, 3, 4, 5], + 2, + [1, 2, 3, 4]) + + def test_delete_hole_with_all(self): + self._test_delete_hole([1, 2, 3, 5], + (2, True), + [1, 2, 4]) + + def test_delete_hole_with_all_full(self): + self._test_delete_hole([1, 2, 3, 4, 5], + (2, True), + [1, 2, 3, 4]) + + def test_insert_hole_with_space(self): + self._test_insert_hole([1, 2, 3, 5], + 2, + [1, 3, 4, 5]) + + def test_insert_hole_without_space(self): + self.assertRaises(errors.InvalidMemoryLocation, + self._test_insert_hole, [1, 2, 3, 4, 5], 2, []) diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py new file mode 100644 index 0000000..575f3e3 --- /dev/null +++ b/tests/unit/test_utils.py @@ -0,0 +1,21 @@ +from chirp import util +from tests.unit import base + + +class TestUtils(base.BaseTest): + def test_hexprint_with_string(self): + util.hexprint('00000000000000') + + def test_hexprint_with_bytes(self): + util.hexprint(b'00000000000000') + + def test_struct_pack(self): + struct = util.StringStruct + + self.assertEqual('\x00c', + struct.pack('bc', 0, 'c')) + + def test_struct_unpack(self): + struct = util.StringStruct + + self.assertEqual((1, 'c'), struct.unpack('bc', '\x01c')) diff --git a/tests/unit/test_yaesu_clone.py b/tests/unit/test_yaesu_clone.py new file mode 100644 index 0000000..869c3b5 --- /dev/null +++ b/tests/unit/test_yaesu_clone.py @@ -0,0 +1,41 @@ +from builtins import bytes +import unittest + +from chirp.drivers import yaesu_clone +from chirp import memmap + + +class TestYaesuChecksum(unittest.TestCase): + def _test_checksum(self, mmap): + cs = yaesu_clone.YaesuChecksum(0, 2, 3) + + self.assertEqual(42, cs.get_existing(mmap)) + self.assertEqual(0x8A, cs.get_calculated(mmap)) + try: + mmap = mmap.get_byte_compatible() + mmap[0] = 3 + except AttributeError: + # str or bytes + try: + # str + mmap = memmap.MemoryMap('\x03' + mmap[1:]) + except TypeError: + # bytes + mmap = memmap.MemoryMapBytes(b'\x03' + mmap[1:]) + + cs.update(mmap) + self.assertEqual(95, cs.get_calculated(mmap)) + + def test_with_MemoryMap(self): + mmap = memmap.MemoryMap('...\x2A') + self._test_checksum(mmap) + + def test_with_MemoryMapBytes(self): + mmap = memmap.MemoryMapBytes(bytes(b'...\x2A')) + self._test_checksum(mmap) + + def test_with_bytes(self): + self._test_checksum(b'...\x2A') + + def test_with_str(self): + self._test_checksum('...\x2A') -- cgit v1.2.3