Skip to content

Commit cdd9ac6

Browse files
committed
Add resolve path tests
1 parent a110e28 commit cdd9ac6

File tree

4 files changed

+85
-34
lines changed

4 files changed

+85
-34
lines changed

src/create_distrobox_dialog.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -332,13 +332,8 @@ impl CreateDistroboxDialog {
332332
#[weak]
333333
this,
334334
async move {
335-
this.root_store()
336-
.is_nvidia_host()
337-
.await
338-
.ok()
339-
.map(|is_nvidia| {
340-
this.imp().nvidia_row.set_active(is_nvidia);
341-
});
335+
let is_nvidia = this.root_store().is_nvidia_host().await;
336+
this.imp().nvidia_row.set_active(is_nvidia);
342337
}
343338
));
344339

src/distrobox/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,7 @@ impl Distrobox {
483483
let mut builder = NullCommandRunnerBuilder::new();
484484
for res in responses {
485485
for (cmd, out) in res.clone().to_commands() {
486-
builder.cmd_full(cmd, out.clone());
486+
builder.cmd_full(cmd, move || out());
487487
}
488488
}
489489
builder.build()

src/fakers/command_runner.rs

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,23 @@
22
// returning predefined outputs, to ease code testing.
33

44
use std::{
5-
collections::HashMap, future::Future, io::{self}, os::unix::process::ExitStatusExt, pin::Pin, process::ExitStatus, rc::Rc
5+
collections::HashMap,
6+
future::Future,
7+
io::{self},
8+
os::unix::process::ExitStatusExt,
9+
pin::Pin,
10+
process::ExitStatus,
11+
rc::Rc,
612
};
713

8-
use crate::fakers::{OutputTracker, Command};
14+
use crate::fakers::{Command, OutputTracker};
915

1016
use async_process::{Command as AsyncCommand, Output};
1117
use futures::{
1218
io::{AsyncRead, AsyncWrite, Cursor},
1319
FutureExt,
1420
};
1521

16-
1722
#[derive(Debug, Clone)]
1823
pub enum CommandRunnerEvent {
1924
Spawned(usize, Command),
@@ -69,7 +74,10 @@ impl CommandRunner {
6974
}
7075

7176
pub fn spawn(&self, command: Command) -> io::Result<Box<dyn Child + Send>> {
72-
self.output_tracker.push(CommandRunnerEvent::Spawned(self.event_id(), command.clone()));
77+
self.output_tracker.push(CommandRunnerEvent::Spawned(
78+
self.event_id(),
79+
command.clone(),
80+
));
7381
self.inner.spawn(command)
7482
}
7583

@@ -78,17 +86,17 @@ impl CommandRunner {
7886
command: Command,
7987
) -> Pin<Box<dyn Future<Output = io::Result<std::process::Output>>>> {
8088
let event_id = self.event_id();
81-
self.output_tracker.push(CommandRunnerEvent::Started(event_id, command.clone()));
89+
self.output_tracker
90+
.push(CommandRunnerEvent::Started(event_id, command.clone()));
8291
let fut = self.inner.output(command);
8392
let this = self.clone();
8493
fut.map(move |result| {
8594
let res_summary = match &result {
86-
Ok(_output) => {
87-
Ok(())
88-
}
95+
Ok(_output) => Ok(()),
8996
Err(_e) => Err(()),
9097
};
91-
this.output_tracker.push(CommandRunnerEvent::Output(event_id, res_summary));
98+
this.output_tracker
99+
.push(CommandRunnerEvent::Output(event_id, res_summary));
92100
result
93101
})
94102
.boxed_local()
@@ -131,8 +139,6 @@ impl InnerCommandRunner for RealCommandRunner {
131139
}
132140
}
133141

134-
135-
136142
#[derive(Default, Clone)]
137143
pub struct NullCommandRunnerBuilder {
138144
responses: HashMap<Vec<String>, Rc<dyn Fn() -> Result<String, io::Error>>>,
@@ -148,15 +154,15 @@ impl NullCommandRunnerBuilder {
148154
let mut cmd = Command::new(args[0]);
149155
cmd.args(&args[1..]);
150156
let out_text = out.as_ref().to_string();
151-
self.cmd_full(cmd, Rc::new(move || Ok(out_text.clone())))
157+
self.cmd_full(cmd, move || Ok(out_text.clone()))
152158
}
153159
pub fn cmd_full(
154160
&mut self,
155161
cmd: Command,
156-
out: Rc<dyn Fn() -> Result<String, io::Error>>,
162+
out: impl Fn() -> Result<String, io::Error> + 'static,
157163
) -> &mut Self {
158164
let key = NullCommandRunner::key_for_cmd(&cmd);
159-
self.responses.insert(key, out);
165+
self.responses.insert(key, Rc::new(out));
160166
self
161167
}
162168
pub fn fallback(&mut self, status: ExitStatus) -> &mut Self {

src/store/root_store.rs

Lines changed: 62 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ impl RootStore {
396396
})?)
397397
}
398398

399-
pub async fn is_nvidia_host(&self) -> Result<bool, distrobox::Error> {
399+
pub async fn is_nvidia_host(&self) -> bool {
400400
// uses lspci to check if the host has an NVIDIA GPU
401401
debug!("Checking if host is NVIDIA");
402402
let cmd = Command::new("lspci");
@@ -405,15 +405,27 @@ impl RootStore {
405405
Ok(output) => {
406406
let is_nvidia = output.contains("NVIDIA") || output.contains("nVidia");
407407
debug!(is_nvidia, "Checked if host is NVIDIA");
408-
Ok(is_nvidia)
408+
is_nvidia
409409
}
410410
Err(e) => {
411411
debug!(?e, "Failed to check if host is NVIDIA");
412-
Ok(false) // If we can't run lspci, we assume it's not NVIDIA
412+
false // If we can't run lspci, we assume it's not NVIDIA
413413
}
414414
}
415415
}
416416

417+
fn getfattr_cmd(path: &str) -> Command {
418+
Command::new_with_args(
419+
"getfattr",
420+
[
421+
"-n",
422+
"user.document-portal.host-path",
423+
"--only-values",
424+
path,
425+
],
426+
)
427+
}
428+
417429
pub async fn resolve_host_path(&self, path: &str) -> Result<String, distrobox::Error> {
418430
// The path could be a:
419431
// 1. Host path, already resolved to a real location, e.g., "/home/user/Documents/custom-home-folder".
@@ -425,15 +437,7 @@ impl RootStore {
425437

426438
debug!(?path, "Resolving host path");
427439

428-
let cmd = Command::new_with_args(
429-
"getfattr",
430-
[
431-
"-n",
432-
"user.document-portal.host-path",
433-
"--only-values",
434-
path,
435-
],
436-
);
440+
let cmd = Self::getfattr_cmd(path);
437441
let output = self
438442
.run_to_string(cmd)
439443
.await
@@ -470,3 +474,49 @@ impl Default for RootStore {
470474
glib::Object::builder().build()
471475
}
472476
}
477+
478+
#[cfg(test)]
479+
mod tests {
480+
use std::io;
481+
482+
use super::*;
483+
use crate::fakers::NullCommandRunnerBuilder;
484+
485+
#[test]
486+
fn test_resolve_path() {
487+
// (input_path, getfattr_output, expected_resolved_path)
488+
let tests = [
489+
(
490+
"/run/user/1000/doc/abc123",
491+
Ok("/home/user/Documents/custom-home-folder"),
492+
Ok("/home/user/Documents/custom-home-folder"),
493+
),
494+
("/home/user/Documents/custom-home-folder", Ok(""), {
495+
Ok("/home/user/Documents/custom-home-folder")
496+
}),
497+
// If the resolution fails and the path is from a sandbox, we expect an error
498+
("/run/user/1000/doc/xyz456", Err(()), Err(())),
499+
];
500+
501+
for (input_path, getfattr_output, expected_resolved_path) in tests {
502+
let runner = NullCommandRunnerBuilder::new()
503+
.cmd_full(RootStore::getfattr_cmd(input_path), move || {
504+
getfattr_output
505+
.map(|s| s.to_string())
506+
// we need to return a real io::Error here
507+
.map_err(|_| io::Error::new(io::ErrorKind::NotFound, "Command not found"))
508+
})
509+
.build();
510+
let store = RootStore::new(runner);
511+
512+
let resolved_path: Result<String, distrobox::Error> =
513+
smol::block_on(store.resolve_host_path(input_path));
514+
515+
if let Ok(expected_resolved_path) = expected_resolved_path {
516+
assert_eq!(resolved_path.unwrap(), expected_resolved_path);
517+
} else {
518+
assert!(expected_resolved_path.is_err());
519+
}
520+
}
521+
}
522+
}

0 commit comments

Comments
 (0)