Skip to content
Merged
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
37 changes: 37 additions & 0 deletions pallet_patcher/search.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,50 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
# Licensed under the Apache License, Version 2.0

from collections import defaultdict
import os
from pathlib import Path

from pallet_patcher.manifest import get_dependencies
from pallet_patcher.manifest import load_manifest


def _get_available_crates(search_path):
"""
Create a list of crates available from a directory.

:param search_path: Local registry source to search for packages
:type search_path: Path

:returns: Collection of pkgs available in a directory and their versions
:rtype: dict

:returns: Collection of the directory and metadata information for a
specific pkgname+version
:rtype: dict
"""
versions = defaultdict(set) # Skip duplicates in versions dict
pkgs_metadata = {}

# Iterate over all the paths provided
for manifest_path in search_path.glob('*/Cargo.toml'):
manifest = load_manifest(manifest_path)
pkgname = manifest.get('package', {}).get('name')
Comment thread
Blast545 marked this conversation as resolved.
# TO-DO: In some cases, we want to crash if we can't find the package
if not pkgname:
continue
version = manifest.get('package', {}).get('version') or '0.0.0'

versions[pkgname].add(version)

# We are assuming here there won't be duplicated crates+version within
# the same search_path.
pkgs_metadata[f'{pkgname}+{version}'] = (
manifest_path.parent, manifest)
Comment thread
cottsay marked this conversation as resolved.

return versions, pkgs_metadata


def _get_reference(specification):
if not isinstance(specification, dict):
return None
Expand Down
1 change: 1 addition & 0 deletions test/packages/upper_layer/pkg-d-0.0.0/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
[package]
name = "pkg-d"
version = "0.0.0"
publish = false

[dependencies]
Expand Down
110 changes: 110 additions & 0 deletions test/test_search.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Copyright 2025 Open Source Robotics Foundation, Inc.
# Licensed under the Apache License, Version 2.0

from pathlib import Path
from unittest.mock import patch

from pallet_patcher.search import _get_available_crates
import pytest


@pytest.fixture
def mock_crates_dir(tmp_path):
"""
Create a temporary directory structure mimicking a Rust workspace.

Structure:
/tmp_dir/
├── crate_a/Cargo.toml
├── crate_b_v1/Cargo.toml
└── crate_b_v2/Cargo.toml
"""
(tmp_path / 'crate_a').mkdir()
(tmp_path / 'crate_a' / 'Cargo.toml').touch()

(tmp_path / 'crate_b_v1').mkdir()
(tmp_path / 'crate_b_v1' / 'Cargo.toml').touch()

(tmp_path / 'crate_b_v2').mkdir()
(tmp_path / 'crate_b_v2' / 'Cargo.toml').touch()

return tmp_path


def test_get_available_crates_structure(mock_crates_dir):
"""Tests that the function correctly parses a directory of crates."""
# We define what load_manifest should return based on the path it receives
def manifest_side_effect(path):
str_path = str(path)
if 'crate_a' in str_path:
return {'package': {'name': 'alpha-lib', 'version': '0.1.0'}}
elif 'crate_b_v1' in str_path:
return {'package': {'name': 'beta-lib', 'version': '1.0.0'}}
elif 'crate_b_v2' in str_path:
return {'package': {'name': 'beta-lib', 'version': '2.0.0'}}
return {}

# Patch 'load_manifest' in the scope where _get_available_crates is defined
with patch('pallet_patcher.search.load_manifest',
side_effect=manifest_side_effect):
versions, metadata = _get_available_crates(mock_crates_dir)

assert 'alpha-lib' in versions
assert 'beta-lib' in versions

assert versions['alpha-lib'] == {'0.1.0'}
assert versions['beta-lib'] == {'1.0.0', '2.0.0'}

key_alpha = 'alpha-lib+0.1.0'
key_beta_1 = 'beta-lib+1.0.0'
key_beta_2 = 'beta-lib+2.0.0'

assert key_alpha in metadata
assert key_beta_1 in metadata
assert key_beta_2 in metadata

# Check content (Path, Manifest)
# Note: metadata values are (parent_path, manifest_dict)

assert metadata[key_alpha][0].name == 'crate_a'
assert metadata[key_alpha][1]['package']['name'] == 'alpha-lib'

assert metadata[key_beta_1][0].name == 'crate_b_v1'
assert metadata[key_beta_1][1]['package']['name'] == 'beta-lib'

assert metadata[key_beta_2][0].name == 'crate_b_v2'
assert metadata[key_beta_2][1]['package']['name'] == 'beta-lib'


def test_get_available_crates_empty(tmp_path):
"""Tests behavior when directory is empty or has no Cargo.toml files."""
versions, metadata = _get_available_crates(tmp_path)
assert not versions
assert not metadata


def test_real_folder_integration():
real_packages_path = Path(__file__).parent / 'packages' / 'upper_layer'

if not real_packages_path.exists():
pytest.skip('Skipping: "packages" directory not found locally.')

versions, metadata = _get_available_crates(real_packages_path)

assert 'pkg-d' in versions
assert 'pkg-e' in versions

assert versions['pkg-d'] == {'0.0.0'}
assert versions['pkg-e'] == {'0.0.0'}

key_d = 'pkg-d+0.0.0'
key_e = 'pkg-e+0.0.0'

assert key_d in metadata
assert key_e in metadata

assert metadata[key_d][0].name == 'pkg-d-0.0.0'
assert metadata[key_d][1]['package']['name'] == 'pkg-d'

assert metadata[key_e][0].name == 'pkg-e-0.0.0'
assert metadata[key_e][1]['package']['name'] == 'pkg-e'
Loading