mirror of
https://github.com/kk7ds/chirp.git
synced 2024-09-21 10:37:15 +00:00
985b78aa4f
If duplex is 'off' we don't need to obsess over the offset value, which is unused anyway. Some drivers may not be able to store an offset if duplex=off and/or may use the offset for signaling of that fact.
226 lines
8.7 KiB
Python
226 lines
8.7 KiB
Python
import functools
|
|
import logging
|
|
import os
|
|
import unittest
|
|
|
|
from chirp import chirp_common
|
|
from chirp import bandplan_na
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class DriverTest(unittest.TestCase):
|
|
RADIO_CLASS = None
|
|
SUB_DEVICE = None
|
|
TEST_IMAGE = None
|
|
|
|
def setUp(self):
|
|
super().setUp()
|
|
self.patches = []
|
|
|
|
if ('CHIRP_TEST_BITWISE_STRICT_BYTES' in os.environ and
|
|
not self.RADIO_CLASS.NEEDS_COMPAT_SERIAL):
|
|
self.use_patch(unittest.mock.patch(
|
|
'chirp.bitwise.DataElement._compat_bytes',
|
|
side_effect=self._strict_bytes))
|
|
self.use_patch(unittest.mock.patch(
|
|
'chirp.bitwise.string_straight_encode',
|
|
side_effect=AssertionError(
|
|
'string_straight_encode not allowed in strict mode')))
|
|
|
|
self.parent = self.RADIO_CLASS(self.TEST_IMAGE)
|
|
self.parent_rf = self.parent.get_features()
|
|
# If SUB_DEVICE is set to an index, then the actual radio we are
|
|
# to test is get_sub_devices()[SUB_DEVICE]. Otherwise, it's the
|
|
# actual class we were handed.
|
|
if self.SUB_DEVICE is not None:
|
|
self.radio = self.parent.get_sub_devices()[self.SUB_DEVICE]
|
|
self.rf = self.radio.get_features()
|
|
else:
|
|
self.radio = self.parent
|
|
self.rf = self.parent_rf
|
|
|
|
def use_patch(self, patch):
|
|
self.patches.append(patch)
|
|
patch.start()
|
|
|
|
def tearDown(self):
|
|
for patch in self.patches:
|
|
patch.stop()
|
|
|
|
def _strict_bytes(self, bs, asbytes):
|
|
"""Enforce strict get_raw() behavior returning bytes()"""
|
|
assert asbytes, 'asbytes must be True in strict mode'
|
|
assert isinstance(bs, bytes), 'Type should be bytes here'
|
|
return bs
|
|
|
|
def get_mem(self):
|
|
"""Attempt to build a suitable memory for testing"""
|
|
# Check to see if memory #1 has immutable fields, and if so,
|
|
# use that as our template instead of constructing a memory ourselves
|
|
try:
|
|
m = self.radio.get_memory(1)
|
|
# Pre-filter the name so it will match what we expect back
|
|
if 'name' not in m.immutable:
|
|
m.name = self.radio.filter_name(m.name)
|
|
# Disable duplex in case it's set because this will cause some
|
|
# weirdness if we much with other values, like offset.
|
|
if 'duplex' not in m.immutable:
|
|
m.duplex = ''
|
|
if m.immutable:
|
|
return m
|
|
except Exception:
|
|
pass
|
|
|
|
m = chirp_common.Memory()
|
|
|
|
# Some of the exposed bands may not be transmit-enabled, so
|
|
# iterate them all
|
|
attempt = 0
|
|
for band_lo, band_hi in self.rf.valid_bands:
|
|
m.freq = band_lo
|
|
if self.rf.valid_tuning_steps:
|
|
# If we have valid tuning steps, go one step above the
|
|
# bottom of the band. Select a different tuning_step each
|
|
# time, as some radios have various requirements for which
|
|
# steps work in each band, mode, etc.
|
|
steps = [x for x in self.rf.valid_tuning_steps
|
|
if x > 1]
|
|
step_index = attempt % len(steps)
|
|
m.tuning_step = steps[step_index]
|
|
m.freq += int(m.tuning_step * 1000)
|
|
elif m.freq + 1000000 < band_hi:
|
|
# Otherwise just pick 1MHz above the bottom, which has been
|
|
# our test basis for a long time, unless that extends past
|
|
# the end of the band.
|
|
m.freq += 1000000
|
|
|
|
if m.freq < 30000000 and "AM" in self.rf.valid_modes:
|
|
m.mode = "AM"
|
|
else:
|
|
try:
|
|
m.mode = self.rf.valid_modes[0]
|
|
except IndexError:
|
|
pass
|
|
|
|
for i in range(*self.rf.memory_bounds)[:10]:
|
|
m.number = i
|
|
msgs = self.radio.validate_memory(m)
|
|
warnings, errors = chirp_common.split_validation_msgs(msgs)
|
|
if warnings and not errors:
|
|
# If we got some warnings and no errors, then we know the
|
|
# memory is almost good enough. Set it and pull it back
|
|
# to let the radio squash whatever was "almost correct"
|
|
# and then use that.
|
|
self.radio.set_memory(m)
|
|
m = self.radio.get_memory(m.number)
|
|
try:
|
|
del m.extra
|
|
except AttributeError:
|
|
pass
|
|
return m
|
|
# If we got no warnings, or we have only one band, then
|
|
# no errors means we found our candidate.
|
|
elif not errors:
|
|
return m
|
|
|
|
attempt += 1
|
|
|
|
self.fail("No mutable memory locations found - unable to run this "
|
|
"test because I don't have a memory to test with")
|
|
|
|
def assertEqualMem(self, a, b, ignore=None):
|
|
if a.tmode == "Cross":
|
|
tx_mode, rx_mode = a.cross_mode.split("->")
|
|
if ignore is None:
|
|
ignore = []
|
|
|
|
if a.duplex == b.duplex == 'off':
|
|
# If we're asking for duplex=off, we should not obsess over the
|
|
# driver keeping track of our offset, as it may use the offset
|
|
# field to manage the TX behavior
|
|
ignore.append('offset')
|
|
|
|
a_vals = {}
|
|
b_vals = {}
|
|
|
|
for k, v in list(a.__dict__.items()):
|
|
if ignore and k in ignore:
|
|
continue
|
|
if k == "power":
|
|
continue # FIXME
|
|
elif k == "extra":
|
|
continue
|
|
elif k == "immutable":
|
|
continue
|
|
elif k == "name":
|
|
if not self.rf.has_name:
|
|
continue # Don't complain about name, if not supported
|
|
else:
|
|
# Name mismatch fair if filter_name() is right
|
|
v = self.radio.filter_name(v).rstrip()
|
|
elif k == "tuning_step" and not self.rf.has_tuning_step:
|
|
continue
|
|
elif k == "rtone" and not (
|
|
a.tmode == "Tone" or
|
|
(a.tmode == "TSQL" and not self.rf.has_ctone) or
|
|
(a.tmode == "Cross" and tx_mode == "Tone") or
|
|
(a.tmode == "Cross" and rx_mode == "Tone" and
|
|
not self.rf.has_ctone)
|
|
):
|
|
continue
|
|
elif k == "ctone" and (not self.rf.has_ctone or
|
|
not (a.tmode == "TSQL" or
|
|
(a.tmode == "Cross" and
|
|
rx_mode == "Tone"))):
|
|
continue
|
|
elif k == "dtcs" and not (
|
|
(a.tmode == "DTCS" and not self.rf.has_rx_dtcs) or
|
|
(a.tmode == "Cross" and tx_mode == "DTCS") or
|
|
(a.tmode == "Cross" and rx_mode == "DTCS" and
|
|
not self.rf.has_rx_dtcs)):
|
|
continue
|
|
elif k == "rx_dtcs" and (not self.rf.has_rx_dtcs or
|
|
not (a.tmode == "Cross" and
|
|
rx_mode == "DTCS")):
|
|
continue
|
|
elif k == "offset" and not a.duplex:
|
|
continue
|
|
elif k == "cross_mode" and a.tmode != "Cross":
|
|
continue
|
|
|
|
if (a.freq in bandplan_na.ALL_GMRS_FREQS and
|
|
k in a.immutable or k in b.immutable):
|
|
# If the radio returned a field in immutable, it probably
|
|
# means that it's a mandatory setting (i.e. power or duplex
|
|
# in GMRS)
|
|
continue
|
|
|
|
a_vals[k] = v
|
|
b_vals[k] = b.__dict__[k]
|
|
|
|
self.assertEqual(a_vals, b_vals,
|
|
'Memories have unexpected differences')
|
|
|
|
# Consider mem.extra as matching if the structure remains the same.
|
|
# Since we don't know anything about model-specific things here we
|
|
# can't really assert any more than that, but we can ensure that the
|
|
# structure doesn't change due to the contents of the rest of the
|
|
# memory.
|
|
if a.extra and b.extra:
|
|
self.assertEqual([x.get_name() for x in a.extra],
|
|
[x.get_name() for x in b.extra],
|
|
'Memories have different mem.extra keys')
|
|
|
|
|
|
def requires_feature(flag):
|
|
def inner(fn):
|
|
@functools.wraps(fn)
|
|
def wraps(self, *a, **k):
|
|
if getattr(self.rf, flag):
|
|
fn(self, *a, **k)
|
|
else:
|
|
self.skipTest('Feature %s not supported' % flag)
|
|
return wraps
|
|
return inner
|