Skip to content

Commit 2839cc7

Browse files
authored
Merge branch 'master' into new_api
2 parents 837ef02 + f89abfc commit 2839cc7

File tree

9 files changed

+131
-59
lines changed

9 files changed

+131
-59
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@
33
All notable changes to this project will be documented in this file.
44
This project adheres to [Semantic Versioning](http://semver.org/).
55

6-
## [0.4.0] 2018-03-17
6+
## [0.4.0] 2020-05-25
77

88
### Changed
99

10+
- PtySession now works with any stream type, e.g. also tcp streams are supported now (thanks, thomasantony)
1011
- breaking: PtyBashSession was renamed and generalized into
1112
PtyReplSession to allow an interface for other REPLs
1213
- better error messages in case of timeout to help debug when you expect
@@ -48,4 +49,4 @@ All `exp_*` methods now also return the yet unread string and/or the matched str
4849
### Fixed
4950

5051
- each execution of rexpect left a temporary file in /tmp/ this is now no longer the case
51-
- try_read was blocking when there was no char ready (!) -> fixed
52+
- try_read was blocking when there was no char ready (!) -> fixed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
description = "Interact with unix processes/bash the same way as pexpect or Don libes expect does"
33
name = "rexpect"
4-
version = "0.3.0"
4+
version = "0.4.0"
55
authors = ["Philipp Keller <[email protected]>"]
66
edition = "2018"
77
repository = "https://github.com/philippkeller/rexpect"
@@ -20,4 +20,4 @@ error-chain = "0.12"
2020
tempfile = "3"
2121

2222
[badges]
23-
travis-ci = { repository = "philippkeller/rexpect" }
23+
travis-ci = { repository = "philippkeller/rexpect" }

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ Add this to your `Cargo.toml`
2222

2323
```toml
2424
[dependencies]
25-
rexpect = "0.3"
25+
rexpect = "0.4"
2626
```
2727

2828
Simple example for interacting via ftp:
@@ -148,9 +148,6 @@ I'm happy to receive PRs or also Issue requests of course.
148148
The tests cover most of the aspects and it should run out of the box for
149149
rust stable, beta and nightly on both Linux or Mac.
150150

151-
That said, I don't know of too many people using it yet, so use this
152-
with caution.
153-
154151
## Design decisions
155152

156153
- use error handling of [error-chain](https://github.com/brson/error-chain)

examples/bash.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ extern crate rexpect;
22
use rexpect::spawn_bash;
33
use rexpect::errors::*;
44

5-
65
fn run() -> Result<()> {
76
let mut p = spawn_bash(Some(1000))?;
87
p.execute("ping 8.8.8.8", "bytes")?;

examples/tcp.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
use rexpect::spawn_stream;
2+
use std::net::TcpStream;
3+
use std::error::Error;
4+
5+
fn main() -> Result<(), Box<dyn Error>>
6+
{
7+
let tcp = TcpStream::connect("www.google.com:80")?;
8+
let tcp_w = tcp.try_clone()?;
9+
let mut session = spawn_stream(tcp, tcp_w, Some(2000));
10+
session.send_line("GET / HTTP/1.1")?;
11+
session.send_line("Host: www.google.com")?;
12+
session.send_line("Accept-Language: fr")?;
13+
session.send_line("")?;
14+
session.exp_string("HTTP/1.1 200 OK")?;
15+
Ok(())
16+
}

src/lib.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,21 +82,21 @@ pub mod process;
8282
pub mod session;
8383
pub mod reader;
8484

85-
pub use session::{spawn, spawn_bash, spawn_python};
85+
pub use session::{spawn, spawn_bash, spawn_python, spawn_stream};
86+
pub use reader::{Until};
8687

8788
pub mod errors {
8889
use std::time;
89-
use crate::process::wait;
9090
// Create the Error, ErrorKind, ResultExt, and Result types
9191
error_chain::error_chain!{
9292
errors {
93-
EOF(expected:String, got:String, exit_code:Option<wait::WaitStatus>) {
93+
EOF(expected:String, got:String, exit_code:Option<String>) {
9494
description("End of filestream (usually stdout) occurred, most probably\
9595
because the process terminated")
9696
display("EOF (End of File): Expected {} but got EOF after reading \"{}\", \
9797
process terminated with {:?}", expected, got,
98-
exit_code.map(|status| format!("{:?}", status))
99-
.unwrap_or("unknown".to_string()))
98+
exit_code.as_ref()
99+
.unwrap_or(& "unknown".to_string()))
100100
}
101101
BrokenPipe {
102102
description("The pipe to the process is broken. Most probably because\
@@ -109,6 +109,10 @@ pub mod errors {
109109
expected, got, (timeout.as_secs() * 1000) as u32
110110
+ timeout.subsec_nanos() / 1_000_000)
111111
}
112+
EmptyProgramName {
113+
description("The provided program name is empty.")
114+
display("EmptyProgramName")
115+
}
112116
}
113117
}
114118
}

src/process.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ use nix::libc::{STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO};
1515
pub use nix::sys::{wait, signal};
1616
use crate::errors::*; // load error-chain
1717

18-
1918
/// Start a process in a forked tty so you can interact with it the same as you would
2019
/// within a terminal
2120
///
@@ -71,7 +70,6 @@ use nix::pty::ptsname_r;
7170
/// based on https://blog.tarq.io/ptsname-on-osx-with-rust/
7271
fn ptsname_r(fd: &PtyMaster) -> nix::Result<String> {
7372
use std::ffi::CStr;
74-
use std::os::unix::io::AsRawFd;
7573
use nix::libc::{ioctl, TIOCPTYGNAME};
7674

7775
// the buffer size on OSX is 128, defined by sys/ttycom.h
@@ -152,21 +150,21 @@ impl PtyProcess {
152150
///
153151
/// This method runs waitpid on the process.
154152
/// This means: If you ran `exit()` before or `status()` this method will
155-
/// return an Error
153+
/// return `None`
156154
///
157155
/// # Example
158156
/// ```rust,no_run
159157
///
160158
/// # extern crate rexpect;
161-
/// use rexpect::process;
159+
/// use rexpect::process::{self, wait::WaitStatus};
162160
/// use std::process::Command;
163161
///
164162
/// # fn main() {
165-
/// let cmd = Command::new("/path/to/myprog");
166-
/// let process = process::PtyProcess::new(cmd).expect("could not execute myprog");
167-
/// while process.status().unwrap() == process::wait::WaitStatus::StillAlive {
168-
/// // do something
169-
/// }
163+
/// let cmd = Command::new("/path/to/myprog");
164+
/// let process = process::PtyProcess::new(cmd).expect("could not execute myprog");
165+
/// while let Some(WaitStatus::StillAlive) = process.status() {
166+
/// // do something
167+
/// }
170168
/// # }
171169
/// ```
172170
///

src/session.rs

Lines changed: 92 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,19 @@ use std::ops::{Deref, DerefMut};
1010
use std::process::Command;
1111
use tempfile;
1212

13-
/// Interact with a process with read/write/signals, etc.
14-
#[allow(dead_code)]
15-
pub struct PtySession {
16-
pub process: PtyProcess,
17-
pub writer: LineWriter<File>,
13+
pub struct StreamSession<W: Write> {
14+
pub writer: LineWriter<W>,
1815
pub reader: NBReader,
19-
pub commandname: String, // only for debugging purposes now
2016
}
2117

22-
/// Start a process in a tty session, write and read from it
23-
///
24-
/// # Example
25-
///
26-
/// ```
27-
///
28-
/// use rexpect::spawn;
29-
/// # use rexpect::errors::*;
30-
///
31-
/// # fn main() {
32-
/// # || -> Result<()> {
33-
/// let mut s = spawn("cat", Some(1000))?;
34-
/// s.send_line("hello, polly!")?;
35-
/// let line = s.read_line()?;
36-
/// assert_eq!("hello, polly!", line);
37-
/// # Ok(())
38-
/// # }().expect("test failed");
39-
/// # }
40-
/// ```
41-
impl PtySession {
18+
impl<W: Write> StreamSession<W> {
19+
pub fn new<R: Read + Send + 'static>(reader: R, writer: W, timeout_ms: Option<u64>) -> Self {
20+
Self {
21+
writer: LineWriter::new(writer),
22+
reader: NBReader::new(reader, timeout_ms),
23+
}
24+
}
25+
4226
/// sends string and a newline to process
4327
///
4428
/// this is guaranteed to be flushed to the process
@@ -123,6 +107,11 @@ impl PtySession {
123107
self.reader.try_read()
124108
}
125109

110+
// wrapper around reader::read_until to give more context for errors
111+
fn exp(&mut self, needle: &ReadUntil) -> Result<(String, String)> {
112+
self.reader.read_until(needle)
113+
}
114+
126115
/// Wait until we see EOF (i.e. child process has terminated)
127116
/// Return all the yet unread output
128117
pub fn exp_eof(&mut self) -> Result<String> {
@@ -154,6 +143,61 @@ impl PtySession {
154143
self.exp(&Str(needle.to_string()))
155144
}
156145
}
146+
/// Interact with a process with read/write/signals, etc.
147+
#[allow(dead_code)]
148+
pub struct PtySession {
149+
pub process: PtyProcess,
150+
pub stream: StreamSession<File>,
151+
pub commandname: String, // only for debugging purposes now
152+
}
153+
154+
155+
// make StreamSession's methods available directly
156+
impl Deref for PtySession {
157+
type Target = StreamSession<File>;
158+
fn deref(&self) -> &StreamSession<File> {
159+
&self.stream
160+
}
161+
}
162+
163+
impl DerefMut for PtySession {
164+
fn deref_mut(&mut self) -> &mut StreamSession<File> {
165+
&mut self.stream
166+
}
167+
}
168+
169+
/// Start a process in a tty session, write and read from it
170+
///
171+
/// # Example
172+
///
173+
/// ```
174+
///
175+
/// use rexpect::spawn;
176+
/// # use rexpect::errors::*;
177+
///
178+
/// # fn main() {
179+
/// # || -> Result<()> {
180+
/// let mut s = spawn("cat", Some(1000))?;
181+
/// s.send_line("hello, polly!")?;
182+
/// let line = s.read_line()?;
183+
/// assert_eq!("hello, polly!", line);
184+
/// # Ok(())
185+
/// # }().expect("test failed");
186+
/// # }
187+
/// ```
188+
impl PtySession {
189+
fn new(process: PtyProcess, timeout_ms: Option<u64>, commandname: String) -> Result<Self> {
190+
191+
let f = process.get_file_handle();
192+
let reader = f.try_clone().chain_err(|| "couldn't open write stream")?;
193+
let stream = StreamSession::new(reader, f, timeout_ms);
194+
Ok(Self {
195+
process,
196+
stream,
197+
commandname: commandname,
198+
})
199+
}
200+
}
157201

158202
/// Turn e.g. "prog arg1 arg2" into ["prog", "arg1", "arg2"]
159203
/// Also takes care of single and double quotes
@@ -180,15 +224,14 @@ fn tokenize_command(program: &str) -> Vec<String> {
180224
/// error message indicating where it stopped.
181225
/// For automation 30'000 (30s, the default in pexpect) is a good value.
182226
pub fn spawn(program: &str, timeout_ms: Option<u64>) -> Result<PtySession> {
183-
let command = if program.find(" ").is_some() {
184-
let mut parts = tokenize_command(program);
185-
let mut cmd = Command::new(&parts[0].to_string());
186-
parts.remove(0);
187-
cmd.args(parts);
188-
cmd
189-
} else {
190-
Command::new(program)
191-
};
227+
if program.is_empty() {
228+
return Err(ErrorKind::EmptyProgramName.into());
229+
}
230+
231+
let mut parts = tokenize_command(program);
232+
let prog = parts.remove(0);
233+
let mut command = Command::new(prog);
234+
command.args(parts);
192235
spawn_command(command, timeout_ms)
193236
}
194237

@@ -395,6 +438,11 @@ pub fn spawn_python(timeout: Option<u64>) -> Result<PtyReplSession> {
395438
})
396439
}
397440

441+
/// Spawn a REPL from a stream
442+
pub fn spawn_stream<R: Read + Send + 'static, W: Write>(reader: R, writer: W, timeout_ms: Option<u64>) -> StreamSession<W> {
443+
StreamSession::new(reader, writer, timeout_ms)
444+
}
445+
398446
#[cfg(test)]
399447
mod tests {
400448
use super::*;
@@ -497,6 +545,15 @@ mod tests {
497545
Ok(())
498546
}()
499547
.unwrap_or_else(|e| panic!("test_expect_any failed: {}", e));
548+
549+
#[test]
550+
fn test_expect_empty_command_error() {
551+
let p = spawn("", Some(1000));
552+
match p {
553+
Ok(_) => assert!(false, "should raise an error"),
554+
Err(Error(ErrorKind::EmptyProgramName, _)) => {}
555+
Err(_) => assert!(false, "should raise EmptyProgramName"),
556+
}
500557
}
501558

502559
#[test]

0 commit comments

Comments
 (0)