test_tool_concurrency.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. """Unit tests for tool concurrency management."""
  2. import pytest
  3. from agency_swarm.tools.concurrency import LockState, ToolConcurrencyManager
  4. class TestLockState:
  5. """Test LockState NamedTuple."""
  6. def test_lock_state_creation(self):
  7. """Test creating LockState instances."""
  8. state = LockState(busy=True, owner="test_tool")
  9. assert state.busy is True
  10. assert state.owner == "test_tool"
  11. def test_lock_state_immutability(self):
  12. """Test that LockState is immutable."""
  13. state = LockState(busy=False, owner=None)
  14. with pytest.raises(AttributeError):
  15. state.busy = True # Should raise error as NamedTuple is immutable
  16. def test_lock_state_equality(self):
  17. """Test LockState equality comparison."""
  18. state1 = LockState(busy=True, owner="tool1")
  19. state2 = LockState(busy=True, owner="tool1")
  20. state3 = LockState(busy=False, owner="tool1")
  21. assert state1 == state2
  22. assert state1 != state3
  23. class TestToolConcurrencyManager:
  24. """Test ToolConcurrencyManager class."""
  25. def test_manager_initialization(self):
  26. """Test manager initializes with correct default state."""
  27. manager = ToolConcurrencyManager()
  28. busy, owner = manager.is_lock_active()
  29. assert busy is False
  30. assert owner is None
  31. assert manager.get_active_count() == 0
  32. def test_lock_acquisition_and_release(self):
  33. """Test lock acquisition and release cycle."""
  34. manager = ToolConcurrencyManager()
  35. # Initially no lock
  36. busy, owner = manager.is_lock_active()
  37. assert busy is False
  38. assert owner is None
  39. # Acquire lock
  40. manager.acquire_lock("test_tool")
  41. busy, owner = manager.is_lock_active()
  42. assert busy is True
  43. assert owner == "test_tool"
  44. # Release lock
  45. manager.release_lock()
  46. busy, owner = manager.is_lock_active()
  47. assert busy is False
  48. assert owner is None
  49. def test_lock_reacquisition(self):
  50. """Test that lock can be reacquired by different tools."""
  51. manager = ToolConcurrencyManager()
  52. # First tool acquires lock
  53. manager.acquire_lock("tool1")
  54. busy, owner = manager.is_lock_active()
  55. assert busy is True
  56. assert owner == "tool1"
  57. # Release and acquire by different tool
  58. manager.release_lock()
  59. manager.acquire_lock("tool2")
  60. busy, owner = manager.is_lock_active()
  61. assert busy is True
  62. assert owner == "tool2"
  63. def test_active_count_operations(self):
  64. """Test active count increment and decrement."""
  65. manager = ToolConcurrencyManager()
  66. # Initially zero
  67. assert manager.get_active_count() == 0
  68. # Increment multiple times
  69. manager.increment_active_count()
  70. assert manager.get_active_count() == 1
  71. manager.increment_active_count()
  72. assert manager.get_active_count() == 2
  73. manager.decrement_active_count()
  74. assert manager.get_active_count() == 1
  75. manager.decrement_active_count()
  76. assert manager.get_active_count() == 0
  77. def test_active_count_underflow_protection(self):
  78. """Test that active count doesn't go below zero."""
  79. manager = ToolConcurrencyManager()
  80. # Try to decrement when count is zero
  81. manager.decrement_active_count()
  82. assert manager.get_active_count() == 0
  83. # Multiple decrements shouldn't cause negative count
  84. manager.decrement_active_count()
  85. manager.decrement_active_count()
  86. assert manager.get_active_count() == 0
  87. def test_lock_with_none_owner(self):
  88. """Test lock operations with None as owner."""
  89. manager = ToolConcurrencyManager()
  90. manager.acquire_lock(None)
  91. busy, owner = manager.is_lock_active()
  92. assert busy is True
  93. assert owner is None
  94. manager.release_lock()
  95. busy, owner = manager.is_lock_active()
  96. assert busy is False
  97. assert owner is None
  98. def test_concurrent_operations_simulation(self):
  99. """Test simulated concurrent operations."""
  100. manager = ToolConcurrencyManager()
  101. # Simulate multiple tools starting
  102. manager.increment_active_count() # Tool 1 starts
  103. manager.increment_active_count() # Tool 2 starts
  104. assert manager.get_active_count() == 2
  105. # One tool acquires one_call lock
  106. manager.acquire_lock("exclusive_tool")
  107. busy, owner = manager.is_lock_active()
  108. assert busy is True
  109. assert owner == "exclusive_tool"
  110. # Tool 1 finishes
  111. manager.decrement_active_count()
  112. assert manager.get_active_count() == 1
  113. # Exclusive tool finishes
  114. manager.release_lock()
  115. manager.decrement_active_count()
  116. assert manager.get_active_count() == 0
  117. busy, owner = manager.is_lock_active()
  118. assert busy is False
  119. assert owner is None
  120. def test_independent_lock_and_count_state(self):
  121. """Test that lock state and active count are independent."""
  122. manager = ToolConcurrencyManager()
  123. # Increment count without acquiring lock
  124. manager.increment_active_count()
  125. manager.increment_active_count()
  126. assert manager.get_active_count() == 2
  127. busy, owner = manager.is_lock_active()
  128. assert busy is False
  129. assert owner is None
  130. # Acquire lock without changing count
  131. manager.acquire_lock("test_tool")
  132. assert manager.get_active_count() == 2 # Count unchanged
  133. busy, owner = manager.is_lock_active()
  134. assert busy is True
  135. assert owner == "test_tool"
  136. # Release lock without changing count
  137. manager.release_lock()
  138. assert manager.get_active_count() == 2 # Count still unchanged
  139. busy, owner = manager.is_lock_active()
  140. assert busy is False
  141. assert owner is None