Skip to content

Commit

Permalink
Improve mock with enhanced_mock feature
Browse files Browse the repository at this point in the history
  • Loading branch information
dvermd authored and conradkleinespel committed Aug 8, 2020
1 parent 955206c commit 8361021
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 38 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Expand Up @@ -16,3 +16,6 @@ libc = "0.2"

[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["std", "winnt", "fileapi", "processenv", "winbase", "handleapi", "consoleapi", "minwindef", "wincon"] }

[features]
enhanced_mock = []
16 changes: 16 additions & 0 deletions README.md
Expand Up @@ -48,6 +48,22 @@ fn main() {

The full API documentation is available at [https://docs.rs/rpassword](https://docs.rs/rpassword).

## Optional feature

The optional feature **enhanced_mock** can be enabled to change `read_password_with_reader` signature from
```
pub fn read_password_with_reader<T>(source: Option<T>) -> ::std::io::Result<String>
where
T: ::std::io::BufRead
```
to
```
pub fn read_password_with_reader<T>(source: Option<&mut T>) -> ::std::io::Result<String>
where
T: ::std::io::BufRead
```
allowing to call `read_password_with_reader` multiple times with the same reader.

## Contributors

We welcome contribution from everyone. Feel free to open an issue or a pull request at any time.
Expand Down
143 changes: 107 additions & 36 deletions src/lib.rs
Expand Up @@ -39,11 +39,6 @@ fn fixes_newline(password: &mut ZeroOnDrop) {
}
}

/// Reads a password from STDIN
pub fn read_password() -> ::std::io::Result<String> {
read_password_with_reader(None::<::std::io::Empty>)
}

#[cfg(unix)]
mod unix {
use libc::{c_int, termios, isatty, tcsetattr, TCSANOW, ECHO, ECHONL, STDIN_FILENO};
Expand Down Expand Up @@ -242,19 +237,116 @@ use unix::{read_password_from_stdin, display_on_tty};
use windows::{read_password_from_stdin, display_on_tty};

/// Reads a password from anything that implements BufRead
pub fn read_password_with_reader<T>(source: Option<T>) -> ::std::io::Result<String>
where T: ::std::io::BufRead {
match source {
Some(mut reader) => {
let mut password = ZeroOnDrop::new();
reader.read_line(&mut password)?;
fixes_newline(&mut password);
Ok(password.into_inner())
},
None => read_password_from_stdin(false),
#[cfg(not(feature = "enhanced_mock"))]
mod legacy_mock {
use super::*;

/// Reads a password from STDIN
pub fn read_password() -> ::std::io::Result<String> {
read_password_with_reader(None::<::std::io::Empty>)
}

/// Reads a password from anything that implements BufRead
pub fn read_password_with_reader<T>(source: Option<T>) -> ::std::io::Result<String>
where
T: ::std::io::BufRead,
{
match source {
Some(mut reader) => {
let mut password = ZeroOnDrop::new();
reader.read_line(&mut password)?;
fixes_newline(&mut password);
Ok(password.into_inner())
},
None => read_password_from_stdin(false),
}
}

#[cfg(test)]
mod tests {
use std::io::Cursor;

fn mock_input_crlf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\r\n\r\n"[..])
}

fn mock_input_lf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\n"[..])
}

#[test]
fn can_read_from_redirected_input() {
let response = ::read_password_with_reader(Some(mock_input_crlf())).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap();
assert_eq!(response, "A mocked response.");
}
}
}

#[cfg(feature = "enhanced_mock")]
mod enhanced_mock {
use super::*;

/// Reads a password from STDIN
pub fn read_password() -> ::std::io::Result<String> {
read_password_with_reader(None::<&mut ::std::io::Empty>)
}

/// Reads a password from anything that implements BufRead
pub fn read_password_with_reader<T>(source: Option<&mut T>) -> ::std::io::Result<String>
where
T: ::std::io::BufRead,
{
match source {
Some(reader) => {
let mut password = ZeroOnDrop::new();
if let Err(err) = reader.read_line(&mut password) {
Err(err)
} else {
fixes_newline(&mut password);
Ok(password.into_inner())
}
}
None => read_password_from_stdin(false),
}
}

#[cfg(test)]
mod tests {
use std::io::Cursor;

fn mock_input_crlf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\r\nAnother mocked response.\r\n"[..])
}

fn mock_input_lf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\nAnother mocked response.\n"[..])
}

#[test]
fn can_read_from_redirected_input_many_times() {
let mut reader_crlf = mock_input_crlf();

let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
assert_eq!(response, "Another mocked response.");

let mut reader_lf = mock_input_lf();
let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
assert_eq!(response, "Another mocked response.");
}
}
}

#[cfg(feature = "enhanced_mock")]
pub use enhanced_mock::{read_password, read_password_with_reader};
#[cfg(not(feature = "enhanced_mock"))]
pub use legacy_mock::{read_password, read_password_with_reader};

/// Reads a password from the terminal
pub fn read_password_from_tty(prompt: Option<&str>)
-> ::std::io::Result<String> {
Expand All @@ -281,24 +373,3 @@ pub fn prompt_password_stderr(prompt: &str) -> std::io::Result<String> {
stderr.flush()?;
read_password()
}

#[cfg(test)]
mod tests {
use std::io::Cursor;

fn mock_input_crlf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\r\n"[..])
}

fn mock_input_lf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\n"[..])
}

#[test]
fn can_read_from_redirected_input() {
let response = ::read_password_with_reader(Some(mock_input_crlf())).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap();
assert_eq!(response, "A mocked response.");
}
}
24 changes: 22 additions & 2 deletions tests/no-terminal.rs
Expand Up @@ -38,13 +38,14 @@ fn close_stdin() {
}

fn mock_input_crlf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\r\n"[..])
Cursor::new(&b"A mocked response.\r\nAnother mocked response.\r\n"[..])
}

fn mock_input_lf() -> Cursor<&'static [u8]> {
Cursor::new(&b"A mocked response.\n"[..])
Cursor::new(&b"A mocked response.\nAnother mocked response.\n"[..])
}

#[cfg(not(feature = "enhanced_mock"))]
#[test]
fn can_read_from_redirected_input() {
close_stdin();
Expand All @@ -54,3 +55,22 @@ fn can_read_from_redirected_input() {
let response = ::read_password_with_reader(Some(mock_input_lf())).unwrap();
assert_eq!(response, "A mocked response.");
}

#[cfg(feature = "enhanced_mock")]
#[test]
fn can_read_from_redirected_input_many_times() {
close_stdin();

let mut reader_crlf = mock_input_crlf();

let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(&mut reader_crlf)).unwrap();
assert_eq!(response, "Another mocked response.");

let mut reader_lf = mock_input_lf();
let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
assert_eq!(response, "A mocked response.");
let response = ::read_password_with_reader(Some(&mut reader_lf)).unwrap();
assert_eq!(response, "Another mocked response.");
}

0 comments on commit 8361021

Please sign in to comment.