1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::{
  io::IO,
  ui::UILock,
  buffer::iters::LinesIter,
};
use super::Result;

#[derive(Debug, PartialEq)]
pub enum FakeIOError {
  ChildExitError,
  NotFound,
}
impl std::fmt::Display for FakeIOError {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    use FakeIOError::*;
    match self {
      ChildExitError => write!(f,"Child process returned error after running."),
      NotFound => write!(f,"Could not open file. Not found or invalid path."),
    }
  }
}
impl std::error::Error for FakeIOError {}
impl crate::error::IOErrorTrait for FakeIOError {}

use std::collections::HashMap;

#[derive(PartialEq, Eq, Hash, Clone)]
pub struct ShellCommand {
  pub command: String,
  pub input: String,
}

/// An [`IO`] implementation intended to simulate filesystem and shell
/// interactions for testing.
#[derive(Clone)]
pub struct FakeIO {
  pub fake_fs: HashMap<String, String>,
  pub fake_shell: HashMap<ShellCommand, String>,
}

impl IO for FakeIO {
  fn run_command(&mut self,
    _ui: &mut UILock,
    command: String,
  ) -> Result<()> {
    if self.fake_shell.contains_key(
      &ShellCommand{command, input: String::new()}
    ) {
      Ok(())
    } else {
      // sh is child and returns error on command not found
      Err(FakeIOError::ChildExitError.into())
    }
  }
  fn run_read_command(&mut self,
    _ui: &mut UILock,
    command: String,
  ) -> Result<String> {
    match self.fake_shell.get(
      &ShellCommand{command, input: String::new()}
    ) {
      Some(x) => Ok(x.to_owned()),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  fn run_write_command(&mut self,
    _ui: &mut UILock,
    command: String,
    input: LinesIter,
  ) -> Result<usize> {
    let input = input.fold(String::new(), |mut s, x| {s.push_str(x); s});
    let inputlen = input.len();
    match self.fake_shell.get(
      &ShellCommand{command, input}
    ) {
      Some(_) => Ok(inputlen),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  fn run_transform_command(&mut self,
    _ui: &mut UILock,
    command: String,
    input: LinesIter,
  ) -> Result<String> {
    let input = input.fold(String::new(), |mut s, x| {s.push_str(x); s});
    match self.fake_shell.get(
      &ShellCommand{command, input}
    ) {
      Some(x) => Ok(x.to_owned()),
      // sh is child and returns error on command not found
      None => Err(FakeIOError::ChildExitError.into()),
    }
  }
  fn write_file(&mut self,
    path: &str,
    append: bool,
    data: LinesIter,
  ) -> Result<usize> {
    let base_data = if append {
      match self.fake_fs.get(path) {
        Some(x) => x.clone(),
        None => String::new(),
      }
    } else {
      String::new()
    };
    let data = data.fold(base_data, |mut s, x|{s.push_str(x); s});
    let datalen = data.len();
    self.fake_fs.insert(path.to_owned(), data);
    Ok(datalen)
  }
  fn read_file(&mut self,
    path: &str,
    must_exist: bool,
  ) -> Result<String> {
    match self.fake_fs.get(path) {
      Some(x) => Ok(x.to_owned()),
      None => if must_exist {
        Err(FakeIOError::NotFound.into())
      } else {
        Ok(String::new())
      },
    }
  }
}