mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
- Remove mock files from main semantica module (keep test environment clean) - Enhance conftest.py with pre-emptive sys.modules mocking - Create mock arrow_exporter module at runtime before imports - Fix pyarrow 'pa' alias and schema mocking issues - Ensure benchmark tests run without heavy dependencies - All tests pass with zero changes to main codebase structure Co-authored-by: ZohaibHassan16 <zohaib.hassan16@example.com> Co-authored-by: KaifAhmad1 <kaifahmad087@gmail.com>
356 lines
10 KiB
Python
356 lines
10 KiB
Python
import importlib.abc
|
|
import importlib.machinery
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import uuid
|
|
from unittest.mock import patch
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
# Import interception
|
|
|
|
HEAVY_LIBS = {
|
|
"pdfplumber",
|
|
"docx",
|
|
"pptx",
|
|
"openpyxl",
|
|
"pandas",
|
|
"PIL",
|
|
"PIL.Image",
|
|
"PIL.ImageDraw",
|
|
"lxml",
|
|
"pytesseract",
|
|
"networkx",
|
|
"chardet",
|
|
"langdetect",
|
|
"neo4j",
|
|
"weaviate",
|
|
"qdrant_client",
|
|
"sentence_transformers",
|
|
"transformers",
|
|
"fastembed",
|
|
"spacy",
|
|
"thinc",
|
|
"torch",
|
|
"matplotlib",
|
|
"umap",
|
|
"pynndescent",
|
|
"fireworks",
|
|
"fireworks.client",
|
|
"docling",
|
|
"docling.document_converter",
|
|
"docling.backend",
|
|
"docling_core",
|
|
"docling_core.types",
|
|
"instructor",
|
|
"instructor.processing",
|
|
"instructor.core",
|
|
"instructor.providers",
|
|
"instructor.providers.fireworks",
|
|
"pyarrow",
|
|
"arrow",
|
|
"pa",
|
|
}
|
|
|
|
|
|
class MockMeta(type):
|
|
"""Metaclass that only claims RobustMocks as instances."""
|
|
|
|
def __instancecheck__(cls, instance):
|
|
return hasattr(instance, "_is_robust_mock")
|
|
|
|
def __subclasscheck__(cls, subclass):
|
|
return True
|
|
|
|
|
|
def create_mock_class(full_name: str):
|
|
return MockMeta(
|
|
full_name.split(".")[-1],
|
|
(object,),
|
|
{
|
|
"__module__": ".".join(full_name.split(".")[:-1]),
|
|
"__doc__": f"Mocked class {full_name}",
|
|
"__getattr__": lambda self, attr: RobustMock(f"{full_name}.{attr}"),
|
|
"__call__": lambda self, *args, **kwargs: RobustMock(full_name),
|
|
"__init__": lambda self, *args, **kwargs: None,
|
|
"__repr__": lambda self: f"<MockClass {full_name}>",
|
|
},
|
|
)
|
|
|
|
|
|
class RobustMock:
|
|
def __init__(self, name: str = "mock"):
|
|
self.__name__ = name
|
|
self.__version__ = "9.9.9"
|
|
self._is_robust_mock = True
|
|
self.__path__ = []
|
|
self.__file__ = "mock_file.py"
|
|
self.__all__ = []
|
|
|
|
def __getattr__(self, name):
|
|
if name.startswith("__") and name.endswith("__"):
|
|
raise AttributeError(name)
|
|
full_name = f"{self.__name__}.{name}"
|
|
|
|
# Special handling for common PIL patterns
|
|
if self.__name__.endswith("Image") and name == "Image":
|
|
return create_mock_class(full_name)
|
|
elif self.__name__.endswith("ImageDraw") and name == "ImageDraw":
|
|
return create_mock_class(full_name)
|
|
# Special handling for pyarrow patterns
|
|
elif self.__name__ in ["pa", "pyarrow", "arrow"] and name in ["schema", "Table", "Dataset", "array", "RecordBatch"]:
|
|
return create_mock_class(full_name)
|
|
# Capital names are classes
|
|
elif name and name[0].isupper():
|
|
return create_mock_class(full_name)
|
|
return RobustMock(full_name)
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
return RobustMock(self.__name__)
|
|
|
|
def __iter__(self):
|
|
return iter([])
|
|
|
|
def __getitem__(self, item):
|
|
return RobustMock(f"{self.__name__}[{item}]")
|
|
|
|
def __len__(self):
|
|
return 0
|
|
|
|
def __bool__(self):
|
|
return True
|
|
|
|
def __hash__(self):
|
|
return id(self)
|
|
|
|
def __repr__(self):
|
|
return f"<RobustMock {self.__name__}>"
|
|
|
|
|
|
class MockLoader(importlib.abc.Loader):
|
|
def create_module(self, spec):
|
|
mock_module = RobustMock(spec.name)
|
|
mock_module.__spec__ = spec
|
|
mock_module.__loader__ = self
|
|
mock_module.__package__ = spec.parent
|
|
return mock_module
|
|
|
|
def exec_module(self, module):
|
|
pass
|
|
|
|
|
|
class MockFinder(importlib.abc.MetaPathFinder):
|
|
def find_spec(self, fullname, path, target=None):
|
|
# Check for exact matches first
|
|
if fullname in HEAVY_LIBS:
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Check for prefix matches (e.g., PIL.Image, PIL.ImageDraw)
|
|
for lib in HEAVY_LIBS:
|
|
if fullname.startswith(lib + "."):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Special handling for PIL submodules
|
|
if fullname.startswith("PIL."):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Special handling for fireworks
|
|
if fullname.startswith("fireworks."):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Special handling for docling
|
|
if fullname.startswith("docling"):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Special handling for instructor
|
|
if fullname.startswith("instructor"):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
# Special handling for pyarrow
|
|
if fullname.startswith("pyarrow") or fullname.startswith("arrow"):
|
|
return importlib.machinery.ModuleSpec(fullname, MockLoader())
|
|
|
|
return None
|
|
|
|
|
|
if os.getenv("BENCHMARK_REAL_LIBS") != "1":
|
|
if not any(isinstance(f, MockFinder) for f in sys.meta_path):
|
|
sys.meta_path.insert(0, MockFinder())
|
|
|
|
# Special handling for 'pa' alias that's commonly used for pyarrow
|
|
if "pa" not in sys.modules:
|
|
sys.modules["pa"] = RobustMock("pa")
|
|
|
|
# Pre-emptively create a mock arrow_exporter module to prevent import errors
|
|
# This must happen BEFORE any semantica.export imports
|
|
import types
|
|
mock_arrow_module = types.ModuleType('semantica.export.arrow_exporter')
|
|
|
|
# Create a mock ArrowExporter class with proper interface
|
|
class MockArrowExporter:
|
|
def __init__(self, *args, **kwargs):
|
|
pass
|
|
def __getattr__(self, name):
|
|
return lambda *args, **kwargs: f"Mock ArrowExporter.{name}"
|
|
|
|
mock_arrow_module.ArrowExporter = MockArrowExporter
|
|
mock_arrow_module.ENTITY_SCHEMA = RobustMock("ENTITY_SCHEMA")
|
|
mock_arrow_module.RELATIONSHIP_SCHEMA = RobustMock("RELATIONSHIP_SCHEMA")
|
|
mock_arrow_module.METADATA_SCHEMA = RobustMock("METADATA_SCHEMA")
|
|
mock_arrow_module.pa = RobustMock("pa")
|
|
|
|
# Inject the mock module into sys.modules
|
|
sys.modules["semantica.export.arrow_exporter"] = mock_arrow_module
|
|
|
|
# Infrastructure and Data Fixtures
|
|
|
|
|
|
class NullTracker:
|
|
def start_tracking(self, *args, **kwargs):
|
|
return "dummy_id"
|
|
|
|
def update_tracking(self, *args, **kwargs):
|
|
pass
|
|
|
|
def stop_tracking(self, *args, **kwargs):
|
|
pass
|
|
|
|
def register_pipeline_modules(self, *args, **kwargs):
|
|
pass
|
|
|
|
def clear_pipeline_context(self, *args, **kwargs):
|
|
pass
|
|
|
|
def update_progress(self, *args, **kwargs):
|
|
pass
|
|
|
|
def update_progress_batch(self, *args, **kwargs):
|
|
pass
|
|
|
|
@property
|
|
def enabled(self):
|
|
return False
|
|
|
|
@enabled.setter
|
|
def enabled(self, value):
|
|
pass
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def kill_io_overhead():
|
|
tracker = NullTracker()
|
|
with patch("semantica.utils.logging.get_logger"), patch(
|
|
"semantica.utils.progress_tracker.get_progress_tracker", return_value=tracker
|
|
):
|
|
# Patch the export module to handle missing ArrowExporter
|
|
try:
|
|
from benchmarks.export.arrow_exporter import ArrowExporter, ENTITY_SCHEMA, RELATIONSHIP_SCHEMA, METADATA_SCHEMA
|
|
mock_arrow_module = RobustMock("semantica.export.arrow_exporter")
|
|
mock_arrow_module.ArrowExporter = ArrowExporter
|
|
mock_arrow_module.ENTITY_SCHEMA = ENTITY_SCHEMA
|
|
mock_arrow_module.RELATIONSHIP_SCHEMA = RELATIONSHIP_SCHEMA
|
|
mock_arrow_module.METADATA_SCHEMA = METADATA_SCHEMA
|
|
except ImportError:
|
|
mock_arrow_module = RobustMock("semantica.export.arrow_exporter")
|
|
|
|
with patch.dict('sys.modules', {
|
|
'semantica.export.arrow_exporter': mock_arrow_module
|
|
}):
|
|
patches = []
|
|
for mod_name, module in list(sys.modules.items()):
|
|
if mod_name.startswith("semantica.") and hasattr(
|
|
module, "get_progress_tracker"
|
|
):
|
|
p = patch.object(module, "get_progress_tracker", return_value=tracker)
|
|
patches.append(p)
|
|
for p in patches:
|
|
p.start()
|
|
yield
|
|
for p in patches:
|
|
p.stop()
|
|
|
|
|
|
class MockVectorStore:
|
|
def __init__(self, dim=384):
|
|
self.dim = dim
|
|
|
|
def embed(self, text: str):
|
|
return np.random.rand(self.dim).astype(np.float32)
|
|
|
|
def store_vectors(self, vectors, metadata):
|
|
pass
|
|
|
|
def search(self, query, limit=5):
|
|
return [
|
|
{"id": str(uuid.uuid4()), "score": 0.9, "content": "test", "metadata": {}}
|
|
for _ in range(limit)
|
|
]
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_vector_store():
|
|
return MockVectorStore()
|
|
|
|
|
|
@pytest.fixture
|
|
def generate_graph_data():
|
|
BASE_NS = "http://semantica.example.org/resource/"
|
|
PRED_NS = "http://semantica.example.org/predicate/"
|
|
|
|
def _gen(n_nodes: int = 100, avg_degree: int = 4):
|
|
nodes = [
|
|
{
|
|
"id": f"{BASE_NS}node/{i}",
|
|
"type": "Entity",
|
|
"properties": {"label": f"Node {i}"},
|
|
}
|
|
for i in range(n_nodes)
|
|
]
|
|
edges = [
|
|
{
|
|
"source_id": f"{BASE_NS}node/{i}",
|
|
"target_id": f"{BASE_NS}node/{(i+1)%n_nodes}",
|
|
"type": f"{PRED_NS}conn",
|
|
"properties": {"w": 1.0},
|
|
}
|
|
for i in range(n_nodes)
|
|
]
|
|
return nodes, edges
|
|
|
|
return _gen
|
|
|
|
|
|
@pytest.fixture
|
|
def populated_context_graph(generate_graph_data):
|
|
from semantica.context.context_graph import ContextGraph
|
|
|
|
def _create(n_nodes=1000):
|
|
g = ContextGraph()
|
|
nodes, edges = generate_graph_data(n_nodes)
|
|
g.add_nodes(nodes)
|
|
g.add_edges(edges)
|
|
return g
|
|
|
|
return _create
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_text_file():
|
|
lines = ["Line " + str(i) for i in range(1000)]
|
|
content = "\n".join(lines)
|
|
with tempfile.NamedTemporaryFile(
|
|
mode="w+", delete=False, suffix=".txt", encoding="utf-8"
|
|
) as tmp:
|
|
tmp.write(content)
|
|
tmp_path = tmp.name
|
|
yield tmp_path
|
|
if os.path.exists(tmp_path):
|
|
os.remove(tmp_path)
|
|
|
|
|
|
@pytest.fixture
|
|
def long_text_string():
|
|
return "benchmark " * 5000
|