Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: Improve mock with enhanced_mock feature #48

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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.");
}