Skip to content

Commit

Permalink
test: for local_environment_matches_spec (#2093)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hofer-Julian authored Sep 20, 2024
1 parent ebda8b1 commit 6358be9
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 29 deletions.
188 changes: 159 additions & 29 deletions src/global/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,9 +498,15 @@ pub(crate) async fn sync(config: &Config, assume_yes: bool) -> Result<(), miette
let env_dir = EnvDir::new(env_root.clone(), env_name.clone()).await?;
let prefix = Prefix::new(env_dir.path());

let prefix_records = prefix.find_installed_packages(Some(50)).await?;
let repodata_records = prefix
.find_installed_packages(Some(50))
.await?
.into_iter()
.map(|r| r.repodata_record)
.collect_vec();

if !specs_match_local_environment(&specs, prefix_records, parsed_environment.platform()) {
if !local_environment_matches_spec(repodata_records, &specs, parsed_environment.platform())
{
install_environment(
&specs,
&parsed_environment,
Expand Down Expand Up @@ -530,58 +536,57 @@ pub(crate) async fn sync(config: &Config, assume_yes: bool) -> Result<(), miette
/// This function verifies that all the given specifications are present in the
/// local environment's prefix records and that there are no extra entries in
/// the prefix records that do not match any of the specifications.
fn specs_match_local_environment<T: AsRef<RepoDataRecord>>(
fn local_environment_matches_spec(
prefix_records: Vec<RepoDataRecord>,
specs: &IndexMap<PackageName, MatchSpec>,
prefix_records: Vec<T>,
platform: Option<Platform>,
) -> bool {
// Check whether all specs in the manifest are present in the installed
// environment
let specs_in_manifest_are_present = specs.values().all(|spec| {
prefix_records
.iter()
.any(|record| spec.matches(record.as_ref()))
});
let specs_in_manifest_are_present = specs
.values()
.all(|spec| prefix_records.iter().any(|record| spec.matches(record)));

if !specs_in_manifest_are_present {
return false;
}

// Check whether all packages in the installed environment have the correct
// platform
let platform_specs_match_env = prefix_records.iter().all(|record| {
let Ok(package_platform) = Platform::from_str(&record.as_ref().package_record.subdir)
else {
return true;
};
if let Some(platform) = platform {
let platform_specs_match_env = prefix_records.iter().all(|record| {
let Ok(package_platform) = Platform::from_str(&record.package_record.subdir) else {
return true;
};

match package_platform {
Platform::NoArch => true,
p if p == platform => true,
_ => false,
}
});

match package_platform {
Platform::NoArch => true,
p if Some(p) == platform => true,
_ => false,
if !platform_specs_match_env {
return false;
}
});

if !platform_specs_match_env {
return false;
}

fn prune_dependencies<T: AsRef<RepoDataRecord>>(
mut remaining_prefix_records: Vec<T>,
matched_record: &T,
) -> Vec<T> {
fn prune_dependencies(
mut remaining_prefix_records: Vec<RepoDataRecord>,
matched_record: &RepoDataRecord,
) -> Vec<RepoDataRecord> {
let mut work_queue = Vec::from([matched_record.as_ref().clone()]);

while let Some(current_record) = work_queue.pop() {
let dependencies = &current_record.as_ref().depends;
let dependencies = &current_record.depends;
for dependency in dependencies {
let Ok(match_spec) = MatchSpec::from_str(dependency, ParseStrictness::Lenient)
else {
continue;
};
let Some(index) = remaining_prefix_records
.iter()
.position(|record| match_spec.matches(&record.as_ref().package_record))
.position(|record| match_spec.matches(&record.package_record))
else {
continue;
};
Expand All @@ -597,7 +602,7 @@ fn specs_match_local_environment<T: AsRef<RepoDataRecord>>(
// Process each spec and remove matched entries and their dependencies
let remaining_prefix_records = specs.iter().fold(prefix_records, |mut acc, (name, spec)| {
let Some(index) = acc.iter().position(|record| {
record.as_ref().package_record.name == *name && spec.matches(record.as_ref())
record.package_record.name == *name && spec.matches(record.as_ref())
}) else {
return acc;
};
Expand All @@ -609,3 +614,128 @@ fn specs_match_local_environment<T: AsRef<RepoDataRecord>>(
// the environment doesn't contain records that don't match the manifest
remaining_prefix_records.is_empty()
}

#[cfg(test)]
mod tests {
use indexmap::IndexMap;
use rattler_conda_types::{MatchSpec, PackageName, ParseStrictness, Platform};
use rattler_lock::LockFile;
use rstest::{fixture, rstest};

use super::*;

#[fixture]
fn ripgrep_specs() -> IndexMap<PackageName, MatchSpec> {
IndexMap::from([(
PackageName::from_str("ripgrep").unwrap(),
MatchSpec::from_str("ripgrep=14.1.0", ParseStrictness::Strict).unwrap(),
)])
}

#[fixture]
fn ripgrep_records() -> Vec<RepoDataRecord> {
LockFile::from_str(include_str!("./test_data/lockfiles/ripgrep.lock"))
.unwrap()
.default_environment()
.unwrap()
.conda_repodata_records_for_platform(Platform::Linux64)
.unwrap()
.unwrap()
}

#[fixture]
fn ripgrep_bat_specs() -> IndexMap<PackageName, MatchSpec> {
IndexMap::from([
(
PackageName::from_str("ripgrep").unwrap(),
MatchSpec::from_str("ripgrep=14.1.0", ParseStrictness::Strict).unwrap(),
),
(
PackageName::from_str("bat").unwrap(),
MatchSpec::from_str("bat=0.24.0", ParseStrictness::Strict).unwrap(),
),
])
}

#[fixture]
fn ripgrep_bat_records() -> Vec<RepoDataRecord> {
LockFile::from_str(include_str!("./test_data/lockfiles/ripgrep_bat.lock"))
.unwrap()
.default_environment()
.unwrap()
.conda_repodata_records_for_platform(Platform::Linux64)
.unwrap()
.unwrap()
}

#[rstest]
fn test_local_environment_matches_spec(
ripgrep_records: Vec<RepoDataRecord>,
ripgrep_specs: IndexMap<PackageName, MatchSpec>,
) {
assert!(local_environment_matches_spec(
ripgrep_records,
&ripgrep_specs,
None
));
}

#[rstest]
fn test_local_environment_misses_entries_for_specs(
mut ripgrep_records: Vec<RepoDataRecord>,
ripgrep_specs: IndexMap<PackageName, MatchSpec>,
) {
// Remove last repodata record
ripgrep_records.pop();

assert!(!local_environment_matches_spec(
ripgrep_records,
&ripgrep_specs,
None
));
}

#[rstest]
fn test_local_environment_has_too_many_entries_to_match_spec(
ripgrep_bat_records: Vec<RepoDataRecord>,
ripgrep_specs: IndexMap<PackageName, MatchSpec>,
ripgrep_bat_specs: IndexMap<PackageName, MatchSpec>,
) {
assert!(!local_environment_matches_spec(
ripgrep_bat_records.clone(),
&ripgrep_specs,
None
), "The function needs to detect that records coming from ripgrep and bat don't match ripgrep alone.");

assert!(
local_environment_matches_spec(ripgrep_bat_records, &ripgrep_bat_specs, None),
"The records and specs match and the function should return `true`."
);
}

#[rstest]
fn test_local_environment_matches_given_platform(
ripgrep_records: Vec<RepoDataRecord>,
ripgrep_specs: IndexMap<PackageName, MatchSpec>,
) {
assert!(
local_environment_matches_spec(
ripgrep_records,
&ripgrep_specs,
Some(Platform::Linux64)
),
"The records contains only linux-64 entries"
);
}

#[rstest]
fn test_local_environment_doesnt_match_given_platform(
ripgrep_records: Vec<RepoDataRecord>,
ripgrep_specs: IndexMap<PackageName, MatchSpec>,
) {
assert!(
!local_environment_matches_spec(ripgrep_records, &ripgrep_specs, Some(Platform::Win64),),
"The record contains linux-64 entries, so the function should always return `false`"
);
}
}
106 changes: 106 additions & 0 deletions src/global/test_data/lockfiles/ripgrep.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
version: 5
environments:
default:
channels:
- url: https://conda.anaconda.org/conda-forge/
packages:
linux-64:
- conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2
- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda
- conda: https://conda.anaconda.org/conda-forge/linux-64/ripgrep-14.1.0-he8a937b_0.conda
packages:
- kind: conda
name: _libgcc_mutex
version: '0.1'
build: conda_forge
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2
sha256: fe51de6107f9edc7aa4f786a70f4a883943bc9d39b3bb7307c04c41410990726
md5: d7c89558ba9fa0495403155b64376d81
license: None
size: 2562
timestamp: 1578324546067
- kind: conda
name: _openmp_mutex
version: '4.5'
build: 2_gnu
build_number: 16
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2
sha256: fbe2c5e56a653bebb982eda4876a9178aedfc2b545f25d0ce9c4c0b508253d22
md5: 73aaf86a425cc6e73fcf236a5a46396d
depends:
- _libgcc_mutex 0.1 conda_forge
- libgomp >=7.5.0
constrains:
- openmp_impl 9999
license: BSD-3-Clause
license_family: BSD
size: 23621
timestamp: 1650670423406
- kind: conda
name: libgcc
version: 14.1.0
build: h77fa898_1
build_number: 1
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-14.1.0-h77fa898_1.conda
sha256: 10fa74b69266a2be7b96db881e18fa62cfa03082b65231e8d652e897c4b335a3
md5: 002ef4463dd1e2b44a94a4ace468f5d2
depends:
- _libgcc_mutex 0.1 conda_forge
- _openmp_mutex >=4.5
constrains:
- libgomp 14.1.0 h77fa898_1
- libgcc-ng ==14.1.0=*_1
license: GPL-3.0-only WITH GCC-exception-3.1
license_family: GPL
size: 846380
timestamp: 1724801836552
- kind: conda
name: libgcc-ng
version: 14.1.0
build: h69a702a_1
build_number: 1
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-14.1.0-h69a702a_1.conda
sha256: b91f7021e14c3d5c840fbf0dc75370d6e1f7c7ff4482220940eaafb9c64613b7
md5: 1efc0ad219877a73ef977af7dbb51f17
depends:
- libgcc 14.1.0 h77fa898_1
license: GPL-3.0-only WITH GCC-exception-3.1
license_family: GPL
size: 52170
timestamp: 1724801842101
- kind: conda
name: libgomp
version: 14.1.0
build: h77fa898_1
build_number: 1
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/libgomp-14.1.0-h77fa898_1.conda
sha256: c96724c8ae4ee61af7674c5d9e5a3fbcf6cd887a40ad5a52c99aa36f1d4f9680
md5: 23c255b008c4f2ae008f81edcabaca89
depends:
- _libgcc_mutex 0.1 conda_forge
license: GPL-3.0-only WITH GCC-exception-3.1
license_family: GPL
size: 460218
timestamp: 1724801743478
- kind: conda
name: ripgrep
version: 14.1.0
build: he8a937b_0
subdir: linux-64
url: https://conda.anaconda.org/conda-forge/linux-64/ripgrep-14.1.0-he8a937b_0.conda
sha256: 4fcf37724b87440765cb3c6cf573e99d12fc631001426a0309d132f495c3d62a
md5: 5a476f7033a8a1b9175626b5ebf86d1d
depends:
- libgcc-ng >=12
license: MIT
license_family: MIT
size: 1683808
timestamp: 1705520837423
Loading

0 comments on commit 6358be9

Please sign in to comment.