Skip to content
Open
Show file tree
Hide file tree
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
87 changes: 87 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,90 @@ def fake_env_name_to_prefix(
# Optionally, verify that our fake function printed the expected message.
output = capsys.readouterr().out
assert "Fake create called with" in output


def test_local_dependency_with_extras(
tmp_path: Path,
capsys: pytest.CaptureFixture,
) -> None:
"""Test that local dependencies with extras are properly installed."""
# Set up the directory structure
package1_dir = tmp_path / "package1"
my_package_dir = tmp_path / "my_package"
my_package2_dir = tmp_path / "my_package2"

package1_dir.mkdir()
my_package_dir.mkdir()
my_package2_dir.mkdir()

# Create requirements.yaml for package1
(package1_dir / "requirements.yaml").write_text(
"""
dependencies:
- common-dep
local_dependencies:
- ../my_package[my-extra]
""",
)

# Create requirements.yaml for my_package
(my_package_dir / "requirements.yaml").write_text(
"""
dependencies:
- my-package-dep
optional_dependencies:
my-extra:
- ../my_package2
""",
)

# Make my_package pip installable
(my_package_dir / "setup.py").write_text(
"""
from setuptools import setup
setup(name="my_package", version="0.1.0")
""",
)

# Create requirements.yaml for my_package2
(my_package2_dir / "requirements.yaml").write_text(
"""
dependencies:
- my-package2-dep
""",
)

# Make my_package2 pip installable
(my_package2_dir / "setup.py").write_text(
"""
from setuptools import setup
setup(name="my_package2", version="0.1.0")
""",
)

# Run the unidep install command
_install_command(
package1_dir / "requirements.yaml",
conda_executable="micromamba",
conda_env_name=None,
conda_env_prefix=None,
conda_lock_file=None,
dry_run=True, # Just print, don't execute
editable=False,
verbose=True,
)

# Check the output
captured = capsys.readouterr().out

# Expect the dependencies to be installed
assert "common-dep" in captured
assert "my-package-dep" in captured
assert "my-package2-dep" in captured

# We expect both my_package and my_package2 to be installed
assert "../my_package" in captured
assert (
f"pip install --no-deps --verbose {my_package_dir.resolve()} {my_package2_dir.resolve()}"
in captured
)
246 changes: 246 additions & 0 deletions tests/test_parse_yaml_local_dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
parse_requirements,
resolve_conflicts,
)
from unidep._dependencies_parsing import _get_local_deps_from_optional_section

from .helpers import maybe_as_toml

Expand Down Expand Up @@ -531,3 +532,248 @@ def test_parse_local_dependencies_without_local_deps_themselves(
r2.write_text("")
with pytest.raises(RuntimeError, match="is not pip installable"):
parse_local_dependencies(r1, verbose=True, raise_if_missing=True)


def test_local_dependency_with_extras(tmp_path: Path) -> None:
"""Test that local dependencies with extras are properly installed."""
# Set up the directory structure
package1_dir = tmp_path / "package1"
my_package_dir = tmp_path / "my_package"
my_package2_dir = tmp_path / "my_package2"

package1_dir.mkdir()
my_package_dir.mkdir()
my_package2_dir.mkdir()

# Create requirements.yaml for package1
(package1_dir / "requirements.yaml").write_text(
"""
dependencies:
- common-dep
local_dependencies:
- ../my_package[my-extra]
""",
)

# Create requirements.yaml for my_package
(my_package_dir / "requirements.yaml").write_text(
"""
dependencies:
- my-package-dep
optional_dependencies:
my-extra:
- ../my_package2
""",
)

# Make my_package pip installable
(my_package_dir / "setup.py").write_text(
"""
from setuptools import setup
setup(name="my_package", version="0.1.0")
""",
)

# Create requirements.yaml for my_package2
(my_package2_dir / "requirements.yaml").write_text(
"""
dependencies:
- my-package2-dep
""",
)

# Make my_package2 pip installable
(my_package2_dir / "setup.py").write_text(
"""
from setuptools import setup
setup(name="my_package2", version="0.1.0")
""",
)
local_dependencies = parse_local_dependencies(
package1_dir / "requirements.yaml",
verbose=True,
)
assert local_dependencies == {
package1_dir.absolute(): [
my_package_dir.absolute(),
my_package2_dir.absolute(),
],
}


def test_nested_extras_in_local_dependencies(
tmp_path: Path,
capsys: pytest.CaptureFixture,
) -> None:
"""Test local dependencies with nested extras chains.

main_package
-> lib_package[extra1,another-extra]
-> utility_package[extra2] (from extra1)
-> base_package (from extra2)
"""
# Create a complex dependency structure:
# main_package -> lib_package[extra1] -> utility_package[extra2] -> base_package

main_dir = tmp_path / "main_package"
lib_dir = tmp_path / "lib_package"
utility_dir = tmp_path / "utility_package"
base_dir = tmp_path / "base_package"

for dir_path in [main_dir, lib_dir, utility_dir, base_dir]:
dir_path.mkdir()
# Make all packages pip-installable
(dir_path / "setup.py").write_text(
f"""
from setuptools import setup
setup(name="{dir_path.name}", version="0.1.0")
""",
)

# Main package depends on lib_package with extra1
(main_dir / "requirements.yaml").write_text(
"""
dependencies:
- main-dependency
local_dependencies:
- ../lib_package[extra1,another-extra]
""",
)

# Lib package has optional dependency on utility_package with extra2
(lib_dir / "requirements.yaml").write_text(
"""
dependencies:
- lib-dependency
optional_dependencies:
extra1:
- lib-extra1-dependency
- ../utility_package[extra2]
another-extra:
- another-extra-dependency
""",
)

# Utility package has optional dependency on base_package
(utility_dir / "requirements.yaml").write_text(
"""
dependencies:
- utility-dependency
optional_dependencies:
extra2:
- utility-extra2-dependency
- ../base_package
other-extra:
- not-included-dependency
""",
)

# Base package has standard dependencies
(base_dir / "requirements.yaml").write_text(
"""
dependencies:
- base-dependency
""",
)

# Parse dependencies with verbose output to capture logs
local_dependencies = parse_local_dependencies(
main_dir / "requirements.yaml",
verbose=True,
)

# Capture and print the output to help with debugging
output = capsys.readouterr().out
print(output)

# Check that all packages are correctly included in dependencies
assert local_dependencies == {
main_dir.absolute(): sorted(
[
lib_dir.absolute(),
utility_dir.absolute(),
base_dir.absolute(),
],
),
}

# Verify that extras were processed correctly through verbose output
assert "Processing `../lib_package[extra1,another-extra]`" in output

yaml = YAML(typ="safe")
extras = ["extra1"]

# Test the function directly to verify non-empty nested extras
deps_from_extras = _get_local_deps_from_optional_section(
req_path=lib_dir / "requirements.yaml",
extras_list=extras,
yaml=yaml,
verbose=True,
)

# We expect a tuple with utility_package path and ["extra2"] as nested extras
assert len(deps_from_extras) == 1
path, extra, nested_extras = deps_from_extras[0]
assert extra == "../utility_package[extra2]"
assert path.name == "utility_package"
assert nested_extras == ["extra2"]

# Also test with "*" to ensure it handles all extras
all_extras_deps = _get_local_deps_from_optional_section(
req_path=lib_dir / "requirements.yaml",
extras_list=["*"],
yaml=yaml,
verbose=True,
)

# Should include dependencies from both extra1 and another-extra
assert len(all_extras_deps) == 1 # Only one is a path
assert all_extras_deps[0][0].name == "utility_package"


def test_wildcard_extras_processing(tmp_path: Path) -> None:
"""Test handling of wildcard extras."""
package_dir = tmp_path / "package"
package_dir.mkdir()

# Create a requirements file with multiple extras
(package_dir / "requirements.yaml").write_text(
"""
dependencies:
- main-dep
optional_dependencies:
extra1:
- ../dep1
extra2:
- ../dep2
extra3:
- not-a-path
""",
)

yaml = YAML(typ="safe")

# Test with wildcard
wildcard_deps = _get_local_deps_from_optional_section(
req_path=package_dir / "requirements.yaml",
extras_list=["*"],
yaml=yaml,
verbose=True,
)

# Should find both path dependencies from all extras
assert len(wildcard_deps) == 2
paths = {dep[0].name for dep in wildcard_deps}
assert paths == {"dep1", "dep2"}

# Test with specific extras
specific_deps = _get_local_deps_from_optional_section(
req_path=package_dir / "requirements.yaml",
extras_list=["extra1"],
yaml=yaml,
verbose=True,
)

# Should only find the dependency from extra1
assert len(specific_deps) == 1
assert specific_deps[0][0].name == "dep1"
Loading