• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

veeso / termscp / 4730861037

pending completion
4730861037

Pull #164

github

GitHub
Merge 1dce36354 into efb30231e
Pull Request #164: bump remotefs-ssh to 0.1.5

5010 of 5343 relevant lines covered (93.77%)

15.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.05
/src/host/mod.rs
1
//! ## Host
5✔
2
//!
3
//! `host` is the module which provides functionalities to host file system
4

5
// ext
6
use filetime::{self, FileTime};
7
#[cfg(target_family = "unix")]
8
use remotefs::fs::UnixPex;
9
use remotefs::fs::{File, FileType, Metadata};
10
use std::fs::{self, File as StdFile, OpenOptions};
11
use std::path::{Path, PathBuf};
12
use thiserror::Error;
13
use wildmatch::WildMatch;
14
// Metadata ext
15
#[cfg(target_family = "unix")]
16
use std::fs::set_permissions;
17
#[cfg(target_family = "unix")]
18
use std::os::unix::fs::PermissionsExt;
19

20
// Locals
21
use crate::utils::path;
22

23
/// HostErrorType provides an overview of the specific host error
24
#[derive(Error, Debug)]
8✔
25
pub enum HostErrorType {
26
    #[error("No such file or directory")]
27
    NoSuchFileOrDirectory,
28
    #[error("File is readonly")]
29
    ReadonlyFile,
30
    #[error("Could not access directory")]
31
    DirNotAccessible,
32
    #[error("Could not access file")]
33
    FileNotAccessible,
34
    #[error("File already exists")]
35
    FileAlreadyExists,
36
    #[error("Could not create file")]
37
    CouldNotCreateFile,
38
    #[error("Command execution failed")]
39
    ExecutionFailed,
40
    #[error("Could not delete file")]
41
    DeleteFailed,
42
}
43

44
/// ### HostError
45
///
46
/// HostError is a wrapper for the error type and the exact io error
47
#[derive(Debug)]
×
48
pub struct HostError {
49
    pub error: HostErrorType,
×
50
    ioerr: Option<std::io::Error>,
51
    path: Option<PathBuf>,
×
52
}
53

54
impl HostError {
55
    /// Instantiates a new HostError
56
    pub(crate) fn new(error: HostErrorType, errno: Option<std::io::Error>, p: &Path) -> Self {
16✔
57
        HostError {
16✔
58
            error,
59
            ioerr: errno,
60
            path: Some(p.to_path_buf()),
16✔
61
        }
62
    }
16✔
63
}
64

65
impl From<HostErrorType> for HostError {
66
    fn from(error: HostErrorType) -> Self {
7✔
67
        HostError {
7✔
68
            error,
69
            ioerr: None,
7✔
70
            path: None,
7✔
71
        }
72
    }
7✔
73
}
74

75
impl std::fmt::Display for HostError {
76
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
8✔
77
        let p_str: String = match self.path.as_ref() {
8✔
78
            None => String::new(),
7✔
79
            Some(p) => format!(" ({})", p.display()),
1✔
80
        };
81
        match &self.ioerr {
8✔
82
            Some(err) => write!(f, "{}: {}{}", self.error, err, p_str),
1✔
83
            None => write!(f, "{}{}", self.error, p_str),
7✔
84
        }
85
    }
8✔
86
}
87

88
/// Localhost is the entity which holds the information about the current directory and host.
89
/// It provides functions to navigate across the local host file system
90
pub struct Localhost {
91
    wrkdir: PathBuf,
92
    files: Vec<File>,
93
}
94

95
impl Localhost {
96
    /// Instantiates a new Localhost struct
97
    pub fn new(wrkdir: PathBuf) -> Result<Localhost, HostError> {
24✔
98
        debug!("Initializing localhost at {}", wrkdir.display());
24✔
99
        let mut host: Localhost = Localhost {
24✔
100
            wrkdir,
101
            files: Vec::new(),
24✔
102
        };
103
        // Check if dir exists
104
        if !host.file_exists(host.wrkdir.as_path()) {
24✔
105
            error!(
1✔
106
                "Failed to initialize localhost: {} doesn't exist",
107
                host.wrkdir.display()
×
108
            );
109
            return Err(HostError::new(
1✔
110
                HostErrorType::NoSuchFileOrDirectory,
1✔
111
                None,
1✔
112
                host.wrkdir.as_path(),
1✔
113
            ));
114
        }
115
        // Retrieve files for provided path
116
        host.files = match host.scan_dir(host.wrkdir.as_path()) {
23✔
117
            Ok(files) => files,
23✔
118
            Err(err) => {
×
119
                error!(
×
120
                    "Failed to initialize localhost: could not scan wrkdir: {}",
121
                    err
122
                );
123
                return Err(err);
×
124
            }
125
        };
126
        info!("Localhost initialized with success");
23✔
127
        Ok(host)
23✔
128
    }
24✔
129

130
    /// Print working directory
131
    pub fn pwd(&self) -> PathBuf {
9✔
132
        self.wrkdir.clone()
9✔
133
    }
9✔
134

135
    /// List files in current directory
136
    #[allow(dead_code)]
137
    pub fn list_dir(&self) -> Vec<File> {
9✔
138
        self.files.clone()
9✔
139
    }
9✔
140

141
    /// Change working directory with the new provided directory
142
    pub fn change_wrkdir(&mut self, new_dir: &Path) -> Result<PathBuf, HostError> {
2✔
143
        let new_dir: PathBuf = self.to_path(new_dir);
2✔
144
        info!("Changing localhost directory to {}...", new_dir.display());
2✔
145
        // Check whether directory exists
146
        if !self.file_exists(new_dir.as_path()) {
2✔
147
            error!("Could not change directory: No such file or directory");
1✔
148
            return Err(HostError::new(
1✔
149
                HostErrorType::NoSuchFileOrDirectory,
1✔
150
                None,
1✔
151
                new_dir.as_path(),
1✔
152
            ));
153
        }
154
        // Change directory
155
        if let Err(err) = std::env::set_current_dir(new_dir.as_path()) {
1✔
156
            error!("Could not enter directory: {}", err);
×
157
            return Err(HostError::new(
×
158
                HostErrorType::NoSuchFileOrDirectory,
×
159
                Some(err),
×
160
                new_dir.as_path(),
×
161
            ));
162
        }
1✔
163
        let prev_dir: PathBuf = self.wrkdir.clone(); // Backup location
1✔
164
                                                     // Update working directory
165
                                                     // Change dir
166
        self.wrkdir = new_dir;
1✔
167
        // Scan new directory
168
        self.files = match self.scan_dir(self.wrkdir.as_path()) {
1✔
169
            Ok(files) => files,
1✔
170
            Err(err) => {
×
171
                error!("Could not scan new directory: {}", err);
×
172
                // Restore directory
173
                self.wrkdir = prev_dir;
×
174
                return Err(err);
×
175
            }
176
        };
177
        debug!("Changed directory to {}", self.wrkdir.display());
1✔
178
        Ok(self.wrkdir.clone())
1✔
179
    }
2✔
180

181
    /// Make a directory at path and update the file list (only if relative)
182
    pub fn mkdir(&mut self, dir_name: &Path) -> Result<(), HostError> {
5✔
183
        self.mkdir_ex(dir_name, false)
5✔
184
    }
5✔
185

186
    /// Extended option version of makedir.
187
    /// ignex: don't report error if directory already exists
188
    pub fn mkdir_ex(&mut self, dir_name: &Path, ignex: bool) -> Result<(), HostError> {
7✔
189
        let dir_path: PathBuf = self.to_path(dir_name);
7✔
190
        info!("Making directory {}", dir_path.display());
7✔
191
        // If dir already exists, return Error
192
        if dir_path.exists() {
7✔
193
            match ignex {
1✔
194
                true => return Ok(()),
×
195
                false => {
196
                    return Err(HostError::new(
1✔
197
                        HostErrorType::FileAlreadyExists,
1✔
198
                        None,
1✔
199
                        dir_path.as_path(),
1✔
200
                    ))
201
                }
202
            }
203
        }
204
        match std::fs::create_dir(dir_path.as_path()) {
6✔
205
            Ok(_) => {
206
                // Update dir
207
                if dir_name.is_relative() {
7✔
208
                    self.files = self.scan_dir(self.wrkdir.as_path())?;
2✔
209
                }
210
                info!("Created directory {}", dir_path.display());
5✔
211
                Ok(())
5✔
212
            }
213
            Err(err) => {
1✔
214
                error!("Could not make directory: {}", err);
1✔
215
                Err(HostError::new(
1✔
216
                    HostErrorType::CouldNotCreateFile,
1✔
217
                    Some(err),
1✔
218
                    dir_path.as_path(),
1✔
219
                ))
220
            }
1✔
221
        }
222
    }
7✔
223

224
    /// Remove file entry
225
    pub fn remove(&mut self, entry: &File) -> Result<(), HostError> {
4✔
226
        if entry.is_dir() {
4✔
227
            // If file doesn't exist; return error
228
            debug!("Removing directory {}", entry.path().display());
2✔
229
            if !entry.path().exists() {
2✔
230
                error!("Directory doesn't exist");
1✔
231
                return Err(HostError::new(
1✔
232
                    HostErrorType::NoSuchFileOrDirectory,
1✔
233
                    None,
1✔
234
                    entry.path(),
1✔
235
                ));
236
            }
237
            // Remove
238
            match std::fs::remove_dir_all(entry.path()) {
1✔
239
                Ok(_) => {
240
                    // Update dir
241
                    self.files = self.scan_dir(self.wrkdir.as_path())?;
1✔
242
                    info!("Removed directory {}", entry.path().display());
1✔
243
                    Ok(())
1✔
244
                }
245
                Err(err) => {
×
246
                    error!("Could not remove directory: {}", err);
×
247
                    Err(HostError::new(
×
248
                        HostErrorType::DeleteFailed,
×
249
                        Some(err),
×
250
                        entry.path(),
×
251
                    ))
252
                }
253
            }
254
        } else {
×
255
            // If file doesn't exist; return error
256
            debug!("Removing file {}", entry.path().display());
2✔
257
            if !entry.path().exists() {
2✔
258
                error!("File doesn't exist");
1✔
259
                return Err(HostError::new(
1✔
260
                    HostErrorType::NoSuchFileOrDirectory,
1✔
261
                    None,
1✔
262
                    entry.path(),
1✔
263
                ));
264
            }
265
            // Remove
266
            match std::fs::remove_file(entry.path()) {
1✔
267
                Ok(_) => {
268
                    // Update dir
269
                    self.files = self.scan_dir(self.wrkdir.as_path())?;
1✔
270
                    info!("Removed file {}", entry.path().display());
1✔
271
                    Ok(())
1✔
272
                }
273
                Err(err) => {
×
274
                    error!("Could not remove file: {}", err);
×
275
                    Err(HostError::new(
×
276
                        HostErrorType::DeleteFailed,
×
277
                        Some(err),
×
278
                        entry.path(),
×
279
                    ))
280
                }
281
            }
282
        }
×
283
    }
4✔
284

285
    /// Rename file or directory to new name
286
    pub fn rename(&mut self, entry: &File, dst_path: &Path) -> Result<(), HostError> {
2✔
287
        match std::fs::rename(entry.path(), dst_path) {
2✔
288
            Ok(_) => {
289
                // Scan dir
290
                self.files = self.scan_dir(self.wrkdir.as_path())?;
1✔
291
                debug!(
1✔
292
                    "Moved file {} to {}",
293
                    entry.path().display(),
×
294
                    dst_path.display()
×
295
                );
296
                Ok(())
1✔
297
            }
298
            Err(err) => {
1✔
299
                error!(
1✔
300
                    "Failed to move {} to {}: {}",
301
                    entry.path().display(),
×
302
                    dst_path.display(),
×
303
                    err
304
                );
305
                Err(HostError::new(
1✔
306
                    HostErrorType::CouldNotCreateFile,
1✔
307
                    Some(err),
1✔
308
                    entry.path(),
1✔
309
                ))
310
            }
311
        }
312
    }
2✔
313

314
    /// Copy file to destination path
315
    pub fn copy(&mut self, entry: &File, dst: &Path) -> Result<(), HostError> {
7✔
316
        // Get absolute path of dest
317
        let dst: PathBuf = self.to_path(dst);
7✔
318
        info!(
7✔
319
            "Copying file {} to {}",
320
            entry.path().display(),
×
321
            dst.display()
×
322
        );
323
        // Match entry
324
        if entry.is_dir() {
7✔
325
            // If destination path doesn't exist, create destination
326
            if !dst.exists() {
2✔
327
                debug!("Directory {} doesn't exist; creating it", dst.display());
2✔
328
                self.mkdir(dst.as_path())?;
2✔
329
            }
330
            // Scan dir
331
            let dir_files: Vec<File> = self.scan_dir(entry.path())?;
2✔
332
            // Iterate files
333
            for dir_entry in dir_files.iter() {
4✔
334
                // Calculate dst
335
                let mut sub_dst: PathBuf = dst.clone();
2✔
336
                sub_dst.push(dir_entry.name());
2✔
337
                // Call function recursively
338
                self.copy(dir_entry, sub_dst.as_path())?;
2✔
339
            }
2✔
340
        } else {
2✔
341
            // Copy file
342
            // If destination path is a directory, push file name
343
            let dst: PathBuf = match dst.as_path().is_dir() {
5✔
344
                true => {
345
                    let mut p: PathBuf = dst.clone();
×
346
                    p.push(entry.name().as_str());
×
347
                    p
×
348
                }
×
349
                false => dst.clone(),
5✔
350
            };
351
            // Copy entry path to dst path
352
            if let Err(err) = std::fs::copy(entry.path(), dst.as_path()) {
5✔
353
                error!("Failed to copy file: {}", err);
1✔
354
                return Err(HostError::new(
1✔
355
                    HostErrorType::CouldNotCreateFile,
1✔
356
                    Some(err),
1✔
357
                    entry.path(),
1✔
358
                ));
359
            }
4✔
360
            info!("File copied");
4✔
361
        }
5✔
362
        // Reload directory if dst is pwd
363
        match dst.is_dir() {
6✔
364
            true => {
365
                if dst == self.pwd().as_path() {
2✔
366
                    self.files = self.scan_dir(self.wrkdir.as_path())?;
×
367
                } else if let Some(parent) = dst.parent() {
2✔
368
                    // If parent is pwd, scan directory
369
                    if parent == self.pwd().as_path() {
4✔
370
                        self.files = self.scan_dir(self.wrkdir.as_path())?;
2✔
371
                    }
372
                }
373
            }
374
            false => {
375
                if let Some(parent) = dst.parent() {
4✔
376
                    // If parent is pwd, scan directory
377
                    if parent == self.pwd().as_path() {
6✔
378
                        self.files = self.scan_dir(self.wrkdir.as_path())?;
2✔
379
                    }
380
                }
381
            }
382
        }
383
        Ok(())
6✔
384
    }
7✔
385

386
    /// Stat file and create a File
387
    pub fn stat(&self, path: &Path) -> Result<File, HostError> {
2,188✔
388
        info!("Stating file {}", path.display());
2,188✔
389
        let path: PathBuf = self.to_path(path);
2,188✔
390
        let attr = match fs::metadata(path.as_path()) {
2,188✔
391
            Ok(metadata) => metadata,
2,188✔
392
            Err(err) => {
×
393
                error!("Could not read file metadata: {}", err);
×
394
                return Err(HostError::new(
×
395
                    HostErrorType::FileNotAccessible,
×
396
                    Some(err),
×
397
                    path.as_path(),
×
398
                ));
399
            }
400
        };
401
        let mut metadata = Metadata::from(attr);
2,188✔
402
        if let Ok(symlink) = fs::read_link(path.as_path()) {
2,188✔
403
            metadata.set_symlink(symlink);
100✔
404
            metadata.file_type = FileType::Symlink;
100✔
405
        }
2,188✔
406
        // Match dir / file
407
        Ok(File { path, metadata })
2,188✔
408
    }
2,188✔
409

410
    /// Set file stat
411
    pub fn setstat(&self, path: &Path, metadata: &Metadata) -> Result<(), HostError> {
1✔
412
        debug!("Setting stat for file at {}", path.display());
1✔
413
        if let Some(mtime) = metadata.modified {
1✔
414
            let mtime = FileTime::from_system_time(mtime);
1✔
415
            debug!("setting mtime {:?}", mtime);
1✔
416
            filetime::set_file_mtime(path, mtime)
2✔
417
                .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?;
1✔
418
        }
419
        if let Some(atime) = metadata.accessed {
1✔
420
            let atime = FileTime::from_system_time(atime);
1✔
421
            filetime::set_file_atime(path, atime)
2✔
422
                .map_err(|e| HostError::new(HostErrorType::FileNotAccessible, Some(e), path))?;
1✔
423
        }
424
        #[cfg(target_family = "unix")]
425
        if let Some(mode) = metadata.mode {
1✔
426
            self.chmod(path, mode)?;
1✔
427
        }
428
        Ok(())
1✔
429
    }
1✔
430

431
    /// Execute a command on localhost
432
    pub fn exec(&self, cmd: &str) -> Result<String, HostError> {
1✔
433
        // Make command
434
        let args: Vec<&str> = cmd.split(' ').collect();
1✔
435
        let cmd: &str = args.first().unwrap();
1✔
436
        let argv: &[&str] = &args[1..];
1✔
437
        info!("Executing command: {} {:?}", cmd, argv);
1✔
438
        match std::process::Command::new(cmd).args(argv).output() {
1✔
439
            Ok(output) => match std::str::from_utf8(&output.stdout) {
1✔
440
                Ok(s) => {
1✔
441
                    info!("Command output: {}", s);
1✔
442
                    Ok(s.to_string())
1✔
443
                }
1✔
444
                Err(_) => Ok(String::new()),
×
445
            },
1✔
446
            Err(err) => {
×
447
                error!("Failed to run command: {}", err);
×
448
                Err(HostError::new(
×
449
                    HostErrorType::ExecutionFailed,
×
450
                    Some(err),
×
451
                    self.wrkdir.as_path(),
×
452
                ))
453
            }
×
454
        }
455
    }
1✔
456

457
    /// Change file mode to file, according to UNIX permissions
458
    #[cfg(target_family = "unix")]
459
    pub fn chmod(&self, path: &Path, pex: UnixPex) -> Result<(), HostError> {
4✔
460
        let path: PathBuf = self.to_path(path);
4✔
461
        // Get metadta
462
        match fs::metadata(path.as_path()) {
4✔
463
            Ok(metadata) => {
3✔
464
                let mut mpex = metadata.permissions();
3✔
465
                mpex.set_mode(pex.into());
3✔
466
                match set_permissions(path.as_path(), mpex) {
3✔
467
                    Ok(_) => {
468
                        info!("Changed mode for {} to {:?}", path.display(), pex);
3✔
469
                        Ok(())
3✔
470
                    }
471
                    Err(err) => {
×
472
                        error!("Could not change mode for file {}: {}", path.display(), err);
×
473
                        Err(HostError::new(
×
474
                            HostErrorType::FileNotAccessible,
×
475
                            Some(err),
×
476
                            path.as_path(),
×
477
                        ))
478
                    }
×
479
                }
480
            }
481
            Err(err) => {
1✔
482
                error!(
1✔
483
                    "Chmod failed; could not read metadata for file {}: {}",
484
                    path.display(),
×
485
                    err
486
                );
487
                Err(HostError::new(
1✔
488
                    HostErrorType::FileNotAccessible,
1✔
489
                    Some(err),
1✔
490
                    path.as_path(),
1✔
491
                ))
492
            }
1✔
493
        }
494
    }
4✔
495

496
    /// Open file for read
497
    pub fn open_file_read(&self, file: &Path) -> Result<StdFile, HostError> {
3✔
498
        let file: PathBuf = self.to_path(file);
3✔
499
        info!("Opening file {} for read", file.display());
3✔
500
        if !self.file_exists(file.as_path()) {
3✔
501
            error!("File doesn't exist!");
1✔
502
            return Err(HostError::new(
1✔
503
                HostErrorType::NoSuchFileOrDirectory,
1✔
504
                None,
1✔
505
                file.as_path(),
1✔
506
            ));
507
        }
508
        match OpenOptions::new()
4✔
509
            .create(false)
510
            .read(true)
511
            .write(false)
512
            .open(file.as_path())
2✔
513
        {
514
            Ok(f) => Ok(f),
1✔
515
            Err(err) => {
1✔
516
                error!("Could not open file for read: {}", err);
1✔
517
                Err(HostError::new(
1✔
518
                    HostErrorType::FileNotAccessible,
1✔
519
                    Some(err),
1✔
520
                    file.as_path(),
1✔
521
                ))
522
            }
1✔
523
        }
524
    }
3✔
525

526
    /// Open file for write
527
    pub fn open_file_write(&self, file: &Path) -> Result<StdFile, HostError> {
2✔
528
        let file: PathBuf = self.to_path(file);
2✔
529
        info!("Opening file {} for write", file.display());
2✔
530
        match OpenOptions::new()
4✔
531
            .create(true)
532
            .write(true)
533
            .truncate(true)
534
            .open(file.as_path())
2✔
535
        {
536
            Ok(f) => Ok(f),
1✔
537
            Err(err) => {
1✔
538
                error!("Failed to open file: {}", err);
1✔
539
                match self.file_exists(file.as_path()) {
1✔
540
                    true => Err(HostError::new(
1✔
541
                        HostErrorType::ReadonlyFile,
1✔
542
                        Some(err),
1✔
543
                        file.as_path(),
1✔
544
                    )),
1✔
545
                    false => Err(HostError::new(
×
546
                        HostErrorType::FileNotAccessible,
×
547
                        Some(err),
×
548
                        file.as_path(),
×
549
                    )),
×
550
                }
551
            }
552
        }
553
    }
2✔
554

555
    /// Returns whether provided file path exists
556
    pub fn file_exists(&self, path: &Path) -> bool {
30✔
557
        path.exists()
30✔
558
    }
30✔
559

560
    /// Get content of the current directory as a list of fs entry
561
    pub fn scan_dir(&self, dir: &Path) -> Result<Vec<File>, HostError> {
39✔
562
        info!("Reading directory {}", dir.display());
39✔
563
        match std::fs::read_dir(dir) {
39✔
564
            Ok(e) => {
39✔
565
                let mut fs_entries: Vec<File> = Vec::new();
39✔
566
                for entry in e.flatten() {
2,223✔
567
                    // NOTE: 0.4.1, don't fail if stat for one file fails
568
                    match self.stat(entry.path().as_path()) {
2,184✔
569
                        Ok(entry) => fs_entries.push(entry),
2,184✔
570
                        Err(e) => error!("Failed to stat {}: {}", entry.path().display(), e),
×
571
                    }
572
                }
2,184✔
573
                Ok(fs_entries)
39✔
574
            }
39✔
575
            Err(err) => Err(HostError::new(
×
576
                HostErrorType::DirNotAccessible,
×
577
                Some(err),
×
578
                dir,
579
            )),
×
580
        }
581
    }
39✔
582

583
    /// Find files matching `search` on localhost starting from current directory. Search supports recursive search of course.
584
    /// The `search` argument supports wilcards ('*', '?')
585
    pub fn find(&self, search: &str) -> Result<Vec<File>, HostError> {
2✔
586
        self.iter_search(self.wrkdir.as_path(), &WildMatch::new(search))
2✔
587
    }
2✔
588

589
    /// Create a symlink at path pointing at target
590
    #[cfg(target_family = "unix")]
591
    pub fn symlink(&self, path: &Path, target: &Path) -> Result<(), HostError> {
3✔
592
        let path = self.to_path(path);
3✔
593
        std::os::unix::fs::symlink(target, path.as_path()).map_err(|e| {
5✔
594
            error!(
2✔
595
                "Failed to create symlink at {} pointing at {}: {}",
596
                path.display(),
×
597
                target.display(),
×
598
                e
599
            );
600
            HostError::new(HostErrorType::CouldNotCreateFile, Some(e), path.as_path())
2✔
601
        })
2✔
602
    }
3✔
603

604
    // -- privates
605

606
    /// Recursive call for `find` method.
607
    /// Search in current directory for files which match `filter`.
608
    /// If a directory is found in current directory, `iter_search` will be called using that dir as argument.
609
    fn iter_search(&self, dir: &Path, filter: &WildMatch) -> Result<Vec<File>, HostError> {
4✔
610
        // Scan directory
611
        let mut drained: Vec<File> = Vec::new();
4✔
612
        match self.scan_dir(dir) {
4✔
613
            Err(err) => Err(err),
×
614
            Ok(entries) => {
4✔
615
                // Iter entries
616
                /* For each entry:
617
                - if is dir: call iter_search with `dir`
618
                    - push `iter_search` result to `drained`
619
                - if is file: check if it matches `filter`
620
                    - if it matches `filter`: push to to filter
621
                */
622
                for entry in entries.into_iter() {
18✔
623
                    if entry.is_dir() {
14✔
624
                        // If directory matches; push directory to drained
625
                        let next_path = entry.path().to_path_buf();
2✔
626
                        if filter.matches(entry.name().as_str()) {
2✔
627
                            drained.push(entry);
1✔
628
                        }
629
                        drained.append(&mut self.iter_search(next_path.as_path(), filter)?);
2✔
630
                    } else if filter.matches(entry.name().as_str()) {
14✔
631
                        drained.push(entry);
4✔
632
                    }
633
                }
14✔
634
                Ok(drained)
4✔
635
            }
4✔
636
        }
637
    }
4✔
638

639
    /// Convert path to absolute path
640
    fn to_path(&self, p: &Path) -> PathBuf {
2,216✔
641
        path::absolutize(self.wrkdir.as_path(), p)
2,216✔
642
    }
2,216✔
643
}
644

645
#[cfg(test)]
646
mod tests {
647

648
    use super::*;
649
    #[cfg(target_family = "unix")]
650
    use crate::utils::test_helpers::make_fsentry;
651
    use crate::utils::test_helpers::{create_sample_file, make_dir_at, make_file_at};
652

653
    use pretty_assertions::assert_eq;
654
    #[cfg(target_family = "unix")]
655
    use std::fs::File as StdFile;
656
    #[cfg(target_family = "unix")]
657
    use std::io::Write;
658

659
    #[cfg(target_family = "unix")]
660
    use std::os::unix::fs::{symlink, PermissionsExt};
661
    use std::time::SystemTime;
662
    use std::{ops::AddAssign, time::Duration};
663

664
    #[test]
665
    fn test_host_error_new() {
2✔
666
        let error: HostError =
667
            HostError::new(HostErrorType::CouldNotCreateFile, None, Path::new("/tmp"));
1✔
668
        assert!(error.ioerr.is_none());
1✔
669
        assert_eq!(error.path.as_ref().unwrap(), Path::new("/tmp"));
1✔
670
    }
2✔
671

672
    #[test]
673
    #[cfg(target_family = "unix")]
674
    fn test_host_localhost_new() {
2✔
675
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
676
        assert_eq!(host.wrkdir, PathBuf::from("/dev"));
1✔
677
        // Scan dir
678
        let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
1✔
679
        let mut counter: usize = 0;
1✔
680
        for _ in entries {
196✔
681
            counter += 1;
195✔
682
        }
683
        assert_eq!(host.files.len(), counter);
1✔
684
    }
2✔
685

686
    #[test]
687
    #[cfg(target_os = "windows")]
688
    fn test_host_localhost_new() {
689
        let host: Localhost = Localhost::new(PathBuf::from("C:\\users")).ok().unwrap();
690
        assert_eq!(host.wrkdir, PathBuf::from("C:\\users"));
691
        // Scan dir
692
        let entries = std::fs::read_dir(PathBuf::from("C:\\users").as_path()).unwrap();
693
        let mut counter: usize = 0;
694
        for _ in entries {
695
            counter = counter + 1;
696
        }
697
        assert_eq!(host.files.len(), counter);
698
    }
699

700
    #[test]
701
    #[should_panic]
702
    fn test_host_localhost_new_bad() {
2✔
703
        Localhost::new(PathBuf::from("/omargabber/123/345"))
1✔
704
            .ok()
705
            .unwrap();
1✔
706
    }
2✔
707

708
    #[test]
709
    #[cfg(target_family = "unix")]
710
    fn test_host_localhost_pwd() {
2✔
711
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
712
        assert_eq!(host.pwd(), PathBuf::from("/dev"));
1✔
713
    }
2✔
714

715
    #[test]
716
    #[cfg(target_family = "unix")]
717
    fn test_host_localhost_list_files() {
2✔
718
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
719
        // Scan dir
720
        let entries = std::fs::read_dir(PathBuf::from("/dev").as_path()).unwrap();
1✔
721
        let mut counter: usize = 0;
1✔
722
        for _ in entries {
196✔
723
            counter += 1;
195✔
724
        }
725
        assert_eq!(host.list_dir().len(), counter);
1✔
726
    }
2✔
727

728
    #[test]
729
    #[cfg(target_family = "unix")]
730
    fn test_host_localhost_change_dir() {
2✔
731
        let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
732
        let new_dir: PathBuf = PathBuf::from("/dev");
1✔
733
        assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
1✔
734
        // Verify new files
735
        // Scan dir
736
        let entries = std::fs::read_dir(new_dir.as_path()).unwrap();
1✔
737
        let mut counter: usize = 0;
1✔
738
        for _ in entries {
196✔
739
            counter += 1;
195✔
740
        }
741
        assert_eq!(host.files.len(), counter);
1✔
742
    }
2✔
743

744
    #[test]
745
    #[cfg(target_family = "unix")]
746
    #[should_panic]
747
    fn test_host_localhost_change_dir_failed() {
2✔
748
        let mut host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
749
        let new_dir: PathBuf = PathBuf::from("/omar/gabber/123/456");
1✔
750
        assert!(host.change_wrkdir(new_dir.as_path()).is_ok());
1✔
751
    }
1✔
752

753
    #[test]
754
    #[cfg(target_family = "unix")]
755
    fn test_host_localhost_open_read() {
2✔
756
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
757
        // Create temp file
758
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
759
        assert!(host.open_file_read(file.path()).is_ok());
1✔
760
    }
2✔
761

762
    #[test]
763
    #[cfg(target_family = "unix")]
764
    #[should_panic]
765
    fn test_host_localhost_open_read_err_no_such_file() {
2✔
766
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
767
        assert!(host
1✔
768
            .open_file_read(PathBuf::from("/bin/foo-bar-test-omar-123-456-789.txt").as_path())
1✔
769
            .is_ok());
770
    }
1✔
771

772
    #[test]
773
    #[cfg(any(target_os = "macos", target_os = "linux"))]
774
    fn test_host_localhost_open_read_err_not_accessible() {
2✔
775
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
776
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
777
        //let mut perms = fs::metadata(file.path())?.permissions();
778
        fs::set_permissions(file.path(), PermissionsExt::from_mode(0o222)).unwrap();
1✔
779
        //fs::set_permissions(file.path(), perms)?;
780
        assert!(host.open_file_read(file.path()).is_err());
1✔
781
    }
2✔
782

783
    #[test]
784
    #[cfg(target_family = "unix")]
785
    fn test_host_localhost_open_write() {
2✔
786
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
787
        // Create temp file
788
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
789
        assert!(host.open_file_write(file.path()).is_ok());
1✔
790
    }
2✔
791

792
    #[test]
793
    #[cfg(any(target_os = "macos", target_os = "linux"))]
794
    fn test_host_localhost_open_write_err() {
2✔
795
        let host: Localhost = Localhost::new(PathBuf::from("/dev")).ok().unwrap();
1✔
796
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
797
        //let mut perms = fs::metadata(file.path())?.permissions();
798
        fs::set_permissions(file.path(), PermissionsExt::from_mode(0o444)).unwrap();
1✔
799
        //fs::set_permissions(file.path(), perms)?;
800
        assert!(host.open_file_write(file.path()).is_err());
1✔
801
    }
2✔
802

803
    #[cfg(target_family = "unix")]
804
    #[test]
805
    fn test_host_localhost_symlinks() {
2✔
806
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
807
        // Create sample file
808
        assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok());
1✔
809
        // Create symlink
810
        assert!(symlink(
1✔
811
            format!("{}/foo.txt", tmpdir.path().display()),
1✔
812
            format!("{}/bar.txt", tmpdir.path().display())
1✔
813
        )
814
        .is_ok());
815
        // Get dir
816
        let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
817
        let files: Vec<File> = host.list_dir();
1✔
818
        // Verify files
819
        let file_0: &File = files.get(0).unwrap();
1✔
820
        if file_0.name() == *"foo.txt" {
1✔
821
            assert!(file_0.metadata.symlink.is_none());
×
822
        } else {
823
            assert_eq!(
1✔
824
                file_0.metadata.symlink.as_ref().unwrap(),
1✔
825
                &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
1✔
826
            );
827
        }
828
        // Verify simlink
829
        let file_1: &File = files.get(1).unwrap();
1✔
830
        if file_1.name() == *"bar.txt" {
1✔
831
            assert_eq!(
×
832
                file_1.metadata.symlink.as_ref().unwrap(),
×
833
                &PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()))
×
834
            );
835
        } else {
836
            assert!(file_1.metadata.symlink.is_none());
1✔
837
        }
838
    }
2✔
839

840
    #[test]
841
    #[cfg(target_family = "unix")]
842
    fn test_host_localhost_mkdir() {
2✔
843
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
844
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
845
        let files: Vec<File> = host.list_dir();
1✔
846
        assert_eq!(files.len(), 0); // There should be 0 files now
1✔
847
        assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
1✔
848
        let files: Vec<File> = host.list_dir();
1✔
849
        assert_eq!(files.len(), 1); // There should be 1 file now
1✔
850
                                    // Try to re-create directory
851
        assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_err());
1✔
852
        // Try abs path
853
        assert!(host
1✔
854
            .mkdir_ex(PathBuf::from("/tmp/test_dir_123456789").as_path(), true)
1✔
855
            .is_ok());
856
        // Fail
857
        assert!(host
1✔
858
            .mkdir_ex(
859
                PathBuf::from("/aaaa/oooooo/tmp/test_dir_123456789").as_path(),
1✔
860
                true
861
            )
862
            .is_err());
863
    }
2✔
864

865
    #[test]
866
    #[cfg(target_family = "unix")]
867
    fn test_host_localhost_remove() {
2✔
868
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
869
        // Create sample file
870
        assert!(StdFile::create(format!("{}/foo.txt", tmpdir.path().display()).as_str()).is_ok());
1✔
871
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
872
        let files: Vec<File> = host.list_dir();
1✔
873
        assert_eq!(files.len(), 1); // There should be 1 file now
1✔
874
                                    // Remove file
875
        assert!(host.remove(files.get(0).unwrap()).is_ok());
1✔
876
        // There should be 0 files now
877
        let files: Vec<File> = host.list_dir();
1✔
878
        assert_eq!(files.len(), 0); // There should be 0 files now
1✔
879
                                    // Create directory
880
        assert!(host.mkdir(PathBuf::from("test_dir").as_path()).is_ok());
1✔
881
        // Delete directory
882
        let files: Vec<File> = host.list_dir();
1✔
883
        assert_eq!(files.len(), 1); // There should be 1 file now
1✔
884
        assert!(host.remove(files.get(0).unwrap()).is_ok());
1✔
885
        // Remove unexisting directory
886
        assert!(host
1✔
887
            .remove(&make_fsentry(PathBuf::from("/a/b/c/d"), true))
1✔
888
            .is_err());
889
        assert!(host
1✔
890
            .remove(&make_fsentry(PathBuf::from("/aaaaaaa"), false))
1✔
891
            .is_err());
892
    }
2✔
893

894
    #[test]
895
    #[cfg(target_family = "unix")]
896
    fn test_host_localhost_rename() {
2✔
897
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
898
        // Create sample file
899
        let src_path: PathBuf =
900
            PathBuf::from(format!("{}/foo.txt", tmpdir.path().display()).as_str());
1✔
901
        assert!(StdFile::create(src_path.as_path()).is_ok());
1✔
902
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
903
        let files: Vec<File> = host.list_dir();
1✔
904
        assert_eq!(files.len(), 1); // There should be 1 file now
1✔
905
        assert_eq!(files.get(0).unwrap().name(), "foo.txt");
1✔
906
        // Rename file
907
        let dst_path: PathBuf =
908
            PathBuf::from(format!("{}/bar.txt", tmpdir.path().display()).as_str());
1✔
909
        assert!(host
1✔
910
            .rename(files.get(0).unwrap(), dst_path.as_path())
1✔
911
            .is_ok());
912
        // There should be still 1 file now, but named bar.txt
913
        let files: Vec<File> = host.list_dir();
1✔
914
        assert_eq!(files.len(), 1); // There should be 0 files now
1✔
915
        assert_eq!(files.get(0).unwrap().name(), "bar.txt");
1✔
916
        // Fail
917
        let bad_path: PathBuf = PathBuf::from("/asdailsjoidoewojdijow/ashdiuahu");
1✔
918
        assert!(host
1✔
919
            .rename(files.get(0).unwrap(), bad_path.as_path())
1✔
920
            .is_err());
921
    }
2✔
922

923
    #[test]
924
    fn should_setstat() {
2✔
925
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
926
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
927
        let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
928
        // stat
929
        let mut filemeta = host.stat(file.path()).unwrap();
1✔
930

931
        let mut new_atime = SystemTime::UNIX_EPOCH;
1✔
932
        new_atime.add_assign(Duration::from_secs(1612164210));
1✔
933

934
        let mut new_mtime = SystemTime::UNIX_EPOCH;
1✔
935
        new_mtime.add_assign(Duration::from_secs(1613160210));
1✔
936

937
        filemeta.metadata.accessed = Some(new_atime);
1✔
938
        filemeta.metadata.modified = Some(new_mtime);
1✔
939

940
        // setstat
941
        assert!(host.setstat(filemeta.path(), filemeta.metadata()).is_ok());
1✔
942
        let new_metadata = host.stat(file.path()).unwrap();
1✔
943

944
        assert_eq!(new_metadata.metadata().accessed, Some(new_atime));
1✔
945
        assert_eq!(new_metadata.metadata().modified, Some(new_mtime));
1✔
946
    }
2✔
947

948
    #[cfg(target_family = "unix")]
949
    #[test]
950
    fn test_host_chmod() {
2✔
951
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
952
        let file: tempfile::NamedTempFile = create_sample_file();
1✔
953
        let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
954
        // Chmod to file
955
        assert!(host.chmod(file.path(), UnixPex::from(0o755)).is_ok());
1✔
956
        // Chmod to dir
957
        assert!(host.chmod(tmpdir.path(), UnixPex::from(0o750)).is_ok());
1✔
958
        // Error
959
        assert!(host
1✔
960
            .chmod(
961
                Path::new("/tmp/krgiogoiegj/kwrgnoerig"),
1✔
962
                UnixPex::from(0o777)
1✔
963
            )
964
            .is_err());
965
    }
2✔
966

967
    #[cfg(target_family = "unix")]
968
    #[test]
969
    fn test_host_copy_file_absolute() {
2✔
970
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
971
        // Create file in tmpdir
972
        let mut file1_path: PathBuf = PathBuf::from(tmpdir.path());
1✔
973
        file1_path.push("foo.txt");
1✔
974
        // Write file 1
975
        let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap();
1✔
976
        assert!(file1.write_all(b"Hello world!\n").is_ok());
1✔
977
        // Get file 2 path
978
        let mut file2_path: PathBuf = PathBuf::from(tmpdir.path());
1✔
979
        file2_path.push("bar.txt");
1✔
980
        // Create host
981
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
982
        let file1_entry: File = host.files.get(0).unwrap().clone();
1✔
983
        assert_eq!(file1_entry.name(), String::from("foo.txt"));
1✔
984
        // Copy
985
        assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
1✔
986
        // Verify host has two files
987
        assert_eq!(host.files.len(), 2);
1✔
988
        // Fail copy
989
        assert!(host
1✔
990
            .copy(
991
                &make_fsentry(PathBuf::from("/a/a7/a/a7a"), false),
1✔
992
                PathBuf::from("571k422i").as_path()
1✔
993
            )
994
            .is_err());
995
    }
2✔
996

997
    #[cfg(target_family = "unix")]
998
    #[test]
999
    fn test_host_copy_file_relative() {
2✔
1000
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1001
        // Create file in tmpdir
1002
        let mut file1_path: PathBuf = PathBuf::from(tmpdir.path());
1✔
1003
        file1_path.push("foo.txt");
1✔
1004
        // Write file 1
1005
        let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap();
1✔
1006
        assert!(file1.write_all(b"Hello world!\n").is_ok());
1✔
1007
        // Get file 2 path
1008
        let file2_path: PathBuf = PathBuf::from("bar.txt");
1✔
1009
        // Create host
1010
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
1011
        let file1_entry: File = host.files.get(0).unwrap().clone();
1✔
1012
        assert_eq!(file1_entry.name(), String::from("foo.txt"));
1✔
1013
        // Copy
1014
        assert!(host.copy(&file1_entry, file2_path.as_path()).is_ok());
1✔
1015
        // Verify host has two files
1016
        assert_eq!(host.files.len(), 2);
1✔
1017
    }
2✔
1018

1019
    #[cfg(target_family = "unix")]
1020
    #[test]
1021
    fn test_host_copy_directory_absolute() {
2✔
1022
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1023
        // Create directory in tmpdir
1024
        let mut dir_src: PathBuf = PathBuf::from(tmpdir.path());
1✔
1025
        dir_src.push("test_dir/");
1✔
1026
        assert!(std::fs::create_dir(dir_src.as_path()).is_ok());
1✔
1027
        // Create file in src dir
1028
        let mut file1_path: PathBuf = dir_src.clone();
1✔
1029
        file1_path.push("foo.txt");
1✔
1030
        // Write file 1
1031
        let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap();
1✔
1032
        assert!(file1.write_all(b"Hello world!\n").is_ok());
1✔
1033
        // Copy dir src to dir ddest
1034
        let mut dir_dest: PathBuf = PathBuf::from(tmpdir.path());
1✔
1035
        dir_dest.push("test_dest_dir/");
1✔
1036
        // Create host
1037
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
1038
        let dir_src_entry: File = host.files.get(0).unwrap().clone();
1✔
1039
        assert_eq!(dir_src_entry.name(), String::from("test_dir"));
1✔
1040
        // Copy
1041
        assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
1✔
1042
        // Verify host has two files
1043
        assert_eq!(host.files.len(), 2);
1✔
1044
        // Verify dir_dest contains foo.txt
1045
        let mut test_file_path: PathBuf = dir_dest.clone();
1✔
1046
        test_file_path.push("foo.txt");
1✔
1047
        assert!(host.stat(test_file_path.as_path()).is_ok());
1✔
1048
    }
2✔
1049

1050
    #[cfg(target_family = "unix")]
1051
    #[test]
1052
    fn test_host_copy_directory_relative() {
2✔
1053
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1054
        // Create directory in tmpdir
1055
        let mut dir_src: PathBuf = PathBuf::from(tmpdir.path());
1✔
1056
        dir_src.push("test_dir/");
1✔
1057
        assert!(std::fs::create_dir(dir_src.as_path()).is_ok());
1✔
1058
        // Create file in src dir
1059
        let mut file1_path: PathBuf = dir_src.clone();
1✔
1060
        file1_path.push("foo.txt");
1✔
1061
        // Write file 1
1062
        let mut file1 = StdFile::create(file1_path.as_path()).ok().unwrap();
1✔
1063
        assert!(file1.write_all(b"Hello world!\n").is_ok());
1✔
1064
        // Copy dir src to dir ddest
1065
        let dir_dest: PathBuf = PathBuf::from("test_dest_dir/");
1✔
1066
        // Create host
1067
        let mut host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
1068
        let dir_src_entry: File = host.files.get(0).unwrap().clone();
1✔
1069
        assert_eq!(dir_src_entry.name(), String::from("test_dir"));
1✔
1070
        // Copy
1071
        assert!(host.copy(&dir_src_entry, dir_dest.as_path()).is_ok());
1✔
1072
        // Verify host has two files
1073
        assert_eq!(host.files.len(), 2);
1✔
1074
        // Verify dir_dest contains foo.txt
1075
        let mut test_file_path: PathBuf = dir_dest.clone();
1✔
1076
        test_file_path.push("foo.txt");
1✔
1077
        assert!(host.stat(test_file_path.as_path()).is_ok());
1✔
1078
    }
2✔
1079

1080
    #[test]
1081
    fn test_host_exec() {
2✔
1082
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1083
        let host: Localhost = Localhost::new(PathBuf::from(tmpdir.path())).ok().unwrap();
1✔
1084
        // Execute
1085
        #[cfg(target_family = "unix")]
1086
        assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\n");
1✔
1087
        #[cfg(target_os = "windows")]
1088
        assert_eq!(host.exec("echo 5").ok().unwrap().as_str(), "5\r\n");
1089
    }
2✔
1090

1091
    #[test]
1092
    fn test_host_find() {
2✔
1093
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1094
        let dir_path: &Path = tmpdir.path();
1✔
1095
        // Make files
1096
        assert!(make_file_at(dir_path, "pippo.txt").is_ok());
1✔
1097
        assert!(make_file_at(dir_path, "foo.jpg").is_ok());
1✔
1098
        // Make nested struct
1099
        assert!(make_dir_at(dir_path, "examples").is_ok());
1✔
1100
        let mut subdir: PathBuf = PathBuf::from(dir_path);
1✔
1101
        subdir.push("examples/");
1✔
1102
        assert!(make_file_at(subdir.as_path(), "omar.txt").is_ok());
1✔
1103
        assert!(make_file_at(subdir.as_path(), "errors.txt").is_ok());
1✔
1104
        assert!(make_file_at(subdir.as_path(), "screenshot.png").is_ok());
1✔
1105
        assert!(make_file_at(subdir.as_path(), "examples.csv").is_ok());
1✔
1106
        let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap();
1✔
1107
        // Find txt files
1108
        let mut result: Vec<File> = host.find("*.txt").ok().unwrap();
1✔
1109
        result.sort_by_key(|x: &File| x.name().to_lowercase());
7✔
1110
        // There should be 3 entries
1111
        assert_eq!(result.len(), 3);
1✔
1112
        // Check names (they should be sorted alphabetically already; NOTE: examples/ comes before pippo.txt)
1113
        assert_eq!(result[0].name(), "errors.txt");
1✔
1114
        assert_eq!(result[1].name(), "omar.txt");
1✔
1115
        assert_eq!(result[2].name(), "pippo.txt");
1✔
1116
        // Search for directory
1117
        let mut result: Vec<File> = host.find("examples*").ok().unwrap();
1✔
1118
        result.sort_by_key(|x: &File| x.name().to_lowercase());
3✔
1119
        assert_eq!(result.len(), 2);
1✔
1120
        assert_eq!(result[0].name(), "examples");
1✔
1121
        assert_eq!(result[1].name(), "examples.csv");
1✔
1122
    }
2✔
1123

1124
    #[cfg(target_family = "unix")]
1125
    #[test]
1126
    fn should_create_symlink() {
2✔
1127
        let tmpdir: tempfile::TempDir = tempfile::TempDir::new().unwrap();
1✔
1128
        let dir_path: &Path = tmpdir.path();
1✔
1129
        // Make file
1130
        assert!(make_file_at(dir_path, "pippo.txt").is_ok());
1✔
1131
        let host: Localhost = Localhost::new(PathBuf::from(dir_path)).ok().unwrap();
1✔
1132
        let mut p = dir_path.to_path_buf();
1✔
1133
        p.push("pippo.txt");
1✔
1134
        // Make symlink
1135
        assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_ok());
1✔
1136
        // Fail symlink
1137
        assert!(host.symlink(Path::new("link.txt"), p.as_path()).is_err());
1✔
1138
        assert!(host
1✔
1139
            .symlink(Path::new("/tmp/oooo/aaaa"), p.as_path())
1✔
1140
            .is_err());
1141
    }
2✔
1142

1143
    #[test]
1144
    fn test_host_fmt_error() {
2✔
1145
        let err: HostError = HostError::new(
1✔
1146
            HostErrorType::CouldNotCreateFile,
1✔
1147
            Some(std::io::Error::from(std::io::ErrorKind::AddrInUse)),
1✔
1148
            Path::new("/tmp"),
1✔
1149
        );
1150
        assert_eq!(
1✔
1151
            format!("{err}"),
1✔
1152
            String::from("Could not create file: address in use (/tmp)"),
1✔
1153
        );
1154
        assert_eq!(
1✔
1155
            format!("{}", HostError::from(HostErrorType::DeleteFailed)),
1✔
1156
            String::from("Could not delete file")
1✔
1157
        );
1158
        assert_eq!(
1✔
1159
            format!("{}", HostError::from(HostErrorType::ExecutionFailed)),
1✔
1160
            String::from("Command execution failed"),
1✔
1161
        );
1162
        assert_eq!(
1✔
1163
            format!("{}", HostError::from(HostErrorType::DirNotAccessible)),
1✔
1164
            String::from("Could not access directory"),
1✔
1165
        );
1166
        assert_eq!(
1✔
1167
            format!("{}", HostError::from(HostErrorType::NoSuchFileOrDirectory)),
1✔
1168
            String::from("No such file or directory")
1✔
1169
        );
1170
        assert_eq!(
1✔
1171
            format!("{}", HostError::from(HostErrorType::ReadonlyFile)),
1✔
1172
            String::from("File is readonly")
1✔
1173
        );
1174
        assert_eq!(
1✔
1175
            format!("{}", HostError::from(HostErrorType::FileNotAccessible)),
1✔
1176
            String::from("Could not access file")
1✔
1177
        );
1178
        assert_eq!(
1✔
1179
            format!("{}", HostError::from(HostErrorType::FileAlreadyExists)),
1✔
1180
            String::from("File already exists")
1✔
1181
        );
1182
    }
2✔
1183
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc