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:
@@ -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'])
|
||||
@@ -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, {})
|
||||
Reference in New Issue
Block a user