aboutsummaryrefslogtreecommitdiff
path: root/tests/unit
diff options
context:
space:
mode:
authorMatthew Poletiek <matthew.poletiek@gmail.com>2020-12-08 21:03:16 -0600
committerMatthew Poletiek <matthew.poletiek@gmail.com>2020-12-08 21:03:16 -0600
commite99416456afd4aa8bde42016826f9a345291cbf3 (patch)
treea7a95639cd1cb5dbe2d91a2ca8e8defafac4296d /tests/unit
parent194cf4e5e0b6a2811103a9b739a72b9afe2b886c (diff)
downloadchirp-e99416456afd4aa8bde42016826f9a345291cbf3.tar.gz
chirp-e99416456afd4aa8bde42016826f9a345291cbf3.tar.xz
Initial Commit
Diffstat (limited to 'tests/unit')
-rw-r--r--tests/unit/__init__.py0
-rw-r--r--tests/unit/base.py50
-rw-r--r--tests/unit/test_bitwise.py341
-rw-r--r--tests/unit/test_chirp_common.py415
-rw-r--r--tests/unit/test_directory.py69
-rw-r--r--tests/unit/test_icom_clone.py117
-rw-r--r--tests/unit/test_import_logic.py373
-rw-r--r--tests/unit/test_mappingmodel.py283
-rw-r--r--tests/unit/test_memedit_edits.py82
-rw-r--r--tests/unit/test_platform.py62
-rw-r--r--tests/unit/test_repeaterbook.py28
-rw-r--r--tests/unit/test_settings.py140
-rw-r--r--tests/unit/test_shiftdialog.py112
-rw-r--r--tests/unit/test_utils.py21
-rw-r--r--tests/unit/test_yaesu_clone.py41
15 files changed, 2134 insertions, 0 deletions
diff --git a/tests/unit/__init__.py b/tests/unit/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/unit/__init__.py
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 <dsmith@danplanet.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <dsmith@danplanet.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <dsmith@danplanet.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <dsmith@danplanet.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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 <dsmith@danplanet.com>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+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')