ytptube/app/tests/test_singleton.py
2025-09-11 21:49:39 +03:00

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__])