1
0
mirror of https://github.com/craigerl/aprsd.git synced 2026-07-30 13:54:23 -04:00

Added unit tests

This commit is contained in:
2025-12-09 17:20:23 -05:00
parent 2b2dbb114b
commit d0dfaa42e6
35 changed files with 5426 additions and 48 deletions
View File
+100
View File
@@ -0,0 +1,100 @@
import datetime
import unittest
from unittest import mock
from oslo_config import cfg
from aprsd.stats import app
CONF = cfg.CONF
class TestAPRSDStats(unittest.TestCase):
"""Unit tests for the APRSDStats class."""
def setUp(self):
"""Set up test fixtures."""
# Reset singleton instance
app.APRSDStats._instance = None
CONF.callsign = 'TEST'
def tearDown(self):
"""Clean up after tests."""
app.APRSDStats._instance = None
def test_singleton_pattern(self):
"""Test that APRSDStats is a singleton."""
stats1 = app.APRSDStats()
stats2 = app.APRSDStats()
self.assertIs(stats1, stats2)
def test_init(self):
"""Test initialization."""
stats = app.APRSDStats()
self.assertIsNotNone(stats.start_time)
self.assertIsInstance(stats.start_time, datetime.datetime)
def test_uptime(self):
"""Test uptime() method."""
stats = app.APRSDStats()
import time
time.sleep(0.1) # Small delay
uptime = stats.uptime()
self.assertIsInstance(uptime, datetime.timedelta)
self.assertGreaterEqual(uptime.total_seconds(), 0.1)
@mock.patch('aprsd.stats.app.tracemalloc.get_traced_memory')
@mock.patch('aprsd.stats.app.aprsd_log.logging_queue')
def test_stats(self, mock_queue, mock_tracemalloc):
"""Test stats() method."""
mock_tracemalloc.return_value = (1024 * 1024, 2 * 1024 * 1024) # 1MB, 2MB
mock_queue.qsize.return_value = 5
stats = app.APRSDStats()
result = stats.stats()
self.assertIn('version', result)
self.assertIn('uptime', result)
self.assertIn('callsign', result)
self.assertIn('memory_current', result)
self.assertIn('memory_current_str', result)
self.assertIn('memory_peak', result)
self.assertIn('memory_peak_str', result)
self.assertIn('loging_queue', result)
self.assertEqual(result['callsign'], 'TEST')
self.assertEqual(result['memory_current'], 1024 * 1024)
self.assertEqual(result['loging_queue'], 5)
@mock.patch('aprsd.stats.app.tracemalloc.get_traced_memory')
@mock.patch('aprsd.stats.app.aprsd_log.logging_queue')
def test_stats_serializable(self, mock_queue, mock_tracemalloc):
"""Test stats() with serializable=True."""
mock_tracemalloc.return_value = (1024 * 1024, 2 * 1024 * 1024)
mock_queue.qsize.return_value = 5
stats = app.APRSDStats()
result = stats.stats(serializable=True)
self.assertIsInstance(result['uptime'], str)
# Should be JSON serializable
import json
json.dumps(result) # Should not raise exception
def test_stats_memory_formatting(self):
"""Test that memory is formatted correctly."""
with mock.patch(
'aprsd.stats.app.tracemalloc.get_traced_memory'
) as mock_tracemalloc:
with mock.patch('aprsd.stats.app.aprsd_log.logging_queue') as mock_queue:
mock_tracemalloc.return_value = (1024 * 1024, 2 * 1024 * 1024)
mock_queue.qsize.return_value = 0
stats = app.APRSDStats()
result = stats.stats()
# 1MB should format as 'MB', not 'KB'
self.assertIn('MB', result['memory_current_str'])
self.assertIn('MB', result['memory_peak_str'])
+191
View File
@@ -0,0 +1,191 @@
import unittest
from unittest import mock
from aprsd.stats import collector
class MockStatsProducer:
"""Mock implementation of StatsProducer for testing."""
_instance = None
def __init__(self, name='MockProducer'):
self.name = name
self.stats_called = False
def __call__(self):
"""Make it callable like a singleton."""
if self._instance is None:
self._instance = self
return self._instance
def stats(self, serializable=False):
self.stats_called = True
return {'test': 'data', 'serializable': serializable}
class TestStatsCollector(unittest.TestCase):
"""Unit tests for the Collector class."""
def setUp(self):
"""Set up test fixtures."""
# Reset singleton instance
collector.Collector._instance = None
# Clear producers to start fresh
c = collector.Collector()
c.producers = []
def tearDown(self):
"""Clean up after tests."""
collector.Collector._instance = None
def test_singleton_pattern(self):
"""Test that Collector is a singleton."""
collector1 = collector.Collector()
collector2 = collector.Collector()
self.assertIs(collector1, collector2)
def test_init(self):
"""Test initialization."""
c = collector.Collector()
# After setUp, producers should be empty
self.assertEqual(len(c.producers), 0)
def test_register_producer(self):
"""Test register_producer() method."""
c = collector.Collector()
producer = MockStatsProducer()
c.register_producer(producer)
self.assertIn(producer, c.producers)
def test_register_producer_non_protocol(self):
"""Test register_producer() raises TypeError for non-protocol objects."""
c = collector.Collector()
non_producer = object()
with self.assertRaises(TypeError):
c.register_producer(non_producer)
def test_unregister_producer(self):
"""Test unregister_producer() method."""
c = collector.Collector()
producer = MockStatsProducer()
c.register_producer(producer)
c.unregister_producer(producer)
self.assertNotIn(producer, c.producers)
def test_unregister_producer_non_protocol(self):
"""Test unregister_producer() raises TypeError for non-protocol objects."""
c = collector.Collector()
non_producer = object()
with self.assertRaises(TypeError):
c.unregister_producer(non_producer)
def test_collect(self):
"""Test collect() method."""
c = collector.Collector()
producer1 = MockStatsProducer('Producer1')
producer2 = MockStatsProducer('Producer2')
c.register_producer(producer1)
c.register_producer(producer2)
stats = c.collect()
self.assertIsInstance(stats, dict)
self.assertIn('MockStatsProducer', stats)
self.assertTrue(producer1().stats_called)
self.assertTrue(producer2().stats_called)
def test_collect_serializable(self):
"""Test collect() with serializable=True."""
c = collector.Collector()
producer = MockStatsProducer()
c.register_producer(producer)
stats = c.collect(serializable=True)
# Should pass serializable flag to producers
self.assertIsInstance(stats, dict)
def test_collect_with_exception(self):
"""Test collect() raises exception from producer."""
c = collector.Collector()
class FailingProducer:
_instance = None
def __call__(self):
if self._instance is None:
self._instance = self
return self._instance
def stats(self, serializable=False):
raise RuntimeError('Stats error')
producer = FailingProducer()
c.register_producer(producer)
with self.assertRaises(RuntimeError):
c.collect()
def test_stop_all(self):
"""Test stop_all() method."""
c = collector.Collector()
producer1 = MockStatsProducer('Producer1')
producer2 = MockStatsProducer('Producer2')
c.register_producer(producer1)
c.register_producer(producer2)
with mock.patch('aprsd.stats.collector.LOG') as mock_log:
c.stop_all()
self.assertEqual(len(c.producers), 0)
# Should log for each producer
self.assertGreaterEqual(mock_log.info.call_count, 2)
def test_multiple_producers(self):
"""Test multiple producers are collected."""
c = collector.Collector()
call_order = []
class OrderedProducer:
_instance = None
def __init__(self, name):
self.name = name
def __call__(self):
if self._instance is None:
self._instance = self
return self._instance
def stats(self, serializable=False):
call_order.append(self.name)
return {'name': self.name}
producer1 = OrderedProducer('Producer1')
producer2 = OrderedProducer('Producer2')
producer3 = OrderedProducer('Producer3')
c.register_producer(producer1)
c.register_producer(producer2)
c.register_producer(producer3)
stats = c.collect()
# All producers are called (verified by call_order)
self.assertIn('Producer1', call_order)
self.assertIn('Producer2', call_order)
self.assertIn('Producer3', call_order)
# But stats dict only has 1 entry because all have same class name
# (last one overwrites previous ones)
self.assertEqual(len(stats), 1)
self.assertIn('OrderedProducer', stats)
def test_empty_collector(self):
"""Test collect() with no producers."""
c = collector.Collector()
stats = c.collect()
self.assertEqual(stats, {})