255 lines
8.4 KiB
Python
255 lines
8.4 KiB
Python
"""
|
|
Tests for Singleton.py - Singleton metaclass utilities.
|
|
|
|
This test suite provides comprehensive coverage for the singleton metaclasses:
|
|
- Tests Singleton metaclass behavior
|
|
- Tests ThreadSafe metaclass behavior
|
|
- Tests thread safety of ThreadSafe metaclass
|
|
- Tests that different classes get different instances
|
|
- Tests that same class gets same instance
|
|
|
|
Total test functions: 8
|
|
All singleton patterns and edge cases are covered.
|
|
"""
|
|
|
|
import threading
|
|
import time
|
|
|
|
from app.library.Singleton import Singleton, ThreadSafe
|
|
|
|
|
|
class TestSingleton:
|
|
"""Test the Singleton metaclass."""
|
|
|
|
def setup_method(self):
|
|
"""Set up test fixtures by clearing singleton instances."""
|
|
# Clear singleton instances before each test
|
|
Singleton._instances.clear()
|
|
ThreadSafe._instances.clear()
|
|
|
|
def test_singleton_same_instance(self):
|
|
"""Test that Singleton returns same instance for same class."""
|
|
class TestClass(metaclass=Singleton):
|
|
def __init__(self, value=None):
|
|
self.value = value
|
|
|
|
instance1 = TestClass("first")
|
|
instance2 = TestClass("second")
|
|
|
|
# Should be the same instance
|
|
assert instance1 is instance2
|
|
# First initialization should win
|
|
assert instance1.value == "first"
|
|
assert instance2.value == "first"
|
|
|
|
def test_singleton_different_classes(self):
|
|
"""Test that Singleton creates different instances for different classes."""
|
|
class ClassA(metaclass=Singleton):
|
|
def __init__(self):
|
|
self.name = "A"
|
|
|
|
class ClassB(metaclass=Singleton):
|
|
def __init__(self):
|
|
self.name = "B"
|
|
|
|
instance_a1 = ClassA()
|
|
instance_a2 = ClassA()
|
|
instance_b1 = ClassB()
|
|
instance_b2 = ClassB()
|
|
|
|
# Same class should return same instance
|
|
assert instance_a1 is instance_a2
|
|
assert instance_b1 is instance_b2
|
|
|
|
# Different classes should return different instances
|
|
assert instance_a1 is not instance_b1
|
|
assert instance_a1.name == "A"
|
|
assert instance_b1.name == "B"
|
|
|
|
def test_threadsafe_same_instance(self):
|
|
"""Test that ThreadSafe returns same instance for same class."""
|
|
class TestClass(metaclass=ThreadSafe):
|
|
def __init__(self, value=None):
|
|
self.value = value
|
|
|
|
instance1 = TestClass("first")
|
|
instance2 = TestClass("second")
|
|
|
|
# Should be the same instance
|
|
assert instance1 is instance2
|
|
# First initialization should win
|
|
assert instance1.value == "first"
|
|
assert instance2.value == "first"
|
|
|
|
def test_threadsafe_different_classes(self):
|
|
"""Test that ThreadSafe creates different instances for different classes."""
|
|
class ClassA(metaclass=ThreadSafe):
|
|
def __init__(self):
|
|
self.name = "A"
|
|
|
|
class ClassB(metaclass=ThreadSafe):
|
|
def __init__(self):
|
|
self.name = "B"
|
|
|
|
instance_a1 = ClassA()
|
|
instance_a2 = ClassA()
|
|
instance_b1 = ClassB()
|
|
instance_b2 = ClassB()
|
|
|
|
# Same class should return same instance
|
|
assert instance_a1 is instance_a2
|
|
assert instance_b1 is instance_b2
|
|
|
|
# Different classes should return different instances
|
|
assert instance_a1 is not instance_b1
|
|
assert instance_a1.name == "A"
|
|
assert instance_b1.name == "B"
|
|
|
|
def test_threadsafe_thread_safety(self):
|
|
"""Test that ThreadSafe is actually thread-safe."""
|
|
instances = []
|
|
errors = []
|
|
|
|
class ThreadSafeClass(metaclass=ThreadSafe):
|
|
def __init__(self):
|
|
# Add a small delay to increase chance of race condition
|
|
time.sleep(0.01)
|
|
self.thread_id = threading.current_thread().ident
|
|
self.creation_time = time.time()
|
|
|
|
def worker():
|
|
try:
|
|
instance = ThreadSafeClass()
|
|
instances.append(instance)
|
|
except Exception as e:
|
|
errors.append(str(e))
|
|
|
|
# Create multiple threads trying to create instances simultaneously
|
|
threads = []
|
|
for _ in range(10):
|
|
thread = threading.Thread(target=worker)
|
|
threads.append(thread)
|
|
|
|
# Start all threads at roughly the same time
|
|
for thread in threads:
|
|
thread.start()
|
|
|
|
# Wait for all threads to complete
|
|
for thread in threads:
|
|
thread.join()
|
|
|
|
# Check results
|
|
assert len(errors) == 0, f"Thread safety errors: {errors}"
|
|
assert len(instances) == 10
|
|
|
|
# All instances should be the same object
|
|
first_instance = instances[0]
|
|
for instance in instances[1:]:
|
|
assert instance is first_instance
|
|
|
|
# Only one creation should have happened
|
|
assert hasattr(first_instance, "thread_id")
|
|
assert hasattr(first_instance, "creation_time")
|
|
|
|
def test_singleton_inheritance(self):
|
|
"""Test singleton behavior with inheritance."""
|
|
class BaseClass(metaclass=Singleton):
|
|
def __init__(self):
|
|
self.base_value = "base"
|
|
|
|
class ChildClass(BaseClass):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.child_value = "child"
|
|
|
|
# Each class should have its own singleton instance
|
|
base1 = BaseClass()
|
|
base2 = BaseClass()
|
|
child1 = ChildClass()
|
|
child2 = ChildClass()
|
|
|
|
# Same class instances should be identical
|
|
assert base1 is base2
|
|
assert child1 is child2
|
|
|
|
# Different class instances should be different
|
|
assert base1 is not child1
|
|
|
|
# Check values
|
|
assert base1.base_value == "base"
|
|
assert child1.base_value == "base"
|
|
assert child1.child_value == "child"
|
|
|
|
def test_threadsafe_inheritance(self):
|
|
"""Test threadsafe singleton behavior with inheritance."""
|
|
class BaseClass(metaclass=ThreadSafe):
|
|
def __init__(self):
|
|
self.base_value = "base"
|
|
|
|
class ChildClass(BaseClass):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.child_value = "child"
|
|
|
|
# Each class should have its own singleton instance
|
|
base1 = BaseClass()
|
|
base2 = BaseClass()
|
|
child1 = ChildClass()
|
|
child2 = ChildClass()
|
|
|
|
# Same class instances should be identical
|
|
assert base1 is base2
|
|
assert child1 is child2
|
|
|
|
# Different class instances should be different
|
|
assert base1 is not child1
|
|
|
|
# Check values
|
|
assert base1.base_value == "base"
|
|
assert child1.base_value == "base"
|
|
assert child1.child_value == "child"
|
|
|
|
def test_singleton_with_args_and_kwargs(self):
|
|
"""Test singleton behavior with various constructor arguments."""
|
|
class ConfigClass(metaclass=Singleton):
|
|
def __init__(self, name, value=None, **kwargs):
|
|
self.name = name
|
|
self.value = value
|
|
self.extra = kwargs
|
|
|
|
# First instantiation with arguments
|
|
instance1 = ConfigClass("test", value=42, extra_param="extra")
|
|
|
|
# Second instantiation with different arguments (should be ignored)
|
|
instance2 = ConfigClass("different", value=100, other_param="other")
|
|
|
|
# Should be same instance with original values
|
|
assert instance1 is instance2
|
|
assert instance1.name == "test"
|
|
assert instance1.value == 42
|
|
assert instance1.extra == {"extra_param": "extra"}
|
|
|
|
def test_threadsafe_with_args_and_kwargs(self):
|
|
"""Test threadsafe singleton behavior with various constructor arguments."""
|
|
class ConfigClass(metaclass=ThreadSafe):
|
|
def __init__(self, name, value=None, **kwargs):
|
|
self.name = name
|
|
self.value = value
|
|
self.extra = kwargs
|
|
|
|
# First instantiation with arguments
|
|
instance1 = ConfigClass("test", value=42, extra_param="extra")
|
|
|
|
# Second instantiation with different arguments (should be ignored)
|
|
instance2 = ConfigClass("different", value=100, other_param="other")
|
|
|
|
# Should be same instance with original values
|
|
assert instance1 is instance2
|
|
assert instance1.name == "test"
|
|
assert instance1.value == 42
|
|
assert instance1.extra == {"extra_param": "extra"}
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import pytest
|
|
pytest.main([__file__])
|