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

zhiburt / expectrl / 8129528574

03 Mar 2024 10:26AM UTC coverage: 47.348% (-9.2%) from 56.575%
8129528574

Pull #68

github

web-flow
Merge 43d30f6c4 into e8b43ff1b
Pull Request #68: [WIP] Move to trait based version `Expect`

159 of 380 new or added lines in 15 files covered. (41.84%)

724 existing lines in 16 files now uncovered.

1491 of 3149 relevant lines covered (47.35%)

3.2 hits per line

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

21.98
/src/stream/stdin.rs
1
//! The module contains a nonblocking version of [std::io::Stdin].  
2

3
use std::io;
4

5
#[cfg(not(feature = "async"))]
6
use std::io::Read;
7

8
#[cfg(feature = "async")]
9
use std::{
10
    pin::Pin,
11
    task::{Context, Poll},
12
};
13

14
#[cfg(feature = "async")]
15
use futures_lite::AsyncRead;
16

17
use crate::Error;
18

19
/// A non blocking version of STDIN.
20
///
21
/// It's not recomended to be used directly.
22
/// But we expose it because it cab be used with [`Session::interact`].
23
///
24
/// [`Session::interact`]: crate::session::Session::interact
25
#[derive(Debug)]
26
pub struct Stdin {
27
    inner: inner::StdinInner,
28
}
29

30
impl Stdin {
31
    /// Creates a new instance of Stdin.
32
    ///
33
    /// It may change terminal's STDIN state therefore, after
34
    /// it's used you must call [Stdin::close].
35
    pub fn open() -> Result<Self, Error> {
2✔
36
        #[cfg(not(feature = "async"))]
37
        {
UNCOV
38
            let mut stdin = inner::StdinInner::new().map(|inner| Self { inner })?;
×
UNCOV
39
            stdin.blocking(true)?;
×
UNCOV
40
            Ok(stdin)
×
41
        }
42

43
        #[cfg(feature = "async")]
44
        {
45
            let stdin = inner::StdinInner::new().map(|inner| Self { inner })?;
6✔
46
            Ok(stdin)
2✔
47
        }
48
    }
49

50
    /// Close frees a resources which were used.
51
    ///
52
    /// It must be called [Stdin] was used.
53
    /// Otherwise the STDIN might be returned to original state.
54
    pub fn close(mut self) -> Result<(), Error> {
2✔
55
        #[cfg(not(feature = "async"))]
×
UNCOV
56
        self.blocking(false)?;
×
57
        self.inner.close()?;
4✔
58
        Ok(())
2✔
59
    }
60

61
    #[cfg(not(feature = "async"))]
UNCOV
62
    pub(crate) fn blocking(&mut self, on: bool) -> Result<(), Error> {
×
UNCOV
63
        self.inner.blocking(on)
×
64
    }
65
}
66

67
#[cfg(not(feature = "async"))]
68
impl Read for Stdin {
69
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
70
        self.inner.read(buf)
×
71
    }
72
}
73

74
#[cfg(feature = "async")]
75
impl AsyncRead for Stdin {
76
    fn poll_read(
×
77
        mut self: Pin<&mut Self>,
78
        cx: &mut Context<'_>,
79
        buf: &mut [u8],
80
    ) -> Poll<io::Result<usize>> {
81
        AsyncRead::poll_read(Pin::new(&mut self.inner), cx, buf)
×
82
    }
83
}
84

85
#[cfg(unix)]
86
impl std::os::unix::prelude::AsRawFd for Stdin {
87
    fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
×
88
        self.inner.as_raw_fd()
×
89
    }
90
}
91

92
#[cfg(unix)]
93
impl std::os::unix::prelude::AsRawFd for &mut Stdin {
94
    fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
×
95
        self.inner.as_raw_fd()
×
96
    }
97
}
98

99
#[cfg(all(unix, feature = "polling"))]
100
impl polling::Source for Stdin {
101
    fn raw(&self) -> std::os::unix::prelude::RawFd {
×
102
        std::os::unix::io::AsRawFd::as_raw_fd(self)
×
103
    }
104
}
105

106
#[cfg(unix)]
107
mod inner {
108
    use super::*;
109

110
    use std::os::unix::prelude::AsRawFd;
111

112
    use nix::{
113
        libc::STDIN_FILENO,
114
        sys::termios::{self, Termios},
115
        unistd::isatty,
116
    };
117
    use ptyprocess::set_raw;
118

119
    #[derive(Debug)]
120
    pub(super) struct StdinInner {
121
        orig_flags: Option<Termios>,
122
        #[cfg(feature = "async")]
123
        stdin: async_io::Async<std::io::Stdin>,
124
        #[cfg(not(feature = "async"))]
125
        stdin: std::io::Stdin,
126
    }
127

128
    impl StdinInner {
129
        pub(super) fn new() -> Result<Self, Error> {
2✔
130
            let stdin = std::io::stdin();
2✔
131
            #[cfg(feature = "async")]
132
            let stdin = async_io::Async::new(stdin)?;
2✔
133

134
            #[cfg(target_os = "macos")]
135
            let orig_flags = None;
×
136

137
            #[cfg(not(target_os = "macos"))]
138
            let orig_flags = Self::prepare()?;
4✔
139

140
            Ok(Self { stdin, orig_flags })
2✔
141
        }
142

143
        pub(super) fn prepare() -> Result<Option<Termios>, Error> {
2✔
144
            // flush buffers
145
            // self.stdin.flush()?;
146

147
            let mut o_pty_flags = None;
2✔
148

149
            // verify: possible controlling fd can be stdout and stderr as well?
150
            // https://stackoverflow.com/questions/35873843/when-setting-terminal-attributes-via-tcsetattrfd-can-fd-be-either-stdout
151
            let isatty_terminal = isatty(STDIN_FILENO)
2✔
152
                .map_err(|e| Error::unknown("failed to call isatty", e.to_string()))?;
×
153
            if isatty_terminal {
2✔
154
                // tcgetattr issues error if a provided fd is not a tty,
155
                // but we can work with such input as it may be redirected.
156
                o_pty_flags = termios::tcgetattr(STDIN_FILENO)
×
157
                    .map(Some)
×
158
                    .map_err(|e| Error::unknown("failed to call tcgetattr", e.to_string()))?;
×
159

160
                set_raw(STDIN_FILENO)
×
161
                    .map_err(|e| Error::unknown("failed to set a raw tty", e.to_string()))?;
×
162
            }
163

164
            Ok(o_pty_flags)
2✔
165
        }
166

167
        pub(super) fn close(&mut self) -> Result<(), Error> {
2✔
168
            if let Some(origin_stdin_flags) = &self.orig_flags {
2✔
169
                termios::tcsetattr(STDIN_FILENO, termios::SetArg::TCSAFLUSH, origin_stdin_flags)
2✔
170
                    .map_err(|e| Error::unknown("failed to call tcsetattr", e.to_string()))?;
×
171
            }
172

173
            Ok(())
2✔
174
        }
175

176
        #[cfg(not(feature = "async"))]
UNCOV
177
        pub(crate) fn blocking(&mut self, on: bool) -> Result<(), Error> {
×
UNCOV
178
            crate::process::unix::make_non_blocking(self.as_raw_fd(), on).map_err(Error::IO)
×
179
        }
180
    }
181

182
    impl AsRawFd for StdinInner {
UNCOV
183
        fn as_raw_fd(&self) -> std::os::unix::prelude::RawFd {
×
UNCOV
184
            self.stdin.as_raw_fd()
×
185
        }
186
    }
187

188
    #[cfg(not(feature = "async"))]
189
    impl Read for StdinInner {
190
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
191
            self.stdin.read(buf)
×
192
        }
193
    }
194

195
    #[cfg(feature = "async")]
196
    impl AsyncRead for StdinInner {
197
        fn poll_read(
×
198
            mut self: Pin<&mut Self>,
199
            cx: &mut Context<'_>,
200
            buf: &mut [u8],
201
        ) -> Poll<io::Result<usize>> {
202
            AsyncRead::poll_read(Pin::new(&mut self.stdin), cx, buf)
×
203
        }
204
    }
205
}
206

207
#[cfg(windows)]
208
mod inner {
209
    use super::*;
210

211
    use conpty::console::Console;
212

213
    pub(super) struct StdinInner {
214
        terminal: Console,
215
        #[cfg(not(feature = "async"))]
216
        is_blocking: bool,
217
        #[cfg(not(feature = "async"))]
218
        stdin: io::Stdin,
219
        #[cfg(feature = "async")]
220
        stdin: blocking::Unblock<io::Stdin>,
221
    }
222

223
    impl std::fmt::Debug for StdinInner {
224
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
×
225
            #[cfg(not(feature = "async"))]
226
            {
227
                f.debug_struct("StdinInner")
×
228
                    .field("terminal", &self.terminal)
×
229
                    .field("is_blocking", &self.is_blocking)
×
230
                    .field("stdin", &self.stdin)
×
231
                    .field("stdin", &self.stdin)
×
232
                    .finish()
233
            }
234
            #[cfg(feature = "async")]
235
            {
236
                f.debug_struct("StdinInner")
×
237
                    .field("terminal", &self.terminal)
×
238
                    .field("stdin", &self.stdin)
×
239
                    .field("stdin", &self.stdin)
×
240
                    .finish()
241
            }
242
        }
243
    }
244

245
    impl StdinInner {
246
        /// Creates a new instance of Stdin.
247
        ///
248
        /// It changes terminal's STDIN state therefore, after
249
        /// it's used please call [Stdin::close].
250
        pub(super) fn new() -> Result<Self, Error> {
×
251
            let console = conpty::console::Console::current().map_err(to_io_error)?;
×
252
            console.set_raw().map_err(to_io_error)?;
×
253

254
            let stdin = io::stdin();
×
255

256
            #[cfg(feature = "async")]
257
            let stdin = blocking::Unblock::new(stdin);
×
258

259
            Ok(Self {
×
260
                terminal: console,
×
261
                #[cfg(not(feature = "async"))]
×
262
                is_blocking: false,
×
263
                stdin,
×
264
            })
265
        }
266

267
        pub(super) fn close(&mut self) -> Result<(), Error> {
×
268
            self.terminal.reset().map_err(to_io_error)?;
×
269
            Ok(())
×
270
        }
271

272
        #[cfg(not(feature = "async"))]
273
        pub(crate) fn blocking(&mut self, on: bool) -> Result<(), Error> {
×
274
            self.is_blocking = on;
×
275
            Ok(())
×
276
        }
277
    }
278

279
    #[cfg(not(feature = "async"))]
280
    impl Read for StdinInner {
281
        fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
×
282
            // fixme: I am not sure why reading works on is_stdin_empty() == true
283
            // maybe rename of the method necessary
284
            if self.is_blocking && !self.terminal.is_stdin_empty().map_err(to_io_error)? {
×
285
                return Err(io::Error::new(io::ErrorKind::WouldBlock, ""));
×
286
            }
287

288
            self.stdin.read(buf)
×
289
        }
290
    }
291

292
    #[cfg(feature = "async")]
293
    impl AsyncRead for StdinInner {
294
        fn poll_read(
×
295
            mut self: Pin<&mut Self>,
296
            cx: &mut Context<'_>,
297
            buf: &mut [u8],
298
        ) -> Poll<io::Result<usize>> {
299
            AsyncRead::poll_read(Pin::new(&mut self.stdin), cx, buf)
×
300
        }
301
    }
302

303
    fn to_io_error(err: impl std::error::Error) -> io::Error {
304
        io::Error::new(io::ErrorKind::Other, err.to_string())
305
    }
306

307
    #[cfg(all(feature = "polling", not(feature = "async")))]
308
    impl Clone for StdinInner {
309
        fn clone(&self) -> Self {
×
310
            Self {
311
                terminal: self.terminal.clone(),
×
312
                is_blocking: self.is_blocking.clone(),
×
313
                stdin: std::io::stdin(),
×
314
            }
315
        }
316
    }
317
}
318

319
#[cfg(all(windows, feature = "polling", not(feature = "async")))]
320
impl Clone for Stdin {
321
    fn clone(&self) -> Self {
×
322
        Self {
323
            inner: self.inner.clone(),
×
324
        }
325
    }
326
}
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