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

lpenz / ogle / 16103139788

06 Jul 2025 09:03PM UTC coverage: 63.678% (+18.2%) from 45.475%
16103139788

push

github

lpenz
Complete refactoring using layers that should be easier to test

Layers connected via streams, which we can mock and test.
This combines a bunch of commits that documented this slow conversion.

374 of 571 new or added lines in 12 files covered. (65.5%)

2 existing lines in 2 files now uncovered.

419 of 658 relevant lines covered (63.68%)

1.69 hits per line

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

94.92
/src/sys.rs
1
// Copyright (C) 2025 Leandro Lisboa Penz <lpenz@lpenz.org>
2
// This file is subject to the terms and conditions defined in
3
// file 'LICENSE', which is part of this source code package.
4

5
//! Module that wraps system functions used as inputs.
6
//!
7
//! Wrapping this makes it very easy to test the whole program. The
8
//! strategy is to parameterize the types with the [`SysApi`] trait,
9
//! and then instantiate them with [`SysReal`] in production, and with
10
//! [`SysVirtual`] in the tests. `SysVirtual` ends up being
11
//! essentially a mock object.
12

13
use color_eyre::Result;
14
use std::cell::RefCell;
15
use std::collections::VecDeque;
16
use tokio::process::Command;
17
use tokio_process_stream as tps;
18

19
use crate::process_wrapper::Cmd;
20
use crate::process_wrapper::Item;
21
use crate::process_wrapper::ProcessStream;
22
use crate::term_wrapper;
23
use crate::time_wrapper::Duration;
24
use crate::time_wrapper::Instant;
25

26
// SysApi ////////////////////////////////////////////////////////////
27

28
/// Trait providing the system functions available for mocking.
29
///
30
/// Users should depend on this type and then take [`SysVirtual`] in
31
/// tests or [`SysReal`] in production.
32
pub trait SysApi: std::fmt::Debug + Clone + Default {
33
    /// Returns an [`Instant`] that corresponds to the current
34
    /// wall-clock time.
35
    fn now(&self) -> Instant;
36

37
    /// Returns the width of the terminal.
38
    #[allow(dead_code)]
39
    fn get_width(&self) -> Option<u16>;
40

41
    /// Starts the execution of the provided [`Cmd`] and returns the
42
    /// corresponding [`ProcessStream`] object.
43
    ///
44
    /// The `ProcessStream` object yields each line printed by the
45
    /// command in its `stdout` and `stderr` via a stream. The same
46
    /// stream gets the [`ExitStatus`](std::process::ExitStatus) when
47
    /// the process finishes.
48
    fn run_command(&mut self, command: Cmd) -> Result<ProcessStream, std::io::Error>;
49
}
50

51
// SysReal ///////////////////////////////////////////////////////////
52

53
/// `SysReal` implements [`SysApi`] by calling real system functions.
54
#[derive(Debug, Clone, Default)]
55
pub struct SysReal {}
56

57
impl SysApi for SysReal {
58
    fn now(&self) -> Instant {
4✔
59
        Instant::from(chrono::offset::Utc::now())
4✔
60
    }
4✔
NEW
61
    fn get_width(&self) -> Option<u16> {
×
NEW
62
        term_wrapper::get_width()
×
NEW
63
    }
×
64
    fn run_command(&mut self, cmd: Cmd) -> Result<ProcessStream, std::io::Error> {
1✔
65
        let process_stream = tps::ProcessLineStream::try_from(Command::from(&cmd))?;
1✔
66
        Ok(ProcessStream::from(process_stream))
1✔
67
    }
1✔
68
}
69

70
// SysVirtual ////////////////////////////////////////////////////////
71

72
/// `SysVirtual` implements [`SysApi`] with behaviors appropriate for
73
/// testing.
74
///
75
/// Namely:
76
/// - [`SysVirtual::now`] starts at the
77
///   [epoch](chrono::DateTime::UNIX_EPOCH) and increments its return
78
///   value by 1 second at every call.
79
/// - [`SysVirtual::get_width`] always returns 80.
80
/// - [`SysVirtual::run_command`] ignores the `cmd` argument and
81
///   yields items from a list that was provided to
82
///   [`SysVirtual::set_items`].
83
#[derive(Debug, Clone, Default)]
84
pub struct SysVirtual {
85
    now: RefCell<Instant>,
86
    items: VecDeque<Item>,
87
}
88

89
impl SysApi for SysVirtual {
90
    /// Returns a "fake" current time by starting at the
91
    /// [epoch](chrono::DateTime::UNIX_EPOCH) and incrementing the
92
    /// return value by 1 second at every call.
93
    fn now(&self) -> Instant {
10✔
94
        let mut now_ref = self.now.borrow_mut();
10✔
95
        let now = *now_ref;
10✔
96
        *now_ref = &now + &Duration::seconds(1);
10✔
97
        now
10✔
98
    }
10✔
99
    fn get_width(&self) -> Option<u16> {
1✔
100
        Some(80)
1✔
101
    }
1✔
102
    /// Yields items from the list that was provided to
103
    /// [`SysVirtual::set_items`].
104
    ///
105
    /// The `cmd` argument is not used.
106
    fn run_command(&mut self, _cmd: Cmd) -> Result<ProcessStream, std::io::Error> {
3✔
107
        let items = std::mem::take(&mut self.items);
3✔
108
        Ok(ProcessStream::from(items))
3✔
109
    }
3✔
110
}
111

112
impl SysVirtual {
113
    /// Sets the list that is going to be yielded by the stream
114
    /// returned by [`SysVirtual::run_command`].
115
    #[allow(dead_code)]
116
    pub fn set_items(&mut self, items: Vec<Item>) {
3✔
117
        self.items = items.into_iter().collect();
3✔
118
    }
3✔
119
}
120

121
// Tests /////////////////////////////////////////////////////////////
122

123
#[cfg(test)]
124
pub mod test {
125
    use color_eyre::Result;
126
    use tokio_stream::StreamExt;
127

128
    use crate::process_wrapper::ExitSts;
129
    use crate::sys::SysReal;
130
    use crate::sys::SysVirtual;
131

132
    use super::*;
133

134
    // A simple test for SysVirtual as we cover it better in
135
    // downstream tests
136

137
    #[tokio::test]
138
    async fn test_sysvirtual() -> Result<()> {
1✔
139
        let list = vec![
1✔
140
            Item::Stdout("stdout".into()),
1✔
141
            Item::Stderr("stderr".into()),
1✔
142
            Item::Done(Ok(ExitSts::default())),
1✔
143
        ];
144
        let mut sys = SysVirtual::default();
1✔
145
        sys.set_items(list.clone());
1✔
146
        let cmd = Cmd::default();
1✔
147
        assert_eq!(format!("{}", cmd), "");
1✔
148
        let streamer = sys.run_command(cmd)?;
1✔
149
        assert_eq!(format!("{:?}", streamer), "ProcessStream::Virtual");
1✔
150
        let streamed = streamer.collect::<Vec<_>>().await;
1✔
151
        assert_eq!(streamed, list);
1✔
152
        assert_eq!(sys.now(), Instant::default());
1✔
153
        assert_eq!(sys.now(), &Instant::default() + &Duration::seconds(1));
1✔
154
        assert_eq!(sys.get_width(), Some(80));
1✔
155
        Ok(())
2✔
156
    }
1✔
157

158
    // A couple of tests for SysReal for minimal coverage
159

160
    #[test]
161
    fn test_sysreal_now() {
1✔
162
        let sys = SysReal::default();
1✔
163
        let now = sys.now();
1✔
164
        let now2 = sys.now();
1✔
165
        assert!(&now2 >= &now);
1✔
166
    }
1✔
167

168
    #[tokio::test]
169
    async fn test_sysreal_run_command() -> Result<()> {
1✔
170
        let mut sys = SysReal::default();
1✔
171
        let cmdarr = ["true"];
1✔
172
        let cmd = Cmd::from(&cmdarr[..]);
1✔
173
        let streamer = sys.run_command(cmd)?;
1✔
174
        let streamed = streamer.collect::<Vec<_>>().await;
1✔
175
        assert_eq!(streamed, vec![Item::Done(Ok(ExitSts::default())),]);
1✔
176
        Ok(())
2✔
177
    }
1✔
178
}
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

© 2026 Coveralls, Inc