import json
import os
import re
import shutil
import subprocess
import sys
import pytest
import acdp
from test_interop import NodeWorker
HERE = os.path.dirname(os.path.abspath(__file__))
REPO = os.path.normpath(os.path.join(HERE, "..", ".."))
MANIFEST = os.path.join(HERE, "expected_surface.json")
def _load_manifest() -> dict:
with open(MANIFEST, encoding="utf-8") as fh:
return json.load(fh)["classes"]
def _python_surface(class_name: str) -> list[str]:
cls = getattr(acdp, class_name)
return sorted(n for n in dir(cls) if not n.startswith("_"))
@pytest.fixture(scope="module")
def node():
if shutil.which("node") is None:
pytest.skip("node executable not found on PATH")
worker = NodeWorker()
try:
worker.call("ping") yield worker
finally:
worker.close()
def test_python_surface_matches_manifest():
manifest = _load_manifest()
for class_name, methods in manifest.items():
assert _python_surface(class_name) == sorted(methods), (
f"Python {class_name} surface drifted from expected_surface.json"
)
def test_node_surface_matches_manifest(node):
manifest = _load_manifest()
described = node.call("describe")["classes"]
for class_name, methods in manifest.items():
assert class_name in described, f"Node binding missing class {class_name}"
assert sorted(described[class_name]) == sorted(methods), (
f"Node {class_name} surface drifted from expected_surface.json"
)
def test_python_and_node_class_sets_match(node):
described = node.call("describe")["classes"]
manifest = _load_manifest()
assert set(described) == set(manifest), "Node class set drifted from manifest"
for class_name in manifest:
assert hasattr(acdp, class_name), f"Python binding missing class {class_name}"
def test_python_and_node_method_sets_match_each_other(node):
described = node.call("describe")["classes"]
for class_name in _load_manifest():
py = _python_surface(class_name)
nd = sorted(described[class_name])
assert py == nd, (
f"{class_name} surface differs between bindings:\n"
f" python only: {sorted(set(py) - set(nd))}\n"
f" node only: {sorted(set(nd) - set(py))}"
)
def _version_from_toml(path: str) -> str:
with open(path, encoding="utf-8") as fh:
for line in fh:
m = re.match(r'\s*version\s*=\s*"([^"]+)"', line)
if m:
return m.group(1)
raise AssertionError(f"no version found in {path}")
def _version_from_json(path: str) -> str:
with open(path, encoding="utf-8") as fh:
return json.load(fh)["version"]
def test_binding_versions_are_in_sync():
versions = {
"acdp-py/pyproject.toml": _version_from_toml(
os.path.join(REPO, "bindings", "acdp-py", "pyproject.toml")
),
"acdp-py/Cargo.toml": _version_from_toml(
os.path.join(REPO, "bindings", "acdp-py", "Cargo.toml")
),
"acdp-node/Cargo.toml": _version_from_toml(
os.path.join(REPO, "bindings", "acdp-node", "Cargo.toml")
),
"acdp-node/package.json": _version_from_json(
os.path.join(REPO, "bindings", "acdp-node", "package.json")
),
}
assert len(set(versions.values())) == 1, (
f"binding versions out of sync: {versions}"
)
def test_node_reported_version_matches_package(node):
pkg_version = _version_from_json(
os.path.join(REPO, "bindings", "acdp-node", "package.json")
)
assert node.call("describe")["version"] == pkg_version
if __name__ == "__main__": sys.exit(pytest.main([__file__, "-v"]))