import pytest
import tempfile
import shutil
from pathlib import Path
import sys
import os
import weakref
import gc
import threading
import time
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'apexbase', 'python'))
try:
from apexbase import ApexClient, FTS_AVAILABLE
except ImportError as e:
pytest.skip(f"ApexBase not available: {e}", allow_module_level=True)
class TestContextManager:
def test_basic_context_manager(self):
with tempfile.TemporaryDirectory() as temp_dir:
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
assert not client._is_closed
client.store({"name": "Alice", "age": 25})
result = client.retrieve(1)
assert result["name"] == "Alice"
assert client._is_closed
def test_context_manager_with_exception(self):
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(ValueError, match="test exception"):
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
client.store({"name": "Alice", "age": 25})
raise ValueError("test exception")
assert client._is_closed
def test_context_manager_chain_operations(self):
with tempfile.TemporaryDirectory() as temp_dir:
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
client.init_fts(index_fields=['content'])
assert client._is_fts_enabled()
client.store({"content": "Python programming"})
results = client.search_text("python")
assert len(results) > 0
assert client._is_closed
def test_context_manager_nested(self):
with tempfile.TemporaryDirectory() as temp_dir:
with ApexClient(dirpath=temp_dir) as client1:
client1.create_table("default")
client1.store({"name": "Alice"})
temp_dir2 = tempfile.mkdtemp()
try:
with ApexClient(dirpath=temp_dir2) as client2:
client2.create_table("default")
client2.store({"name": "Bob"})
assert not client2._is_closed
assert client2._is_closed
finally:
import shutil
shutil.rmtree(temp_dir2, ignore_errors=True)
def test_context_manager_return_value(self):
with tempfile.TemporaryDirectory() as temp_dir:
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
assert client is not None
assert hasattr(client, 'store')
assert hasattr(client, 'query')
def test_context_manager_exit_suppression(self):
with tempfile.TemporaryDirectory() as temp_dir:
with pytest.raises(ValueError, match="test exception"):
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
client.store({"test": "data"})
raise ValueError("test exception")
assert client._is_closed
class TestManualCloseOperations:
def test_manual_close_basic(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
assert not client._is_closed
client.store({"name": "Alice", "age": 25})
result = client.retrieve(1)
assert result["name"] == "Alice"
client.close()
assert client._is_closed
with pytest.raises(RuntimeError, match="connection has been closed"):
client.store({"test": "data"})
def test_multiple_close_calls(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"name": "Alice"})
client.close()
assert client._is_closed
client.close()
client.close()
assert client._is_closed
def test_force_close_operation(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"name": "Alice"})
client._force_close()
assert client._is_closed
client._force_close()
assert client._is_closed
def test_close_with_fts_enabled(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.init_fts(index_fields=['content'])
client.store({"content": "Test content"})
client.close()
assert client._is_closed
with pytest.raises((RuntimeError, ValueError, AttributeError)):
client.search_text("test")
class TestInstanceRegistry:
def test_registry_registration(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
db_path = str(client._db_path)
assert db_path in client._registry._instances
client.close()
assert db_path not in client._registry._instances
def test_registry_auto_cleanup(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"name": "Alice", "age": 25})
client_ref = weakref.ref(client)
del client
gc.collect()
time.sleep(0.1)
assert client_ref() is None
def test_registry_close_all(self):
temp_dirs = [tempfile.mkdtemp() for _ in range(3)]
clients = []
try:
for i, td in enumerate(temp_dirs):
client = ApexClient(dirpath=td)
client.create_table("default")
client.store({"id": i, "name": f"Client_{i}"})
clients.append(client)
if clients:
clients[0]._registry.close_all()
for client in clients:
assert client._is_closed
finally:
import shutil
for td in temp_dirs:
shutil.rmtree(td, ignore_errors=True)
def test_registry_duplicate_paths(self):
with tempfile.TemporaryDirectory() as temp_dir:
client1 = ApexClient(dirpath=temp_dir)
client1.create_table("default")
client1.store({"name": "First"})
client2 = ApexClient(dirpath=temp_dir)
assert not client1._is_closed
assert not client2._is_closed
client2.use_table("default")
result = client2.execute("SELECT * FROM default")
assert len(result) == 1
client2.store({"name": "Second"})
result1 = client1.execute("SELECT * FROM default")
result2 = client2.execute("SELECT * FROM default")
assert len(result1) == 2
assert len(result2) == 2
client1.close()
client2.close()
def test_registry_disabled_auto_manage(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir, _auto_manage=False)
client.create_table("default")
db_path = str(client._db_path)
assert db_path not in client._registry._instances
client.close()
assert client._is_closed
class TestLifecycleStateTransitions:
def test_state_transition_sequence(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
assert not client._is_closed
client.store({"test": "data"})
client.close()
assert client._is_closed
with pytest.raises(RuntimeError):
client.store({"test": "data"})
def test_state_persistence_across_operations(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
operations = [
lambda: client.store({"test": "data"}),
lambda: client.query(),
lambda: client.retrieve(1),
lambda: client.list_tables(),
lambda: client.count_rows(),
]
for op in operations:
assert not client._is_closed
op()
assert not client._is_closed
client.close()
assert client._is_closed
def test_state_with_table_operations(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.create_table("test_table")
assert not client._is_closed
client.use_table("test_table")
assert not client._is_closed
client.drop_table("test_table")
assert not client._is_closed
client.close()
assert client._is_closed
class TestResourceLeakPrevention:
def test_file_handle_cleanup(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
for i in range(100):
client.store({"id": i, "data": f"test_{i}"})
client.close()
try:
shutil.rmtree(temp_dir)
os.makedirs(temp_dir)
except OSError as e:
pytest.fail(f"File handle leak detected: {e}")
def test_memory_cleanup_on_close(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
large_data = [{"id": i, "data": "x" * 1000} for i in range(1000)]
client.store(large_data)
client_ref = weakref.ref(client)
client.close()
assert client._is_closed
del client
gc.collect()
time.sleep(0.1)
assert client_ref() is None
def test_fts_resource_cleanup(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.init_fts(index_fields=['content'])
for i in range(100):
client.store({"content": f"Searchable content {i}"})
results = client.search_text("content")
assert len(results) > 0
client.close()
assert client._is_closed
fts_dir = Path(temp_dir) / "fts_indexes"
if fts_dir.exists():
assert fts_dir.is_dir()
class TestExceptionHandling:
def test_exception_during_init(self):
with tempfile.TemporaryDirectory() as temp_dir:
try:
client = ApexClient(dirpath=temp_dir, durability="invalid")
client.create_table("default")
except ValueError:
pass
def test_exception_during_operation(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
try:
client.delete(999999)
except Exception:
pass
client.store({"recovery": "test"})
result = client.retrieve(1)
assert result["recovery"] == "test"
client.close()
def test_exception_in_context_manager(self):
with tempfile.TemporaryDirectory() as temp_dir:
exception_caught = False
try:
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
client.store({"test": "data"})
raise ValueError("Test exception")
except ValueError:
exception_caught = True
assert exception_caught
assert client._is_closed
def test_nested_exception_handling(self):
temp_dir1 = tempfile.mkdtemp()
temp_dir2 = tempfile.mkdtemp()
try:
with ApexClient(dirpath=temp_dir1) as client1:
client1.create_table("default")
client1.store({"name": "Alice"})
try:
with ApexClient(dirpath=temp_dir2) as client2:
client2.create_table("default")
client2.store({"name": "Bob"})
raise RuntimeError("Inner exception")
except RuntimeError:
pass
client1.store({"name": "Charlie"})
except Exception as e:
print(f"Nested exception test: {e}")
finally:
import shutil
shutil.rmtree(temp_dir1, ignore_errors=True)
shutil.rmtree(temp_dir2, ignore_errors=True)
class TestAtexitCleanup:
def test_atexit_registration(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.close()
assert client._is_closed
def test_cleanup_on_interpreter_shutdown(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"test": "data"})
client._registry.close_all()
assert client._is_closed
class TestConcurrentLifecycle:
def test_concurrent_close(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
for i in range(100):
client.store({"id": i, "data": f"test_{i}"})
def close_client():
try:
client.close()
return True
except Exception as e:
print(f"Close error: {e}")
return False
import threading
threads = []
results = []
for i in range(5):
thread = threading.Thread(target=lambda: results.append(close_client()))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
assert client._is_closed
assert any(results)
def test_concurrent_creation_cleanup(self):
with tempfile.TemporaryDirectory() as temp_dir:
def create_and_close():
try:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.store({"test": "data"})
client.close()
return True
except Exception as e:
if "locked" in str(e).lower():
return False
print(f"Create/close error: {e}")
return False
import threading
threads = []
results = []
for i in range(10):
thread = threading.Thread(target=lambda: results.append(create_and_close()))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
success_count = sum(results)
assert success_count >= 1, f"At least one concurrent operation should succeed, got {success_count}"
class TestLifecycleWithFTS:
def test_fts_lifecycle_integration(self):
with tempfile.TemporaryDirectory() as temp_dir:
with ApexClient(dirpath=temp_dir) as client:
client.create_table("default")
client.init_fts(index_fields=['content'])
client.store({"content": "Python programming"})
results = client.search_text("python")
try:
client.replace(1, {"content": "JavaScript programming"})
except Exception as e:
print(f"FTS lifecycle replace: {e}")
assert client._is_closed
def test_fts_cleanup_on_close(self):
with tempfile.TemporaryDirectory() as temp_dir:
client = ApexClient(dirpath=temp_dir)
client.create_table("default")
client.init_fts(index_fields=['content'])
client.store({"content": "Test content"})
results = client.search_text("test")
client.close()
assert client._is_closed
with pytest.raises((RuntimeError, ValueError, AttributeError)):
client.search_text("test")
if __name__ == "__main__":
pytest.main([__file__, "-v"])