Skip to content

Commit 30088a7

Browse files
committed
Implement machinery for writing unit tests of the resolver
1 parent 07e2d52 commit 30088a7

File tree

5 files changed

+199
-2
lines changed

5 files changed

+199
-2
lines changed

murek/src/core/manifest/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ impl Summary {
5353
}
5454

5555
/// Subset of a [`Manifest`] that contains package metadata.
56-
#[derive(Clone, Debug, Serialize, Deserialize)]
56+
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
5757
pub struct ManifestMetadata {
5858
pub authors: Option<Vec<String>>,
5959
pub urls: Option<BTreeMap<String, String>>,

murek/src/core/package/id.rs

+31
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,37 @@ impl PackageId {
4444
};
4545
Self(CACHE.intern(inner))
4646
}
47+
48+
#[cfg(test)]
49+
pub(crate) fn from_display_str(string: &str) -> Result<Self> {
50+
use anyhow::{anyhow, bail};
51+
52+
let mut s = string.splitn(3, ' ');
53+
54+
let name = s.next().unwrap().into();
55+
56+
let Some(version) = s.next() else {
57+
bail!("invalid displayed PackageId: missing version");
58+
};
59+
let Some(version) = version.strip_prefix('v') else {
60+
bail!("invalid displayed PackageId: version does not start with letter `v`");
61+
};
62+
let version = version
63+
.to_version()
64+
.map_err(|err| anyhow!("invalid displayed PackageId: {}", err))?;
65+
66+
let Some(source_id) = s.next() else {
67+
todo!("Default SourceId is not implemented yet.")
68+
};
69+
let source_id = if source_id.starts_with('(') && source_id.ends_with(')') {
70+
&source_id[1..source_id.len() - 1]
71+
} else {
72+
bail!("invalid displayed PackageId: source url is not wrapped with parentheses",);
73+
};
74+
let source_id = SourceId::from_display_str(source_id)?;
75+
76+
Ok(PackageId::pure(name, version, source_id))
77+
}
4778
}
4879

4980
impl Deref for PackageId {

murek/src/core/registry/mod.rs

+156
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,159 @@ pub trait Registry {
1414
/// Fetch full package by its ID.
1515
async fn download(&mut self, package_id: PackageId) -> Result<Package>;
1616
}
17+
18+
#[cfg(test)]
19+
pub(crate) mod mock {
20+
use std::collections::hash_map::Entry;
21+
use std::collections::{HashMap, HashSet};
22+
use std::path::PathBuf;
23+
24+
use anyhow::{anyhow, bail, Result};
25+
use async_trait::async_trait;
26+
use itertools::Itertools;
27+
28+
use crate::core::package::PackageName;
29+
use crate::core::registry::Registry;
30+
use crate::core::{Manifest, ManifestDependency, Package, PackageId, SourceId, Summary};
31+
32+
#[derive(Clone, Debug, Default)]
33+
pub struct MockRegistry {
34+
index: HashMap<(PackageName, SourceId), HashSet<PackageId>>,
35+
dependencies: HashMap<PackageId, Vec<ManifestDependency>>,
36+
packages: HashMap<PackageId, Package>,
37+
}
38+
39+
impl MockRegistry {
40+
pub fn new() -> Self {
41+
Default::default()
42+
}
43+
44+
pub fn put(&mut self, package_id: PackageId, mut dependencies: Vec<ManifestDependency>) {
45+
assert!(
46+
!self.has_package(package_id),
47+
"Package {} is already in registry",
48+
package_id
49+
);
50+
51+
self.index
52+
.entry((package_id.name.clone(), package_id.source_id))
53+
.or_default()
54+
.insert(package_id);
55+
56+
self.dependencies
57+
.entry(package_id)
58+
.or_default()
59+
.append(&mut dependencies);
60+
}
61+
62+
pub fn has_package(&self, package_id: PackageId) -> bool {
63+
self.dependencies.contains_key(&package_id)
64+
}
65+
66+
pub fn get_package(&mut self, package_id: PackageId) -> Result<Package> {
67+
if !self.has_package(package_id) {
68+
bail!("MockRegistry/get_package: unknown package {package_id}");
69+
}
70+
71+
match self.packages.entry(package_id) {
72+
Entry::Occupied(entry) => Ok(entry.get().clone()),
73+
Entry::Vacant(entry) => {
74+
let summary = Summary::new(package_id, self.dependencies[&package_id].clone());
75+
let manifest = Box::new(Manifest {
76+
summary,
77+
metadata: Default::default(),
78+
});
79+
let package = Package::new(package_id, PathBuf::new(), manifest);
80+
entry.insert(package.clone());
81+
Ok(package)
82+
}
83+
}
84+
}
85+
}
86+
87+
#[async_trait(?Send)]
88+
impl Registry for MockRegistry {
89+
async fn query(&mut self, dependency: &ManifestDependency) -> Result<Vec<Summary>> {
90+
Ok(self
91+
.index
92+
.get(&(dependency.name.clone(), dependency.source_id))
93+
.ok_or_else(|| {
94+
anyhow!(
95+
"MockRegistry/query: unknown package {} ({})",
96+
dependency.name,
97+
dependency.source_id
98+
)
99+
})?
100+
.iter()
101+
.copied()
102+
.filter(|id| dependency.version_req.matches(&id.version))
103+
.sorted_unstable_by(|a, b| b.version.cmp(&a.version))
104+
.map(|id| self.get_package(id).unwrap().manifest.summary.clone())
105+
.collect())
106+
}
107+
108+
async fn download(&mut self, package_id: PackageId) -> Result<Package> {
109+
self.get_package(package_id)
110+
}
111+
}
112+
113+
macro_rules! registry {
114+
[$($x:tt),* $(,)?] => {{
115+
#[allow(unused_imports)]
116+
use $crate::core::registry::mock;
117+
#[allow(unused_mut)]
118+
let mut registry = mock::MockRegistry::new();
119+
$({
120+
let (package_id, dependencies) = mock::registry_entry!($x);
121+
registry.put(package_id, dependencies);
122+
})*
123+
registry
124+
}};
125+
}
126+
127+
pub(crate) use registry;
128+
129+
macro_rules! registry_entry {
130+
(($p:literal, [ $($d:tt),* $(,)? ] $(,)?)) => {{
131+
#[allow(unused_imports)]
132+
use $crate::core::registry::mock;
133+
let package_id = $crate::core::PackageId::from_display_str($p).unwrap();
134+
let dependencies = mock::deps![$($d),*].iter().cloned().collect();
135+
(package_id, dependencies)
136+
}};
137+
}
138+
139+
pub(crate) use registry_entry;
140+
141+
macro_rules! deps {
142+
[$($x:tt),* $(,)?] => (
143+
&[
144+
$($crate::core::registry::mock::dep!($x)),*
145+
]
146+
);
147+
}
148+
149+
pub(crate) use deps;
150+
151+
macro_rules! dep {
152+
(($n:literal, $v:literal, $s:literal)) => {
153+
$crate::core::ManifestDependency {
154+
name: ($n).into(),
155+
version_req: ::semver::VersionReq::parse($v).unwrap(),
156+
source_id: $crate::core::SourceId::from_display_str($s).unwrap(),
157+
}
158+
};
159+
}
160+
161+
pub(crate) use dep;
162+
163+
macro_rules! pkgs {
164+
[$($x:expr),* $(,)?] => (
165+
&[
166+
$($crate::core::PackageId::from_display_str($x).unwrap()),*
167+
]
168+
);
169+
}
170+
171+
pub(crate) use pkgs;
172+
}

murek/src/core/resolver.rs

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::core::PackageId;
99
/// Represents a fully-resolved package dependency graph.
1010
///
1111
/// Each node in the graph is a package and edges represent dependencies between packages.
12+
#[derive(Debug)]
1213
pub struct Resolve {
1314
/// Directional graph representing package dependencies.
1415
///

murek/src/core/source/id.rs

+10-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use url::Url;
1010
use crate::core::source::Source;
1111
use crate::core::Config;
1212
use crate::internal::static_hash_cache::StaticHashCache;
13-
use crate::sources::PathSource;
1413

1514
/// Unique identifier for a source of packages.
1615
///
@@ -153,8 +152,18 @@ impl SourceId {
153152
}
154153
}
155154

155+
#[cfg(test)]
156+
pub(crate) fn from_display_str(string: &str) -> Result<Self> {
157+
if string.starts_with('/') {
158+
Self::for_path(&PathBuf::try_from(string)?)
159+
} else {
160+
Self::from_pretty_url(string)
161+
}
162+
}
163+
156164
/// Creates an implementation of `Source` corresponding to this ID.
157165
pub fn load<'c>(self, config: &'c Config) -> Result<Box<dyn Source + 'c>> {
166+
use crate::sources::*;
158167
match self.kind {
159168
SourceKind::Path => Ok(Box::new(PathSource::new(self, config))),
160169
SourceKind::Git(_) => todo!("Git sources are not implemented yet"),

0 commit comments

Comments
 (0)