Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 173 additions & 0 deletions sdk/python/tests/unit/test_dependency_sync.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import ast
import pathlib
import pytest
from packaging.requirements import Requirement

try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib


ROOT = pathlib.Path(__file__).parent
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 ROOT path points to test directory instead of repo root, causing test to always skip

The ROOT variable on line 12 is set to pathlib.Path(__file__).parent, which resolves to sdk/python/tests/unit/. Both parse_pyproject() and parse_setup() look for pyproject.toml and setup.py relative to this ROOT directory. However, neither file exists there — pyproject.toml is at the repo root (/) and setup.py is also at the repo root.

Root Cause and Impact

Since ROOT / "pyproject.toml" resolves to sdk/python/tests/unit/pyproject.toml which doesn't exist, parse_pyproject() returns (set(), {}) on line 33. Similarly, ROOT / "setup.py" resolves to sdk/python/tests/unit/setup.py which also doesn't exist, so parse_setup() returns (set(), {}) on line 87.

In test_dependencies_in_sync() at line 141, the condition if not py_core and not py_optional is always True, so pytest.skip() is always called. The test never actually validates anything — it silently skips every time.

The fix should set ROOT to the actual repository root directory, e.g.:

ROOT = pathlib.Path(__file__).resolve().parents[4]

or find the root by traversing up to where pyproject.toml and setup.py actually exist.

Impact: The test is entirely non-functional. It was intended to verify that dependencies in pyproject.toml and setup.py are in sync, but it never runs the actual comparison logic.

Prompt for agents
In sdk/python/tests/unit/test_dependency_sync.py, line 12 sets ROOT = pathlib.Path(__file__).parent which resolves to sdk/python/tests/unit/. This directory contains neither pyproject.toml nor setup.py. The actual pyproject.toml is at the repository root, and setup.py is also at the repository root. Change ROOT to point to the repository root. For example, since the test file is at sdk/python/tests/unit/test_dependency_sync.py (4 levels deep from the repo root), you could use: ROOT = pathlib.Path(__file__).resolve().parents[4]. Alternatively, you could walk up the directory tree until you find a directory containing both pyproject.toml and setup.py.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.



def normalize(dep_list):
normalized = set()
for dep in dep_list:
try:
req = Requirement(dep)
normalized.add(str(req))
except Exception:
normalized.add(dep.strip())
return normalized


# -------------------------------------------------
# Parsing pyproject.toml
# -------------------------------------------------
def parse_pyproject():
pyproject_path = ROOT / "pyproject.toml"

if not pyproject_path.exists():
return set(), {}

with pyproject_path.open("rb") as f:
data = tomllib.load(f)

core = set()
optional = {}


if "project" in data:
project = data["project"]
core = set(project.get("dependencies", []))
optional = project.get("optional-dependencies", {})


elif "tool" in data and "poetry" in data["tool"]:
poetry = data["tool"]["poetry"]
deps = poetry.get("dependencies", {})

for name, version in deps.items():
if name == "python":
continue
if isinstance(version, str):
core.add(f"{name}{version}")
else:
core.add(name)

optional = poetry.get("extras", {})

return normalize(core), {k: normalize(v) for k, v in optional.items()}


def extract_list(node):
if isinstance(node, ast.List):
return {
elt.value for elt in node.elts
if isinstance(elt, ast.Constant)
}
return set()


def extract_dict(node):
result = {}
if isinstance(node, ast.Dict):
for k, v in zip(node.keys, node.values):
if isinstance(k, ast.Constant):
result[k.value] = extract_list(v)
return result


def parse_setup():
setup_path = ROOT / "setup.py"

if not setup_path.exists():
return set(), {}

tree = ast.parse(setup_path.read_text())

variables = {}

for node in tree.body:
if isinstance(node, ast.Assign):
if isinstance(node.targets[0], ast.Name):
name = node.targets[0].id
value = node.value

if isinstance(value, ast.List):
variables[name] = extract_list(value)

elif isinstance(value, ast.Dict):
variables[name] = extract_dict(value)

install_requires = set()
extras_require = {}

for node in ast.walk(tree):
if isinstance(node, ast.Call) and getattr(node.func, "id", "") == "setup":
for keyword in node.keywords:

if keyword.arg == "install_requires":
if isinstance(keyword.value, ast.List):
install_requires = extract_list(keyword.value)
elif isinstance(keyword.value, ast.Name):
install_requires = variables.get(
keyword.value.id, set()
)

if keyword.arg == "extras_require":
if isinstance(keyword.value, ast.Dict):
extras_require = extract_dict(keyword.value)
elif isinstance(keyword.value, ast.Name):
extras_require = variables.get(
keyword.value.id, {}
)

return normalize(install_requires), {
k: normalize(v) for k, v in extras_require.items()
}


# -------------------------------------------------
# Test
# -------------------------------------------------
def test_dependencies_in_sync():
py_core, py_optional = parse_pyproject()

# If pyproject does not define dependencies, skip test
# Manually Checked the package dependencies are not present
if not py_core and not py_optional:
pytest.skip("pyproject.toml does not define dependencies")

setup_core, setup_optional = parse_setup()

core_missing = py_core - setup_core
core_extra = setup_core - py_core

assert not core_missing and not core_extra, (
f"\nCore dependency mismatch:\n"
f"Missing in setup.py: {core_missing}\n"
f"Extra in setup.py: {core_extra}"
)

py_groups = set(py_optional.keys())
setup_groups = set(setup_optional.keys())

assert py_groups == setup_groups, (
f"\nOptional group mismatch:\n"
f"Only in pyproject.toml: {py_groups - setup_groups}\n"
f"Only in setup.py: {setup_groups - py_groups}"
)

for group in py_groups:
missing = py_optional[group] - setup_optional[group]
extra = setup_optional[group] - py_optional[group]

assert not missing and not extra, (
f"\nMismatch in optional group '{group}':\n"
f"Missing in setup.py: {missing}\n"
f"Extra in setup.py: {extra}"
)

Loading