mirror of
https://github.com/craigerl/aprsd.git
synced 2026-01-15 18:15:33 -05:00
192 lines
5.9 KiB
Python
192 lines
5.9 KiB
Python
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, {})
|