Skip to content

Commit 4e4a3dc

Browse files
committed
refactor: add which method to Worker trait
Before this change, the `which` crate was used inside the experiments themselves to lookup the location of binaries in the $PATH of the target system. This worked ok, but left the test suite susceptible to the $PATH of the machine running the tests. In this commit, a `which` method is added to the `Worker` trait. In the real-world `System` impl, this is a simple call again to the `which` crate. In the `MockSystem`, provision has been added to indicate that certain mocked files should be returned when the `MockSystem::which` function is called.
1 parent ca95567 commit 4e4a3dc

File tree

5 files changed

+53
-35
lines changed

5 files changed

+53
-35
lines changed

src/experiments/sudors.rs

+10-11
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::utils::Worker;
22
use anyhow::Result;
33
use std::path::{Path, PathBuf};
44
use tracing::info;
5-
use which::which;
65

76
const PACKAGE: &str = "sudo-rs";
87
const FIRST_SUPPORTED_RELEASE: &str = "24.04";
@@ -49,8 +48,8 @@ impl<'a> SudoRsExperiment<'a> {
4948
self.system.install_package(PACKAGE)?;
5049

5150
for f in Self::sudors_files() {
52-
let filename = f.file_name().unwrap();
53-
let existing = match which(filename) {
51+
let filename = f.file_name().unwrap().to_str().unwrap();
52+
let existing = match self.system.which(filename) {
5453
Ok(path) => path,
5554
Err(_) => Path::new("/usr/bin").join(filename),
5655
};
@@ -63,8 +62,8 @@ impl<'a> SudoRsExperiment<'a> {
6362
/// Disable the experiment by removing the package and restoring the original files.
6463
pub fn disable(&self) -> Result<()> {
6564
for f in Self::sudors_files() {
66-
let filename = f.file_name().unwrap();
67-
let existing = match which(filename) {
65+
let filename = f.file_name().unwrap().to_str().unwrap();
66+
let existing = match self.system.which(filename) {
6867
Ok(path) => path,
6968
Err(_) => Path::new("/usr/bin").join(filename),
7069
};
@@ -168,12 +167,12 @@ mod tests {
168167
fn sudors_compatible_runner() -> MockSystem {
169168
let runner = MockSystem::default();
170169
runner.mock_files(vec![
171-
("/usr/lib/cargo/bin/sudo", ""),
172-
("/usr/lib/cargo/bin/su", ""),
173-
("/usr/lib/cargo/bin/visudo", ""),
174-
("/usr/bin/sudo", ""),
175-
("/usr/bin/su", ""),
176-
("/usr/sbin/visudo", ""),
170+
("/usr/lib/cargo/bin/sudo", "", false),
171+
("/usr/lib/cargo/bin/su", "", false),
172+
("/usr/lib/cargo/bin/visudo", "", false),
173+
("/usr/bin/sudo", "", true),
174+
("/usr/bin/su", "", true),
175+
("/usr/sbin/visudo", "", true),
177176
]);
178177
runner
179178
}

src/experiments/uutils.rs

+12-13
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::utils::Worker;
22
use anyhow::Result;
33
use std::path::{Path, PathBuf};
44
use tracing::info;
5-
use which::which;
65

76
/// An experiment to install and configure a Rust-based replacement for a system utility.
87
pub struct UutilsExperiment<'a> {
@@ -66,8 +65,8 @@ impl<'a> UutilsExperiment<'a> {
6665
let files = self.system.list_files(self.bin_directory.clone())?;
6766

6867
for f in files {
69-
let filename = f.file_name().unwrap();
70-
let existing = match which(filename) {
68+
let filename = f.file_name().unwrap().to_str().unwrap();
69+
let existing = match self.system.which(filename) {
7170
Ok(path) => path,
7271
Err(_) => Path::new("/usr/bin").join(filename),
7372
};
@@ -88,8 +87,8 @@ impl<'a> UutilsExperiment<'a> {
8887
let files = self.system.list_files(self.bin_directory.clone())?;
8988

9089
for f in files {
91-
let filename = f.file_name().unwrap();
92-
let existing = match which(filename) {
90+
let filename = f.file_name().unwrap().to_str().unwrap();
91+
let existing = match self.system.which(filename) {
9392
Ok(path) => path,
9493
Err(_) => Path::new("/usr/bin").join(filename),
9594
};
@@ -209,10 +208,10 @@ mod tests {
209208
fn coreutils_compatible_runner() -> MockSystem {
210209
let runner = MockSystem::default();
211210
runner.mock_files(vec![
212-
("/usr/lib/cargo/bin/coreutils/date", ""),
213-
("/usr/lib/cargo/bin/coreutils/sort", ""),
214-
("/usr/bin/sort", ""),
215-
("/usr/bin/date", ""),
211+
("/usr/lib/cargo/bin/coreutils/date", "", false),
212+
("/usr/lib/cargo/bin/coreutils/sort", "", false),
213+
("/usr/bin/sort", "", true),
214+
("/usr/bin/date", "", true),
216215
]);
217216
runner
218217
}
@@ -231,10 +230,10 @@ mod tests {
231230
fn findutils_compatible_runner() -> MockSystem {
232231
let runner = MockSystem::default();
233232
runner.mock_files(vec![
234-
("/usr/lib/cargo/bin/findutils/find", ""),
235-
("/usr/lib/cargo/bin/findutils/xargs", ""),
236-
("/usr/bin/find", ""),
237-
("/usr/bin/xargs", ""),
233+
("/usr/lib/cargo/bin/findutils/find", "", false),
234+
("/usr/lib/cargo/bin/findutils/xargs", "", false),
235+
("/usr/bin/find", "", true),
236+
("/usr/bin/xargs", "", true),
238237
]);
239238
runner
240239
}

src/main.rs

+5-5
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ fn main() -> Result<()> {
125125
}
126126

127127
/// Enables selected experiments
128-
fn enable<'a>(system: &'a impl Worker, experiments: Vec<Experiment>, yes: bool) -> Result<()> {
128+
fn enable(system: &impl Worker, experiments: Vec<Experiment>, yes: bool) -> Result<()> {
129129
confirm_or_exit(yes);
130130

131131
info!("Updating apt package cache");
@@ -147,17 +147,17 @@ fn disable(experiments: Vec<Experiment<'_>>, yes: bool) -> Result<()> {
147147
}
148148

149149
/// Get selected experiments from the command line arguments.
150-
fn selected_experiments<'a>(
150+
fn selected_experiments(
151151
all: bool,
152152
selected: Vec<String>,
153-
system: &'a impl Worker,
154-
) -> Vec<Experiment<'a>> {
153+
system: &impl Worker,
154+
) -> Vec<Experiment<'_>> {
155155
let all_experiments = all_experiments(system);
156156
let default_experiments = default_experiments();
157157

158158
match all {
159159
true => {
160-
if selected.len() > 0 && !vecs_eq(selected, default_experiments) {
160+
if !selected.is_empty() && !vecs_eq(selected, default_experiments) {
161161
warn!("Ignoring --experiments flag as --all is set");
162162
}
163163

src/utils/worker.rs

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::{
66
use anyhow::Result;
77
use std::fs;
88
use tracing::{debug, trace, warn};
9+
use which::which;
910

1011
use super::{Command, Distribution};
1112

@@ -30,6 +31,9 @@ pub trait Worker {
3031
/// List files in a directory, returning an error if the directory does not exist.
3132
fn list_files(&self, directory: PathBuf) -> Result<Vec<PathBuf>>;
3233

34+
/// Find the path to a binary in the system's PATH.
35+
fn which(&self, binary_name: &str) -> Result<PathBuf>;
36+
3337
/// Install a package using the system package manager.
3438
fn install_package(&self, package: &str) -> Result<()> {
3539
let cmd = Command::build("apt-get", &["install", "-y", package]);
@@ -122,6 +126,11 @@ impl Worker for System {
122126
Ok(files)
123127
}
124128

129+
/// Find the path to a binary in the system's PATH.
130+
fn which(&self, binary_name: &str) -> Result<PathBuf> {
131+
Ok(which(binary_name)?)
132+
}
133+
125134
/// Replace a file with a symlink. If the target file already exists, it will be backed up
126135
/// before being replaced.
127136
fn replace_file_with_symlink(&self, source: PathBuf, target: PathBuf) -> Result<()> {

src/utils/worker_mock.rs

+17-6
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ pub mod tests {
99
pub struct MockSystem {
1010
/// Tracks the commands executed by the Worker
1111
pub commands: RefCell<Vec<String>>,
12-
/// Mock files that the Worker's file-related methods can see/act upon
13-
pub files: RefCell<HashMap<PathBuf, String>>,
12+
/// Mock files that the Worker's file-related methods can see/act upon.
13+
/// The String is the contents, and the bool indicates if this file takes
14+
/// priority in the simulated $PATH, and returned by the `MockSystem::which` function.
15+
pub files: RefCell<HashMap<PathBuf, (String, bool)>>,
1416
/// A list of packages that should report as "installed" on the mock system
1517
pub installed_packages: RefCell<Vec<String>>,
1618
/// List of symlinks created by the worker
@@ -46,14 +48,14 @@ pub mod tests {
4648

4749
s.mock_command("lsb_release -is", distribution.id.as_str());
4850
s.mock_command("lsb_release -rs", distribution.release.as_str());
49-
return s;
51+
s
5052
}
5153

52-
pub fn mock_files(&self, files: Vec<(&str, &str)>) {
53-
for (path, contents) in files {
54+
pub fn mock_files(&self, files: Vec<(&str, &str, bool)>) {
55+
for (path, contents, primary) in files {
5456
self.files
5557
.borrow_mut()
56-
.insert(PathBuf::from(path), contents.to_string());
58+
.insert(PathBuf::from(path), (contents.to_string(), primary));
5759
}
5860
}
5961

@@ -102,6 +104,15 @@ pub mod tests {
102104
Ok(files)
103105
}
104106

107+
fn which(&self, binary_name: &str) -> Result<PathBuf> {
108+
for (filename, file) in self.files.borrow().iter() {
109+
if filename.file_name().unwrap().to_str().unwrap() == binary_name && file.1 {
110+
return Ok(filename.clone());
111+
}
112+
}
113+
anyhow::bail!("{} not found in mocked filesystem", binary_name);
114+
}
115+
105116
fn replace_file_with_symlink(&self, source: PathBuf, target: PathBuf) -> Result<()> {
106117
if self.files.borrow().contains_key(&target) {
107118
self.backup_file(target.clone())?;

0 commit comments

Comments
 (0)