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

zhiburt / expectrl / 8133416044

03 Mar 2024 10:18PM UTC coverage: 55.91% (-0.7%) from 56.575%
8133416044

Pull #68

github

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

219 of 377 new or added lines in 14 files covered. (58.09%)

155 existing lines in 6 files now uncovered.

1490 of 2665 relevant lines covered (55.91%)

3.4 hits per line

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

69.89
/src/process/unix.rs
1
//! This module contains a Unix implementation of [crate::process::Process].
2

3
use std::{
4
    io::{self, ErrorKind, Read, Result, Write},
5
    ops::{Deref, DerefMut},
6
    os::unix::prelude::{AsRawFd, RawFd},
7
    process::Command,
8
};
9

10
use crate::{
11
    error::to_io_error,
12
    process::{Healthcheck, NonBlocking, Process, Termios},
13
};
14

15
use ptyprocess::{errno::Errno, stream::Stream, PtyProcess};
16

17
#[cfg(feature = "async")]
18
use super::IntoAsyncStream;
19
#[cfg(feature = "async")]
20
use futures_lite::{AsyncRead, AsyncWrite};
21
#[cfg(feature = "async")]
22
use std::{
23
    pin::Pin,
24
    task::{Context, Poll},
25
};
26

27
pub use ptyprocess::{Signal, WaitStatus};
28

29
/// A Unix representation of a [Process] via [PtyProcess]
30
#[derive(Debug)]
31
pub struct UnixProcess {
32
    proc: PtyProcess,
33
}
34

35
impl Process for UnixProcess {
36
    type Command = Command;
37
    type Stream = PtyStream;
38

39
    fn spawn<S>(cmd: S) -> Result<Self>
7✔
40
    where
41
        S: AsRef<str>,
42
    {
43
        let args = tokenize_command(cmd.as_ref());
14✔
44
        if args.is_empty() {
14✔
45
            return Err(io_error("failed to parse a command"));
2✔
46
        }
47

48
        let mut command = std::process::Command::new(&args[0]);
14✔
49
        let _ = command.args(args.iter().skip(1));
14✔
50

51
        Self::spawn_command(command)
7✔
52
    }
53

54
    fn spawn_command(command: Self::Command) -> Result<Self> {
8✔
55
        let proc = PtyProcess::spawn(command).map_err(to_io_error("Failed to spawn a command"))?;
8✔
56

57
        Ok(Self { proc })
8✔
58
    }
59

60
    fn open_stream(&mut self) -> Result<Self::Stream> {
8✔
61
        let stream = self
24✔
62
            .proc
×
63
            .get_pty_stream()
64
            .map_err(to_io_error("Failed to create a stream"))?;
16✔
65
        let stream = PtyStream::new(stream);
16✔
66
        Ok(stream)
8✔
67
    }
68
}
69

70
impl Healthcheck for UnixProcess {
71
    type Status = WaitStatus;
72

73
    fn get_status(&self) -> Result<Self::Status> {
2✔
74
        get_status(&self.proc)
2✔
75
    }
76

NEW
77
    fn is_alive(&self) -> Result<bool> {
×
UNCOV
78
        self.proc
×
79
            .is_alive()
NEW
80
            .map_err(to_io_error("failed to determine if process is alive"))
×
81
    }
82
}
83

84
impl Termios for UnixProcess {
85
    fn is_echo(&self) -> Result<bool> {
2✔
86
        let value = self.proc.get_echo()?;
2✔
87

88
        Ok(value)
2✔
89
    }
90

91
    fn set_echo(&mut self, on: bool) -> Result<bool> {
2✔
92
        let value = self.proc.set_echo(on, None)?;
2✔
93

94
        Ok(value)
2✔
95
    }
96
}
97

98
impl Deref for UnixProcess {
99
    type Target = PtyProcess;
100

101
    fn deref(&self) -> &Self::Target {
4✔
102
        &self.proc
×
103
    }
104
}
105

106
impl DerefMut for UnixProcess {
107
    fn deref_mut(&mut self) -> &mut Self::Target {
5✔
108
        &mut self.proc
×
109
    }
110
}
111

112
/// A IO stream (write/read) of [UnixProcess].
113
#[derive(Debug)]
114
pub struct PtyStream {
115
    handle: Stream,
116
}
117

118
impl PtyStream {
119
    fn new(stream: Stream) -> Self {
8✔
120
        Self { handle: stream }
121
    }
122
}
123

124
impl Write for PtyStream {
125
    fn write(&mut self, buf: &[u8]) -> Result<usize> {
8✔
126
        self.handle.write(buf)
8✔
127
    }
128

129
    fn flush(&mut self) -> Result<()> {
1✔
130
        self.handle.flush()
1✔
131
    }
132

133
    fn write_vectored(&mut self, bufs: &[io::IoSlice<'_>]) -> Result<usize> {
×
134
        self.handle.write_vectored(bufs)
×
135
    }
136
}
137

138
impl Read for PtyStream {
139
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
8✔
140
        self.handle.read(buf)
8✔
141
    }
142
}
143

144
impl NonBlocking for PtyStream {
145
    fn set_blocking(&mut self, on: bool) -> Result<()> {
7✔
146
        let fd = self.handle.as_raw_fd();
7✔
147
        match on {
7✔
148
            true => make_non_blocking(fd, false),
7✔
149
            false => make_non_blocking(fd, true),
7✔
150
        }
151
    }
152
}
153

154
impl AsRawFd for PtyStream {
155
    fn as_raw_fd(&self) -> RawFd {
×
156
        self.handle.as_raw_fd()
×
157
    }
158
}
159

160
#[cfg(feature = "async")]
161
impl IntoAsyncStream for PtyStream {
162
    type AsyncStream = AsyncPtyStream;
163

164
    fn into_async_stream(self) -> Result<Self::AsyncStream> {
×
165
        AsyncPtyStream::new(self)
×
166
    }
167
}
168

169
/// An async version of IO stream of [UnixProcess].
170
#[cfg(feature = "async")]
171
#[derive(Debug)]
172
pub struct AsyncPtyStream {
173
    stream: async_io::Async<PtyStream>,
174
}
175

176
#[cfg(feature = "async")]
177
impl AsyncPtyStream {
178
    fn new(stream: PtyStream) -> Result<Self> {
×
179
        let stream = async_io::Async::new(stream)?;
×
180
        Ok(Self { stream })
×
181
    }
182
}
183

184
#[cfg(feature = "async")]
185
impl AsyncWrite for AsyncPtyStream {
186
    fn poll_write(
×
187
        mut self: Pin<&mut Self>,
188
        cx: &mut Context<'_>,
189
        buf: &[u8],
190
    ) -> Poll<Result<usize>> {
191
        Pin::new(&mut self.stream).poll_write(cx, buf)
×
192
    }
193

194
    fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
×
195
        Pin::new(&mut self.stream).poll_flush(cx)
×
196
    }
197

198
    fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<()>> {
×
199
        Pin::new(&mut self.stream).poll_close(cx)
×
200
    }
201
}
202

203
#[cfg(feature = "async")]
204
impl AsyncRead for AsyncPtyStream {
205
    fn poll_read(
×
206
        mut self: Pin<&mut Self>,
207
        cx: &mut Context<'_>,
208
        buf: &mut [u8],
209
    ) -> Poll<Result<usize>> {
210
        Pin::new(&mut self.stream).poll_read(cx, buf)
×
211
    }
212
}
213

214
#[cfg(feature = "polling")]
215
impl polling::Source for PtyStream {
216
    fn raw(&self) -> RawFd {
×
217
        self.as_raw_fd()
×
218
    }
219
}
220

221
pub(crate) fn make_non_blocking(fd: RawFd, blocking: bool) -> Result<()> {
7✔
222
    use nix::fcntl::{fcntl, FcntlArg, OFlag};
223

224
    let opt = fcntl(fd, FcntlArg::F_GETFL).map_err(nix_error_to_io)?;
7✔
225
    let mut opt = OFlag::from_bits_truncate(opt);
7✔
226
    opt.set(OFlag::O_NONBLOCK, blocking);
7✔
227
    let _ = fcntl(fd, FcntlArg::F_SETFL(opt)).map_err(nix_error_to_io)?;
14✔
228
    Ok(())
7✔
229
}
230

231
fn nix_error_to_io(err: nix::Error) -> io::Error {
×
232
    io::Error::new(io::ErrorKind::Other, err)
×
233
}
234

235
/// Turn e.g. "prog arg1 arg2" into ["prog", "arg1", "arg2"]
236
/// It takes care of single and double quotes but,
237
///
238
/// It doesn't cover all edge cases.
239
/// So it may not be compatible with real shell arguments parsing.
240
fn tokenize_command(program: &str) -> Vec<String> {
10✔
241
    let re = regex::Regex::new(r#""[^"]+"|'[^']+'|[^'" ]+"#).unwrap();
8✔
242
    let mut res = vec![];
8✔
243
    for cap in re.captures_iter(program) {
16✔
244
        res.push(cap[0].to_string());
16✔
245
    }
246
    res
8✔
247
}
248

249
fn get_status(proc: &PtyProcess) -> std::prelude::v1::Result<WaitStatus, io::Error> {
2✔
250
    match proc.status() {
2✔
251
        Ok(status) => Ok(status),
2✔
252
        Err(err) => match err {
1✔
253
            Errno::ECHILD | Errno::ESRCH => Err(io::Error::new(ErrorKind::WouldBlock, err)),
1✔
NEW
254
            err => Err(io::Error::new(ErrorKind::Other, err)),
×
255
        },
256
    }
257
}
258

259
fn io_error(msg: &str) -> io::Error {
1✔
260
    io::Error::new(io::ErrorKind::Other, msg)
1✔
261
}
262

263
#[cfg(test)]
264
mod tests {
265
    use super::*;
266

267
    #[cfg(unix)]
268
    #[test]
269
    fn test_tokenize_command() {
3✔
270
        let res = tokenize_command("prog arg1 arg2");
1✔
271
        assert_eq!(vec!["prog", "arg1", "arg2"], res);
2✔
272

273
        let res = tokenize_command("prog -k=v");
1✔
274
        assert_eq!(vec!["prog", "-k=v"], res);
2✔
275

276
        let res = tokenize_command("prog 'my text'");
1✔
277
        assert_eq!(vec!["prog", "'my text'"], res);
2✔
278

279
        let res = tokenize_command(r#"prog "my text""#);
1✔
280
        assert_eq!(vec!["prog", r#""my text""#], res);
2✔
281
    }
282
}
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