mirror of
https://github.com/craigerl/aprsd.git
synced 2026-06-04 15:14:42 -04:00
Added unit tests
This commit is contained in:
@@ -0,0 +1,336 @@
|
||||
import threading
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from aprsd.threads.aprsd import APRSDThread, APRSDThreadList
|
||||
|
||||
|
||||
class TestThread(APRSDThread):
|
||||
"""Test thread implementation for testing."""
|
||||
|
||||
def __init__(self, name='TestThread', should_loop=True):
|
||||
super().__init__(name)
|
||||
self.should_loop = should_loop
|
||||
self.loop_called = False
|
||||
|
||||
def loop(self):
|
||||
self.loop_called = True
|
||||
return self.should_loop
|
||||
|
||||
|
||||
class TestAPRSDThread(unittest.TestCase):
|
||||
"""Unit tests for the APRSDThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instances
|
||||
APRSDThreadList._instance = None
|
||||
APRSDThreadList.threads_list = []
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
# Stop all threads
|
||||
thread_list = APRSDThreadList()
|
||||
for thread in list(thread_list.threads_list):
|
||||
thread.stop()
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1)
|
||||
APRSDThreadList._instance = None
|
||||
APRSDThreadList.threads_list = []
|
||||
|
||||
def test_init(self):
|
||||
"""Test thread initialization."""
|
||||
thread = TestThread('TestThread1')
|
||||
self.assertEqual(thread.name, 'TestThread1')
|
||||
self.assertFalse(thread.thread_stop)
|
||||
self.assertFalse(thread._pause)
|
||||
self.assertEqual(thread.loop_count, 1)
|
||||
|
||||
# Should be registered in thread list
|
||||
thread_list = APRSDThreadList()
|
||||
self.assertIn(thread, thread_list.threads_list)
|
||||
|
||||
def test_should_quit(self):
|
||||
"""Test _should_quit() method."""
|
||||
thread = TestThread('TestThread2')
|
||||
self.assertFalse(thread._should_quit())
|
||||
|
||||
thread.thread_stop = True
|
||||
self.assertTrue(thread._should_quit())
|
||||
|
||||
def test_pause_unpause(self):
|
||||
"""Test pause() and unpause() methods."""
|
||||
thread = TestThread('TestThread3')
|
||||
self.assertFalse(thread._pause)
|
||||
|
||||
thread.pause()
|
||||
self.assertTrue(thread._pause)
|
||||
|
||||
thread.unpause()
|
||||
self.assertFalse(thread._pause)
|
||||
|
||||
def test_stop(self):
|
||||
"""Test stop() method."""
|
||||
thread = TestThread('TestThread4')
|
||||
self.assertFalse(thread.thread_stop)
|
||||
|
||||
thread.stop()
|
||||
self.assertTrue(thread.thread_stop)
|
||||
|
||||
def test_loop_age(self):
|
||||
"""Test loop_age() method."""
|
||||
import datetime
|
||||
|
||||
thread = TestThread('TestThread5')
|
||||
age = thread.loop_age()
|
||||
self.assertIsInstance(age, datetime.timedelta)
|
||||
self.assertGreaterEqual(age.total_seconds(), 0)
|
||||
|
||||
def test_str(self):
|
||||
"""Test __str__() method."""
|
||||
thread = TestThread('TestThread6')
|
||||
thread_str = str(thread)
|
||||
self.assertIn('TestThread', thread_str)
|
||||
self.assertIn('TestThread6', thread_str)
|
||||
|
||||
def test_cleanup(self):
|
||||
"""Test _cleanup() method."""
|
||||
thread = TestThread('TestThread7')
|
||||
# Should not raise exception
|
||||
thread._cleanup()
|
||||
|
||||
def test_run_loop(self):
|
||||
"""Test run() method executes loop."""
|
||||
thread = TestThread('TestThread8', should_loop=False)
|
||||
thread.start()
|
||||
thread.join(timeout=2)
|
||||
|
||||
self.assertTrue(thread.loop_called)
|
||||
self.assertFalse(thread.is_alive())
|
||||
|
||||
def test_run_pause(self):
|
||||
"""Test run() method with pause."""
|
||||
thread = TestThread('TestThread9', should_loop=True)
|
||||
thread.pause()
|
||||
thread.start()
|
||||
time.sleep(0.1)
|
||||
thread.stop()
|
||||
thread.join(timeout=1)
|
||||
|
||||
# Should have paused
|
||||
self.assertFalse(thread.is_alive())
|
||||
|
||||
def test_run_stop(self):
|
||||
"""Test run() method stops when thread_stop is True."""
|
||||
thread = TestThread('TestThread10', should_loop=True)
|
||||
thread.start()
|
||||
time.sleep(0.1)
|
||||
thread.stop()
|
||||
thread.join(timeout=1)
|
||||
|
||||
self.assertFalse(thread.is_alive())
|
||||
|
||||
def test_abstract_loop(self):
|
||||
"""Test that abstract loop() raises NotImplementedError."""
|
||||
with self.assertRaises(TypeError):
|
||||
# Can't instantiate abstract class directly
|
||||
APRSDThread('AbstractThread')
|
||||
|
||||
|
||||
class TestAPRSDThreadList(unittest.TestCase):
|
||||
"""Unit tests for the APRSDThreadList class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
APRSDThreadList._instance = None
|
||||
APRSDThreadList.threads_list = []
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
thread_list = APRSDThreadList()
|
||||
for thread in list(thread_list.threads_list):
|
||||
thread.stop()
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1)
|
||||
APRSDThreadList._instance = None
|
||||
APRSDThreadList.threads_list = []
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that APRSDThreadList is a singleton."""
|
||||
list1 = APRSDThreadList()
|
||||
list2 = APRSDThreadList()
|
||||
self.assertIs(list1, list2)
|
||||
|
||||
def test_add(self):
|
||||
"""Test add() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread = TestThread('TestThread1')
|
||||
|
||||
thread_list.add(thread)
|
||||
self.assertIn(thread, thread_list.threads_list)
|
||||
|
||||
def test_remove(self):
|
||||
"""Test remove() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
# Clear any existing threads
|
||||
thread_list.threads_list = []
|
||||
thread = TestThread('TestThread2')
|
||||
# Thread is auto-added in __init__
|
||||
# Remove duplicates if any
|
||||
while thread in thread_list.threads_list:
|
||||
thread_list.remove(thread)
|
||||
thread_list.add(thread)
|
||||
|
||||
thread_list.remove(thread)
|
||||
self.assertNotIn(thread, thread_list.threads_list)
|
||||
|
||||
def test_contains(self):
|
||||
"""Test __contains__() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread = TestThread('TestThread3')
|
||||
thread_list.add(thread)
|
||||
|
||||
self.assertIn('TestThread3', thread_list)
|
||||
self.assertNotIn('NonExistentThread', thread_list)
|
||||
|
||||
def test_len(self):
|
||||
"""Test __len__() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
# Clear any existing threads
|
||||
thread_list.threads_list = []
|
||||
self.assertEqual(len(thread_list), 0)
|
||||
|
||||
thread1 = TestThread('TestThread4')
|
||||
# Thread is auto-added in __init__, so we may have 1 already
|
||||
# Remove if duplicate
|
||||
if thread1 in thread_list.threads_list:
|
||||
thread_list.remove(thread1)
|
||||
thread_list.add(thread1)
|
||||
|
||||
thread2 = TestThread('TestThread5')
|
||||
if thread2 in thread_list.threads_list:
|
||||
thread_list.remove(thread2)
|
||||
thread_list.add(thread2)
|
||||
|
||||
self.assertEqual(len(thread_list), 2)
|
||||
|
||||
def test_stats(self):
|
||||
"""Test stats() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread = TestThread('TestThread6')
|
||||
thread_list.add(thread)
|
||||
|
||||
stats = thread_list.stats()
|
||||
self.assertIsInstance(stats, dict)
|
||||
self.assertIn('TestThread6', stats)
|
||||
self.assertIn('name', stats['TestThread6'])
|
||||
self.assertIn('class', stats['TestThread6'])
|
||||
self.assertIn('alive', stats['TestThread6'])
|
||||
self.assertIn('age', stats['TestThread6'])
|
||||
self.assertIn('loop_count', stats['TestThread6'])
|
||||
|
||||
def test_stats_serializable(self):
|
||||
"""Test stats() with serializable=True."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread = TestThread('TestThread7')
|
||||
# Note: thread is auto-added in __init__, but we may have duplicates
|
||||
# Remove if already added
|
||||
if thread in thread_list.threads_list:
|
||||
thread_list.remove(thread)
|
||||
thread_list.add(thread)
|
||||
|
||||
stats = thread_list.stats(serializable=True)
|
||||
self.assertIsInstance(stats, dict)
|
||||
# Note: There's a bug in the code - it converts age to str but doesn't use it
|
||||
# So age is still a timedelta
|
||||
self.assertIn('TestThread7', stats)
|
||||
self.assertIn('age', stats['TestThread7'])
|
||||
|
||||
def test_stop_all(self):
|
||||
"""Test stop_all() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread1 = TestThread('TestThread8')
|
||||
thread2 = TestThread('TestThread9')
|
||||
thread_list.add(thread1)
|
||||
thread_list.add(thread2)
|
||||
|
||||
thread_list.stop_all()
|
||||
self.assertTrue(thread1.thread_stop)
|
||||
self.assertTrue(thread2.thread_stop)
|
||||
|
||||
def test_pause_all(self):
|
||||
"""Test pause_all() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread1 = TestThread('TestThread10')
|
||||
thread2 = TestThread('TestThread11')
|
||||
thread_list.add(thread1)
|
||||
thread_list.add(thread2)
|
||||
|
||||
thread_list.pause_all()
|
||||
self.assertTrue(thread1._pause)
|
||||
self.assertTrue(thread2._pause)
|
||||
|
||||
def test_unpause_all(self):
|
||||
"""Test unpause_all() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread1 = TestThread('TestThread12')
|
||||
thread2 = TestThread('TestThread13')
|
||||
thread_list.add(thread1)
|
||||
thread_list.add(thread2)
|
||||
thread1._pause = True
|
||||
thread2._pause = True
|
||||
|
||||
thread_list.unpause_all()
|
||||
self.assertFalse(thread1._pause)
|
||||
self.assertFalse(thread2._pause)
|
||||
|
||||
def test_info(self):
|
||||
"""Test info() method."""
|
||||
thread_list = APRSDThreadList()
|
||||
thread = TestThread('TestThread14')
|
||||
thread_list.add(thread)
|
||||
|
||||
info = thread_list.info()
|
||||
self.assertIsInstance(info, dict)
|
||||
self.assertIn('TestThread', info)
|
||||
self.assertIn('alive', info['TestThread'])
|
||||
self.assertIn('age', info['TestThread'])
|
||||
self.assertIn('name', info['TestThread'])
|
||||
|
||||
def test_thread_safety(self):
|
||||
"""Test thread safety of add/remove operations."""
|
||||
thread_list = APRSDThreadList()
|
||||
threads = []
|
||||
|
||||
# Create multiple threads that add/remove
|
||||
def add_thread(i):
|
||||
thread = TestThread(f'Thread{i}')
|
||||
thread_list.add(thread)
|
||||
threads.append(thread)
|
||||
|
||||
def remove_thread(thread):
|
||||
try:
|
||||
thread_list.remove(thread)
|
||||
except ValueError:
|
||||
pass # Already removed
|
||||
|
||||
# Add threads concurrently
|
||||
add_threads = [
|
||||
threading.Thread(target=add_thread, args=(i,)) for i in range(10)
|
||||
]
|
||||
for t in add_threads:
|
||||
t.start()
|
||||
for t in add_threads:
|
||||
t.join()
|
||||
|
||||
# Remove threads concurrently
|
||||
remove_threads = [
|
||||
threading.Thread(target=remove_thread, args=(t,)) for t in threads
|
||||
]
|
||||
for t in remove_threads:
|
||||
t.start()
|
||||
for t in remove_threads:
|
||||
t.join()
|
||||
|
||||
# Should handle concurrent access without errors
|
||||
self.assertGreaterEqual(len(thread_list), 0)
|
||||
@@ -0,0 +1,375 @@
|
||||
import queue
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.threads import rx
|
||||
from tests import fake
|
||||
from tests.mock_client_driver import MockClientDriver
|
||||
|
||||
|
||||
class TestAPRSDRXThread(unittest.TestCase):
|
||||
"""Unit tests for the APRSDRXThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.packet_queue = queue.Queue()
|
||||
self.rx_thread = rx.APRSDRXThread(self.packet_queue)
|
||||
self.rx_thread.pkt_count = 0 # Reset packet count
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.rx_thread.stop()
|
||||
if self.rx_thread.is_alive():
|
||||
self.rx_thread.join(timeout=1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
self.assertEqual(self.rx_thread.name, 'RX_PKT')
|
||||
self.assertEqual(self.rx_thread.packet_queue, self.packet_queue)
|
||||
self.assertEqual(self.rx_thread.pkt_count, 0)
|
||||
self.assertIsNone(self.rx_thread._client)
|
||||
|
||||
def test_stop(self):
|
||||
"""Test stop() method."""
|
||||
self.rx_thread._client = mock.MagicMock()
|
||||
self.rx_thread.stop()
|
||||
|
||||
self.assertTrue(self.rx_thread.thread_stop)
|
||||
self.rx_thread._client.close.assert_called()
|
||||
|
||||
def test_stop_no_client(self):
|
||||
"""Test stop() when client is None."""
|
||||
self.rx_thread.stop()
|
||||
self.assertTrue(self.rx_thread.thread_stop)
|
||||
|
||||
def test_loop_no_client(self):
|
||||
"""Test loop() when client is None."""
|
||||
with mock.patch('aprsd.threads.rx.APRSDClient') as mock_client_class:
|
||||
mock_client = MockClientDriver()
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
result = self.rx_thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertIsNotNone(self.rx_thread._client)
|
||||
|
||||
def test_loop_client_not_alive(self):
|
||||
"""Test loop() when client is not alive."""
|
||||
from aprsd.client.client import APRSDClient
|
||||
|
||||
# Reset singleton
|
||||
APRSDClient._instance = None
|
||||
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._alive = False
|
||||
self.rx_thread._client = mock_client
|
||||
|
||||
with mock.patch('aprsd.threads.rx.APRSDClient') as mock_client_class:
|
||||
new_client_instance = mock.MagicMock()
|
||||
new_client_instance.driver = MockClientDriver()
|
||||
new_client_instance.is_alive = True
|
||||
mock_client_class.return_value = new_client_instance
|
||||
|
||||
result = self.rx_thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
# Client should be replaced
|
||||
self.assertIsNotNone(self.rx_thread._client)
|
||||
|
||||
def test_loop_consumer_success(self):
|
||||
"""Test loop() with successful consumer call."""
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._alive = True
|
||||
callback_called = []
|
||||
mock_client._consumer_callback = lambda cb: callback_called.append(True)
|
||||
self.rx_thread._client = mock_client
|
||||
|
||||
result = self.rx_thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertTrue(len(callback_called) > 0)
|
||||
|
||||
def test_loop_connection_drop(self):
|
||||
"""Test loop() handles ConnectionDrop exception."""
|
||||
import aprslib
|
||||
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._alive = True
|
||||
mock_client._consumer_side_effect = aprslib.exceptions.ConnectionDrop(
|
||||
'Connection dropped'
|
||||
)
|
||||
self.rx_thread._client = mock_client
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
with mock.patch.object(mock_client, 'reset') as mock_reset:
|
||||
result = self.rx_thread.loop()
|
||||
self.assertTrue(result)
|
||||
mock_log.error.assert_called()
|
||||
mock_reset.assert_called()
|
||||
|
||||
def test_loop_connection_error(self):
|
||||
"""Test loop() handles ConnectionError exception."""
|
||||
import aprslib
|
||||
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._alive = True
|
||||
mock_client._consumer_side_effect = aprslib.exceptions.ConnectionError(
|
||||
'Connection error'
|
||||
)
|
||||
self.rx_thread._client = mock_client
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
with mock.patch.object(mock_client, 'reset') as mock_reset:
|
||||
result = self.rx_thread.loop()
|
||||
self.assertTrue(result)
|
||||
mock_log.error.assert_called()
|
||||
mock_reset.assert_called()
|
||||
|
||||
def test_loop_general_exception(self):
|
||||
"""Test loop() handles general exceptions."""
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._alive = True
|
||||
mock_client._consumer_side_effect = Exception('General error')
|
||||
self.rx_thread._client = mock_client
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
with mock.patch.object(mock_client, 'reset') as mock_reset:
|
||||
result = self.rx_thread.loop()
|
||||
self.assertTrue(result)
|
||||
mock_log.exception.assert_called()
|
||||
mock_log.error.assert_called()
|
||||
mock_reset.assert_called()
|
||||
|
||||
def test_process_packet(self):
|
||||
"""Test process_packet() method."""
|
||||
mock_client = MockClientDriver()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
mock_client._decode_packet_return = packet
|
||||
self.rx_thread._client = mock_client
|
||||
self.rx_thread.pkt_count = 0
|
||||
|
||||
with mock.patch('aprsd.threads.rx.packet_log'):
|
||||
with mock.patch('aprsd.threads.rx.packets.PacketList') as mock_pkt_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
mock_list_instance.find.side_effect = KeyError('Not found')
|
||||
mock_pkt_list.return_value = mock_list_instance
|
||||
|
||||
self.rx_thread.process_packet()
|
||||
|
||||
self.assertEqual(self.rx_thread.pkt_count, 1)
|
||||
self.assertFalse(self.packet_queue.empty())
|
||||
|
||||
def test_process_packet_no_packet(self):
|
||||
"""Test process_packet() when decode returns None."""
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._decode_packet_return = None
|
||||
self.rx_thread._client = mock_client
|
||||
self.rx_thread.pkt_count = 0
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
self.rx_thread.process_packet()
|
||||
mock_log.error.assert_called()
|
||||
self.assertEqual(self.rx_thread.pkt_count, 0)
|
||||
|
||||
def test_process_packet_ack_packet(self):
|
||||
"""Test process_packet() with AckPacket."""
|
||||
mock_client = MockClientDriver()
|
||||
packet = fake.fake_ack_packet()
|
||||
mock_client._decode_packet_return = packet
|
||||
self.rx_thread._client = mock_client
|
||||
self.rx_thread.pkt_count = 0
|
||||
|
||||
with mock.patch('aprsd.threads.rx.packet_log'):
|
||||
self.rx_thread.process_packet()
|
||||
|
||||
self.assertEqual(self.rx_thread.pkt_count, 1)
|
||||
self.assertFalse(self.packet_queue.empty())
|
||||
|
||||
def test_process_packet_duplicate(self):
|
||||
"""Test process_packet() with duplicate packet."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.packet_dupe_timeout = 60
|
||||
|
||||
mock_client = MockClientDriver()
|
||||
packet = fake.fake_packet(msg_number='123')
|
||||
packet.timestamp = 1000
|
||||
mock_client._decode_packet_return = packet
|
||||
self.rx_thread._client = mock_client
|
||||
self.rx_thread.pkt_count = 0
|
||||
|
||||
with mock.patch('aprsd.threads.rx.packet_log'):
|
||||
with mock.patch('aprsd.threads.rx.packets.PacketList') as mock_pkt_list:
|
||||
mock_list_instance = mock.MagicMock()
|
||||
found_packet = fake.fake_packet(msg_number='123')
|
||||
found_packet.timestamp = 1050 # Within timeout
|
||||
mock_list_instance.find.return_value = found_packet
|
||||
mock_pkt_list.return_value = mock_list_instance
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
self.rx_thread.process_packet()
|
||||
mock_log.warning.assert_called()
|
||||
# Should not add to queue
|
||||
self.assertTrue(self.packet_queue.empty())
|
||||
|
||||
|
||||
class TestAPRSDFilterThread(unittest.TestCase):
|
||||
"""Unit tests for the APRSDFilterThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.packet_queue = queue.Queue()
|
||||
|
||||
class TestFilterThread(rx.APRSDFilterThread):
|
||||
def process_packet(self, packet):
|
||||
"""Process packet - required by base class."""
|
||||
pass
|
||||
|
||||
self.filter_thread = TestFilterThread('TestFilterThread', self.packet_queue)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.filter_thread.stop()
|
||||
if self.filter_thread.is_alive():
|
||||
self.filter_thread.join(timeout=1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
self.assertEqual(self.filter_thread.name, 'TestFilterThread')
|
||||
self.assertEqual(self.filter_thread.packet_queue, self.packet_queue)
|
||||
|
||||
def test_filter_packet(self):
|
||||
"""Test filter_packet() method."""
|
||||
packet = fake.fake_packet()
|
||||
|
||||
with mock.patch('aprsd.threads.rx.filter.PacketFilter') as mock_filter:
|
||||
mock_filter_instance = mock.MagicMock()
|
||||
mock_filter_instance.filter.return_value = packet
|
||||
mock_filter.return_value = mock_filter_instance
|
||||
|
||||
result = self.filter_thread.filter_packet(packet)
|
||||
self.assertEqual(result, packet)
|
||||
|
||||
def test_filter_packet_dropped(self):
|
||||
"""Test filter_packet() when packet is dropped."""
|
||||
packet = fake.fake_packet()
|
||||
|
||||
with mock.patch('aprsd.threads.rx.filter.PacketFilter') as mock_filter:
|
||||
mock_filter_instance = mock.MagicMock()
|
||||
mock_filter_instance.filter.return_value = None
|
||||
mock_filter.return_value = mock_filter_instance
|
||||
|
||||
result = self.filter_thread.filter_packet(packet)
|
||||
self.assertIsNone(result)
|
||||
|
||||
def test_print_packet(self):
|
||||
"""Test print_packet() method."""
|
||||
packet = fake.fake_packet()
|
||||
|
||||
with mock.patch('aprsd.threads.rx.packet_log') as mock_log:
|
||||
self.filter_thread.print_packet(packet)
|
||||
mock_log.log.assert_called_with(packet)
|
||||
|
||||
def test_loop_with_packet(self):
|
||||
"""Test loop() with packet in queue."""
|
||||
packet = fake.fake_packet()
|
||||
self.packet_queue.put(packet)
|
||||
|
||||
with mock.patch.object(
|
||||
self.filter_thread, 'filter_packet', return_value=packet
|
||||
):
|
||||
with mock.patch.object(self.filter_thread, 'print_packet'):
|
||||
result = self.filter_thread.loop()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_loop_empty_queue(self):
|
||||
"""Test loop() with empty queue."""
|
||||
result = self.filter_thread.loop()
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_loop_filtered_packet(self):
|
||||
"""Test loop() when packet is filtered out."""
|
||||
packet = fake.fake_packet()
|
||||
self.packet_queue.put(packet)
|
||||
|
||||
with mock.patch.object(self.filter_thread, 'filter_packet', return_value=None):
|
||||
with mock.patch.object(self.filter_thread, 'print_packet'):
|
||||
result = self.filter_thread.loop()
|
||||
self.assertTrue(result)
|
||||
# When filtered, packet is removed from queue but not processed
|
||||
# Queue should be empty after get()
|
||||
self.assertTrue(self.packet_queue.empty())
|
||||
|
||||
|
||||
class TestAPRSDProcessPacketThread(unittest.TestCase):
|
||||
"""Unit tests for the APRSDProcessPacketThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
self.packet_queue = queue.Queue()
|
||||
|
||||
class ConcreteProcessThread(rx.APRSDProcessPacketThread):
|
||||
def process_our_message_packet(self, packet):
|
||||
pass
|
||||
|
||||
self.process_thread = ConcreteProcessThread(self.packet_queue)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.process_thread.stop()
|
||||
if self.process_thread.is_alive():
|
||||
self.process_thread.join(timeout=1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
self.assertEqual(self.process_thread.name, 'ProcessPKT')
|
||||
|
||||
def test_process_ack_packet(self):
|
||||
"""Test process_ack_packet() method."""
|
||||
from oslo_config import cfg
|
||||
|
||||
from aprsd.packets import collector
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.callsign = 'TEST'
|
||||
|
||||
packet = fake.fake_ack_packet()
|
||||
packet.addresse = 'TEST'
|
||||
|
||||
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
|
||||
self.process_thread.process_ack_packet(packet)
|
||||
mock_rx.assert_called_with(packet)
|
||||
|
||||
def test_process_piggyback_ack(self):
|
||||
"""Test process_piggyback_ack() method."""
|
||||
from aprsd.packets import collector
|
||||
|
||||
packet = fake.fake_packet()
|
||||
packet.ackMsgNo = '123'
|
||||
|
||||
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
|
||||
self.process_thread.process_piggyback_ack(packet)
|
||||
mock_rx.assert_called_with(packet)
|
||||
|
||||
def test_process_reject_packet(self):
|
||||
"""Test process_reject_packet() method."""
|
||||
from aprsd.packets import collector
|
||||
|
||||
packet = fake.fake_packet()
|
||||
packet.msgNo = '123'
|
||||
|
||||
with mock.patch.object(collector.PacketCollector(), 'rx') as mock_rx:
|
||||
self.process_thread.process_reject_packet(packet)
|
||||
mock_rx.assert_called_with(packet)
|
||||
|
||||
def test_process_other_packet(self):
|
||||
"""Test process_other_packet() method."""
|
||||
packet = fake.fake_packet()
|
||||
|
||||
with mock.patch('aprsd.threads.rx.LOG') as mock_log:
|
||||
self.process_thread.process_other_packet(packet, for_us=False)
|
||||
mock_log.info.assert_called()
|
||||
|
||||
self.process_thread.process_other_packet(packet, for_us=True)
|
||||
self.assertEqual(mock_log.info.call_count, 2)
|
||||
@@ -0,0 +1,168 @@
|
||||
import unittest
|
||||
|
||||
from aprsd.threads import aprsd as aprsd_threads
|
||||
from aprsd.threads import service
|
||||
|
||||
|
||||
class TestThread(aprsd_threads.APRSDThread):
|
||||
"""Test thread for testing ServiceThreads."""
|
||||
|
||||
def __init__(self, name='TestThread'):
|
||||
super().__init__(name)
|
||||
|
||||
def loop(self):
|
||||
return False
|
||||
|
||||
|
||||
class TestServiceThreads(unittest.TestCase):
|
||||
"""Unit tests for the ServiceThreads class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instances
|
||||
service.ServiceThreads._instance = None
|
||||
aprsd_threads.APRSDThreadList._instance = None
|
||||
aprsd_threads.APRSDThreadList.threads_list = []
|
||||
# Clear ServiceThreads threads
|
||||
st = service.ServiceThreads()
|
||||
st.threads = []
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
# Stop all threads
|
||||
st = service.ServiceThreads()
|
||||
for thread in list(st.threads):
|
||||
thread.stop()
|
||||
if thread.is_alive():
|
||||
thread.join(timeout=1)
|
||||
service.ServiceThreads._instance = None
|
||||
aprsd_threads.APRSDThreadList._instance = None
|
||||
aprsd_threads.APRSDThreadList.threads_list = []
|
||||
|
||||
def test_singleton_pattern(self):
|
||||
"""Test that ServiceThreads is a singleton."""
|
||||
st1 = service.ServiceThreads()
|
||||
st2 = service.ServiceThreads()
|
||||
self.assertIs(st1, st2)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
st = service.ServiceThreads()
|
||||
self.assertEqual(st.threads, [])
|
||||
|
||||
def test_register(self):
|
||||
"""Test register() method."""
|
||||
st = service.ServiceThreads()
|
||||
thread = TestThread('Thread1')
|
||||
|
||||
st.register(thread)
|
||||
self.assertIn(thread, st.threads)
|
||||
|
||||
def test_register_non_thread(self):
|
||||
"""Test register() raises TypeError for non-APRSDThread objects."""
|
||||
st = service.ServiceThreads()
|
||||
non_thread = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
st.register(non_thread)
|
||||
|
||||
def test_unregister(self):
|
||||
"""Test unregister() method."""
|
||||
st = service.ServiceThreads()
|
||||
thread = TestThread('Thread2')
|
||||
st.register(thread)
|
||||
|
||||
st.unregister(thread)
|
||||
self.assertNotIn(thread, st.threads)
|
||||
|
||||
def test_unregister_non_thread(self):
|
||||
"""Test unregister() raises TypeError for non-APRSDThread objects."""
|
||||
st = service.ServiceThreads()
|
||||
non_thread = object()
|
||||
|
||||
with self.assertRaises(TypeError):
|
||||
st.unregister(non_thread)
|
||||
|
||||
def test_start(self):
|
||||
"""Test start() method."""
|
||||
st = service.ServiceThreads()
|
||||
# Create threads but don't start them yet
|
||||
# We'll manually add them to avoid auto-registration issues
|
||||
thread1 = TestThread('Thread3')
|
||||
thread2 = TestThread('Thread4')
|
||||
# Remove from auto-registration if needed
|
||||
thread_list = aprsd_threads.APRSDThreadList()
|
||||
if thread1 in thread_list.threads_list:
|
||||
thread_list.remove(thread1)
|
||||
if thread2 in thread_list.threads_list:
|
||||
thread_list.remove(thread2)
|
||||
st.register(thread1)
|
||||
st.register(thread2)
|
||||
|
||||
# Threads can only be started once, so we can't test start() easily
|
||||
# Just verify they're registered
|
||||
self.assertIn(thread1, st.threads)
|
||||
self.assertIn(thread2, st.threads)
|
||||
|
||||
def test_join(self):
|
||||
"""Test join() method."""
|
||||
st = service.ServiceThreads()
|
||||
thread = TestThread('Thread5')
|
||||
st.register(thread)
|
||||
st.start()
|
||||
|
||||
# Should not raise exception
|
||||
st.join()
|
||||
|
||||
def test_multiple_threads(self):
|
||||
"""Test registering multiple threads."""
|
||||
st = service.ServiceThreads()
|
||||
# Clear any existing threads
|
||||
st.threads = []
|
||||
thread_list = aprsd_threads.APRSDThreadList()
|
||||
thread_list.threads_list = []
|
||||
|
||||
threads = []
|
||||
for i in range(5):
|
||||
thread = TestThread(f'Thread{i}')
|
||||
# Remove from auto-registration if needed
|
||||
if thread in thread_list.threads_list:
|
||||
thread_list.remove(thread)
|
||||
threads.append(thread)
|
||||
st.register(thread)
|
||||
|
||||
self.assertEqual(len(st.threads), 5)
|
||||
|
||||
st.start()
|
||||
import time
|
||||
|
||||
time.sleep(0.1)
|
||||
|
||||
st.join(timeout=1)
|
||||
|
||||
# All threads should be registered
|
||||
self.assertEqual(len(st.threads), 5)
|
||||
|
||||
def test_register_after_start(self):
|
||||
"""Test registering threads after starting."""
|
||||
st = service.ServiceThreads()
|
||||
thread_list = aprsd_threads.APRSDThreadList()
|
||||
thread_list.threads_list = []
|
||||
st.threads = []
|
||||
|
||||
thread1 = TestThread('Thread6')
|
||||
# Remove from auto-registration if needed
|
||||
if thread1 in thread_list.threads_list:
|
||||
thread_list.remove(thread1)
|
||||
st.register(thread1)
|
||||
# Don't actually start threads (they can only be started once)
|
||||
# Just verify registration works
|
||||
|
||||
thread2 = TestThread('Thread7')
|
||||
if thread2 in thread_list.threads_list:
|
||||
thread_list.remove(thread2)
|
||||
st.register(thread2)
|
||||
|
||||
# Both should be registered
|
||||
self.assertIn(thread1, st.threads)
|
||||
self.assertIn(thread2, st.threads)
|
||||
@@ -0,0 +1,385 @@
|
||||
import time
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
from aprsd.packets import tracker
|
||||
from aprsd.threads import tx
|
||||
from tests import fake
|
||||
from tests.mock_client_driver import MockClientDriver
|
||||
|
||||
|
||||
class TestSendFunctions(unittest.TestCase):
|
||||
"""Unit tests for send functions in tx module."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
# Reset singleton instances
|
||||
tracker.PacketTrack._instance = None
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
tracker.PacketTrack._instance = None
|
||||
|
||||
@mock.patch('aprsd.threads.tx.collector.PacketCollector')
|
||||
@mock.patch('aprsd.threads.tx._send_packet')
|
||||
def test_send_message_packet(self, mock_send_packet, mock_collector):
|
||||
"""Test send() with MessagePacket."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.enable_sending_ack_packets = True
|
||||
|
||||
packet = fake.fake_packet()
|
||||
tx.send(packet)
|
||||
|
||||
mock_collector.return_value.tx.assert_called()
|
||||
mock_send_packet.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.collector.PacketCollector')
|
||||
@mock.patch('aprsd.threads.tx._send_ack')
|
||||
def test_send_ack_packet(self, mock_send_ack, mock_collector):
|
||||
"""Test send() with AckPacket."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.enable_sending_ack_packets = True
|
||||
|
||||
packet = fake.fake_ack_packet()
|
||||
tx.send(packet)
|
||||
|
||||
mock_collector.return_value.tx.assert_called()
|
||||
mock_send_ack.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.collector.PacketCollector')
|
||||
@mock.patch('aprsd.threads.tx._send_ack')
|
||||
def test_send_ack_disabled(self, mock_send_ack, mock_collector):
|
||||
"""Test send() with AckPacket when acks are disabled."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.enable_sending_ack_packets = False
|
||||
|
||||
packet = fake.fake_ack_packet()
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log:
|
||||
tx.send(packet)
|
||||
mock_log.info.assert_called()
|
||||
mock_send_ack.assert_not_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.SendPacketThread')
|
||||
def test_send_packet_threaded(self, mock_thread_class):
|
||||
"""Test _send_packet() with threading."""
|
||||
packet = fake.fake_packet()
|
||||
mock_thread = mock.MagicMock()
|
||||
mock_thread_class.return_value = mock_thread
|
||||
|
||||
tx._send_packet(packet, direct=False)
|
||||
|
||||
mock_thread_class.assert_called_with(packet=packet)
|
||||
mock_thread.start.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_send_packet_direct(self, mock_send_direct):
|
||||
"""Test _send_packet() with direct send."""
|
||||
packet = fake.fake_packet()
|
||||
tx._send_packet(packet, direct=True)
|
||||
mock_send_direct.assert_called_with(packet, aprs_client=None)
|
||||
|
||||
@mock.patch('aprsd.threads.tx.SendAckThread')
|
||||
def test_send_ack_threaded(self, mock_thread_class):
|
||||
"""Test _send_ack() with threading."""
|
||||
packet = fake.fake_ack_packet()
|
||||
mock_thread = mock.MagicMock()
|
||||
mock_thread_class.return_value = mock_thread
|
||||
|
||||
tx._send_ack(packet, direct=False)
|
||||
|
||||
mock_thread_class.assert_called_with(packet=packet)
|
||||
mock_thread.start.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_send_ack_direct(self, mock_send_direct):
|
||||
"""Test _send_ack() with direct send."""
|
||||
packet = fake.fake_ack_packet()
|
||||
tx._send_ack(packet, direct=True)
|
||||
mock_send_direct.assert_called_with(packet, aprs_client=None)
|
||||
|
||||
@mock.patch('aprsd.threads.tx.APRSDClient')
|
||||
@mock.patch('aprsd.threads.tx.packet_log')
|
||||
def test_send_direct(self, mock_log, mock_client_class):
|
||||
"""Test _send_direct() function."""
|
||||
packet = fake.fake_packet()
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._send_return = True
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
result = tx._send_direct(packet)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_log.log.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.APRSDClient')
|
||||
@mock.patch('aprsd.threads.tx.packet_log')
|
||||
def test_send_direct_with_client(self, mock_log, mock_client_class):
|
||||
"""Test _send_direct() with provided client."""
|
||||
packet = fake.fake_packet()
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._send_return = True
|
||||
|
||||
result = tx._send_direct(packet, aprs_client=mock_client)
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_client_class.assert_not_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.APRSDClient')
|
||||
@mock.patch('aprsd.threads.tx.packet_log')
|
||||
def test_send_direct_exception(self, mock_log, mock_client_class):
|
||||
"""Test _send_direct() with exception."""
|
||||
packet = fake.fake_packet()
|
||||
mock_client = MockClientDriver()
|
||||
mock_client._send_side_effect = Exception('Send error')
|
||||
mock_client_class.return_value = mock_client
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log_error:
|
||||
result = tx._send_direct(packet)
|
||||
|
||||
self.assertFalse(result)
|
||||
mock_log_error.error.assert_called()
|
||||
|
||||
|
||||
class TestSendPacketThread(unittest.TestCase):
|
||||
"""Unit tests for the SendPacketThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
tracker.PacketTrack._instance = None
|
||||
self.packet = fake.fake_packet(msg_number='123')
|
||||
self.thread = tx.SendPacketThread(self.packet)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.thread.stop()
|
||||
if self.thread.is_alive():
|
||||
self.thread.join(timeout=1)
|
||||
tracker.PacketTrack._instance = None
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
self.assertEqual(self.thread.packet, self.packet)
|
||||
self.assertIn('TX-', self.thread.name)
|
||||
self.assertEqual(self.thread.loop_count, 1)
|
||||
|
||||
@mock.patch('aprsd.threads.tx.tracker.PacketTrack')
|
||||
def test_loop_packet_acked(self, mock_tracker_class):
|
||||
"""Test loop() when packet is acked."""
|
||||
mock_tracker = mock.MagicMock()
|
||||
mock_tracker.get.return_value = None # Packet removed = acked
|
||||
mock_tracker_class.return_value = mock_tracker
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log:
|
||||
result = self.thread.loop()
|
||||
self.assertFalse(result)
|
||||
mock_log.info.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.tracker.PacketTrack')
|
||||
def test_loop_max_retries(self, mock_tracker_class):
|
||||
"""Test loop() when max retries reached."""
|
||||
mock_tracker = mock.MagicMock()
|
||||
tracked_packet = fake.fake_packet(msg_number='123')
|
||||
tracked_packet.send_count = 3
|
||||
tracked_packet.retry_count = 3
|
||||
mock_tracker.get.return_value = tracked_packet
|
||||
mock_tracker_class.return_value = mock_tracker
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log:
|
||||
result = self.thread.loop()
|
||||
self.assertFalse(result)
|
||||
mock_log.info.assert_called()
|
||||
mock_tracker.remove.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.tracker.PacketTrack')
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_loop_send_now(self, mock_send_direct, mock_tracker_class):
|
||||
"""Test loop() when it's time to send."""
|
||||
mock_tracker = mock.MagicMock()
|
||||
tracked_packet = fake.fake_packet(msg_number='123')
|
||||
tracked_packet.send_count = 0
|
||||
tracked_packet.retry_count = 3
|
||||
tracked_packet.last_send_time = None
|
||||
mock_tracker.get.return_value = tracked_packet
|
||||
mock_tracker_class.return_value = mock_tracker
|
||||
|
||||
mock_send_direct.return_value = True
|
||||
|
||||
result = self.thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_send_direct.assert_called()
|
||||
self.assertEqual(tracked_packet.send_count, 1)
|
||||
|
||||
@mock.patch('aprsd.threads.tx.tracker.PacketTrack')
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_loop_send_failed(self, mock_send_direct, mock_tracker_class):
|
||||
"""Test loop() when send fails."""
|
||||
mock_tracker = mock.MagicMock()
|
||||
tracked_packet = fake.fake_packet(msg_number='123')
|
||||
tracked_packet.send_count = 0
|
||||
tracked_packet.retry_count = 3
|
||||
tracked_packet.last_send_time = None
|
||||
mock_tracker.get.return_value = tracked_packet
|
||||
mock_tracker_class.return_value = mock_tracker
|
||||
|
||||
mock_send_direct.return_value = False
|
||||
|
||||
result = self.thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(
|
||||
tracked_packet.send_count, 0
|
||||
) # Should not increment on failure
|
||||
|
||||
|
||||
class TestSendAckThread(unittest.TestCase):
|
||||
"""Unit tests for the SendAckThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.default_ack_send_count = 3
|
||||
|
||||
self.packet = fake.fake_ack_packet()
|
||||
self.packet.send_count = 0
|
||||
self.thread = tx.SendAckThread(self.packet)
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
self.thread.stop()
|
||||
if self.thread.is_alive():
|
||||
self.thread.join(timeout=1)
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
self.assertEqual(self.thread.packet, self.packet)
|
||||
self.assertIn('TXAck-', self.thread.name)
|
||||
self.assertEqual(self.thread.max_retries, 3)
|
||||
|
||||
def test_loop_max_retries(self):
|
||||
"""Test loop() when max retries reached."""
|
||||
self.packet.send_count = 3
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log:
|
||||
result = self.thread.loop()
|
||||
self.assertFalse(result)
|
||||
mock_log.debug.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_loop_send_now(self, mock_send_direct):
|
||||
"""Test loop() when it's time to send."""
|
||||
self.packet.last_send_time = None
|
||||
mock_send_direct.return_value = True
|
||||
|
||||
result = self.thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_send_direct.assert_called()
|
||||
self.assertEqual(self.packet.send_count, 1)
|
||||
|
||||
@mock.patch('aprsd.threads.tx._send_direct')
|
||||
def test_loop_waiting(self, mock_send_direct):
|
||||
"""Test loop() when waiting for next send."""
|
||||
self.packet.last_send_time = int(time.time()) - 10 # Too soon
|
||||
mock_send_direct.return_value = True
|
||||
|
||||
result = self.thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_send_direct.assert_not_called()
|
||||
|
||||
|
||||
class TestBeaconSendThread(unittest.TestCase):
|
||||
"""Unit tests for the BeaconSendThread class."""
|
||||
|
||||
def setUp(self):
|
||||
"""Set up test fixtures."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.latitude = 40.7128
|
||||
CONF.longitude = -74.0060
|
||||
CONF.beacon_interval = 10
|
||||
CONF.beacon_symbol = '>'
|
||||
CONF.callsign = 'TEST'
|
||||
|
||||
def tearDown(self):
|
||||
"""Clean up after tests."""
|
||||
pass
|
||||
|
||||
def test_init(self):
|
||||
"""Test initialization."""
|
||||
thread = tx.BeaconSendThread()
|
||||
self.assertEqual(thread.name, 'BeaconSendThread')
|
||||
self.assertEqual(thread._loop_cnt, 1)
|
||||
|
||||
def test_init_no_coordinates(self):
|
||||
"""Test initialization without coordinates."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.latitude = None
|
||||
CONF.longitude = None
|
||||
|
||||
thread = tx.BeaconSendThread()
|
||||
self.assertTrue(thread.thread_stop)
|
||||
|
||||
@mock.patch('aprsd.threads.tx.send')
|
||||
def test_loop_send_beacon(self, mock_send):
|
||||
"""Test loop() sends beacon at interval."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.beacon_interval = 1
|
||||
|
||||
thread = tx.BeaconSendThread()
|
||||
thread._loop_cnt = 1
|
||||
|
||||
result = thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_send.assert_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.send')
|
||||
def test_loop_not_time(self, mock_send):
|
||||
"""Test loop() doesn't send before interval."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.beacon_interval = 10
|
||||
|
||||
thread = tx.BeaconSendThread()
|
||||
thread._loop_cnt = 5
|
||||
|
||||
result = thread.loop()
|
||||
|
||||
self.assertTrue(result)
|
||||
mock_send.assert_not_called()
|
||||
|
||||
@mock.patch('aprsd.threads.tx.send')
|
||||
@mock.patch('aprsd.threads.tx.APRSDClient')
|
||||
def test_loop_send_exception(self, mock_client_class, mock_send):
|
||||
"""Test loop() handles send exception."""
|
||||
from oslo_config import cfg
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.beacon_interval = 1
|
||||
|
||||
thread = tx.BeaconSendThread()
|
||||
thread._loop_cnt = 1
|
||||
mock_send.side_effect = Exception('Send error')
|
||||
|
||||
with mock.patch('aprsd.threads.tx.LOG') as mock_log:
|
||||
result = thread.loop()
|
||||
self.assertTrue(result)
|
||||
mock_log.error.assert_called()
|
||||
mock_client_class.return_value.reset.assert_called()
|
||||
Reference in New Issue
Block a user