Skip to content

Commit 4c65a4a

Browse files
bors[bot]ltratt
andauthored
Merge #64
64: Add the ability to specify stdin to pass to a sub-command. r=vext01 a=ltratt Co-authored-by: Laurence Tratt <laurie@tratt.net>
2 parents e811ea6 + 1ba80cf commit 4c65a4a

File tree

5 files changed

+98
-12
lines changed

5 files changed

+98
-12
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ following:
108108
* `extra-args: <arg 1> [... <arg n>]`, where each space separated argument
109109
will be appended, in order, to those arguments specified as part of
110110
the `test_cmds` function.
111+
* `stdin: <string>`, text to be passed to the command's `stdin`. If the command exits without
112+
having consumed all of `<string>`, an error will be raised. Note, though, that operating
113+
system file buffers can mean that the command *appears* to have consumed all of `<string>`
114+
without it actually having done so.
111115

112116
The above file thus contains 4 meaningful tests, two specified by the user and
113117
two implied by defaults: the `Compiler` should succeed (e.g. return a `0` exit
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Run-time:
2+
// status: success
3+
// stdin: abc
4+
// stdout: Hello abc
5+
6+
use std::io::{Read, stdin};
7+
8+
fn main() {
9+
let mut buf = String::new();
10+
stdin().read_to_string(&mut buf).unwrap();
11+
println!("Hello {}", buf);
12+
}

src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@
100100
//!
101101
//! * `extra-args: <arg 1> [... <arg n>]`, where each space separated argument will be appended,
102102
//! in order, to those arguments specified as part of the `test_cmds` function.
103+
//! * `stdin: <string>`, text to be passed to the command's `stdin`. If the command exits without
104+
//! having consumed all of `<string>`, an error will be raised. Note, though, that operating
105+
//! system file buffers can mean that the command *appears* to have consumed all of `<string>`
106+
//! without it actually having done so.
103107
//!
104108
//! The above file thus contains 4 meaningful tests, two specified by the user and two implied by
105109
//! defaults: the `Compiler` should succeed (e.g. return a `0` exit code when run on Unix), and

src/parser.rs

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ pub(crate) fn parse_tests(test_str: &str) -> Tests {
7272
};
7373
testcmd.status = status;
7474
}
75+
"stdin" => {
76+
testcmd.stdin = Some(val.join("\n"));
77+
}
7578
"stderr" => {
7679
testcmd.stderr = val;
7780
}

src/tester.rs

+75-12
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ use std::{
2121

2222
use fm::FMBuilder;
2323
use getopts::Options;
24-
use libc::{fcntl, poll, pollfd, F_GETFL, F_SETFL, O_NONBLOCK, POLLERR, POLLHUP, POLLIN};
24+
use libc::{
25+
close, fcntl, poll, pollfd, F_GETFL, F_SETFL, O_NONBLOCK, POLLERR, POLLHUP, POLLIN, POLLOUT,
26+
};
2527
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
2628
use threadpool::ThreadPool;
2729
use walkdir::WalkDir;
@@ -369,6 +371,12 @@ impl LangTester {
369371
if let Some(ref status) = test.status {
370372
eprintln!("\n---- lang_tests::{} status ----\n{}", test_fname, status);
371373
}
374+
if test.stdin_remaining != 0 {
375+
eprintln!(
376+
"\n---- lang_tests::{} stdin ----\n{} bytes of stdin were not consumed",
377+
test_fname, test.stdin_remaining
378+
);
379+
}
372380
if let Some(ref stderr) = test.stderr {
373381
eprintln!(
374382
"\n---- lang_tests::{} stderr ----\n{}\n",
@@ -425,6 +433,7 @@ pub(crate) enum Status {
425433
#[derive(Clone, Debug)]
426434
pub(crate) struct TestCmd<'a> {
427435
pub status: Status,
436+
pub stdin: Option<String>,
428437
pub stderr: Vec<&'a str>,
429438
pub stdout: Vec<&'a str>,
430439
/// A list of custom command line arguments which should be passed when
@@ -436,6 +445,7 @@ impl<'a> TestCmd<'a> {
436445
pub fn default() -> Self {
437446
Self {
438447
status: Status::Success,
448+
stdin: None,
439449
stderr: vec!["..."],
440450
stdout: vec!["..."],
441451
args: Vec::new(),
@@ -454,6 +464,7 @@ pub(crate) struct Tests<'a> {
454464
#[derive(Debug, PartialEq)]
455465
struct TestFailure {
456466
status: Option<String>,
467+
stdin_remaining: usize,
457468
stderr: Option<String>,
458469
stdout: Option<String>,
459470
}
@@ -597,14 +608,16 @@ fn run_tests<'a>(
597608

598609
let mut failure = TestFailure {
599610
status: None,
611+
stdin_remaining: 0,
600612
stderr: None,
601613
stdout: None,
602614
};
603615
for (cmd_name, mut cmd) in cmd_pairs {
604616
let default_test = TestCmd::default();
605617
let test = tests.get(&cmd_name).unwrap_or(&default_test);
606618
cmd.args(&test.args);
607-
let (status, stderr, stdout) = run_cmd(inner.clone(), &test_fname, cmd);
619+
let (status, stdin_remaining, stderr, stdout) =
620+
run_cmd(inner.clone(), &test_fname, cmd, &test);
608621

609622
let mut meant_to_error = false;
610623

@@ -635,7 +648,7 @@ fn run_tests<'a>(
635648
// successfully (i.e. if the stderr test failed, print that out; but, equally, if
636649
// stderr wasn't specified as a test, print it out, because the user can't
637650
// otherwise know what it contains).
638-
if !(pass_status && pass_stderr && pass_stdout) {
651+
if !(pass_status && stdin_remaining == 0 && pass_stderr && pass_stdout) {
639652
if !pass_status || failure.status.is_none() {
640653
match test.status {
641654
Status::Success | Status::Error => {
@@ -670,6 +683,8 @@ fn run_tests<'a>(
670683
failure.stdout = Some(stdout);
671684
}
672685

686+
failure.stdin_remaining = stdin_remaining;
687+
673688
// If a sub-test failed, bail out immediately, otherwise subsequent sub-tests
674689
// will overwrite the failure output!
675690
break;
@@ -694,6 +709,7 @@ fn run_tests<'a>(
694709
if failure
695710
!= (TestFailure {
696711
status: None,
712+
stdin_remaining: 0,
697713
stderr: None,
698714
stdout: None,
699715
})
@@ -721,34 +737,43 @@ fn run_cmd(
721737
inner: Arc<LangTesterPooler>,
722738
test_fname: &str,
723739
mut cmd: Command,
724-
) -> (ExitStatus, String, String) {
740+
test: &TestCmd,
741+
) -> (ExitStatus, usize, String, String) {
725742
// The basic sequence here is:
726743
// 1) Spawn the command
727744
// 2) Read everything from stderr & stdout until they are both disconnected
728745
// 3) wait() for the command to finish
729746

730747
let mut child = cmd
748+
.stdin(process::Stdio::piped())
731749
.stderr(process::Stdio::piped())
732750
.stdout(process::Stdio::piped())
733-
.stdin(process::Stdio::null())
734751
.spawn()
735752
.unwrap_or_else(|_| fatal(&format!("Couldn't run command {:?}.", cmd)));
736753

754+
let stdin = child.stdin.as_mut().unwrap();
737755
let stderr = child.stderr.as_mut().unwrap();
738756
let stdout = child.stdout.as_mut().unwrap();
739757

758+
let stdin_fd = stdin.as_raw_fd();
740759
let stderr_fd = stderr.as_raw_fd();
741760
let stdout_fd = stdout.as_raw_fd();
742-
if set_nonblock(stderr_fd)
761+
if set_nonblock(stdin_fd)
762+
.and_then(|_| set_nonblock(stderr_fd))
743763
.and_then(|_| set_nonblock(stdout_fd))
744764
.is_err()
745765
{
746-
fatal("Couldn't set stderr and/or stdout to be non-blocking");
766+
fatal("Couldn't set stdin and/or stderr and/or stdout to be non-blocking");
747767
}
748768

749769
let mut cap_stderr = String::new();
750770
let mut cap_stdout = String::new();
751771
let mut pollfds = [
772+
pollfd {
773+
fd: stdin_fd,
774+
events: 0,
775+
revents: 0,
776+
},
752777
pollfd {
753778
fd: stderr_fd,
754779
events: POLLERR | POLLIN | POLLHUP,
@@ -760,6 +785,14 @@ fn run_cmd(
760785
revents: 0,
761786
},
762787
];
788+
let mut stdin_off = 0;
789+
let mut stdin_finished;
790+
if test.stdin.is_none() {
791+
stdin_finished = true;
792+
} else {
793+
stdin_finished = false;
794+
pollfds[0].events = POLLERR | POLLOUT | POLLHUP;
795+
}
763796
let mut buf = [0; READBUF];
764797
let start = Instant::now();
765798
let mut last_warning = Instant::now();
@@ -774,8 +807,28 @@ fn run_cmd(
774807
.unwrap_or(1000),
775808
)
776809
.unwrap_or(1000);
777-
if unsafe { poll((&mut pollfds) as *mut _ as *mut pollfd, 2, timeout) } != -1 {
778-
if pollfds[0].revents & POLLIN == POLLIN {
810+
if unsafe { poll((&mut pollfds) as *mut _ as *mut pollfd, 3, timeout) } != -1 {
811+
if pollfds[0].revents & POLLOUT == POLLOUT {
812+
// This unwrap() is safe as long as POLLOUT is removed from stdin's events when
813+
// stdin is closed.
814+
let stdin_str = test.stdin.as_ref().unwrap();
815+
if let Ok(i) = stdin.write(&stdin_str.as_bytes()[stdin_off..]) {
816+
stdin_off += i;
817+
}
818+
if stdin_off == stdin_str.len() {
819+
stdin_finished = true;
820+
// This is a bit icky, because the `stdin` variable will later close stdin when the
821+
// variable is dropped. However, some programs expect stdin to be closed before
822+
// they'll continue, so this is the least worst option.
823+
unsafe {
824+
close(stdin_fd);
825+
}
826+
// Remove POLLOUT from events so that we don't try reading anything again.
827+
pollfds[0].events = POLLERR | POLLHUP;
828+
}
829+
}
830+
831+
if pollfds[1].revents & POLLIN == POLLIN {
779832
if let Ok(i) = stderr.read(&mut buf) {
780833
if i > 0 {
781834
let utf8 = str::from_utf8(&buf[..i]).unwrap_or_else(|_| {
@@ -789,7 +842,7 @@ fn run_cmd(
789842
}
790843
}
791844

792-
if pollfds[1].revents & POLLIN == POLLIN {
845+
if pollfds[2].revents & POLLIN == POLLIN {
793846
if let Ok(i) = stdout.read(&mut buf) {
794847
if i > 0 {
795848
let utf8 = str::from_utf8(&buf[..i]).unwrap_or_else(|_| {
@@ -803,7 +856,10 @@ fn run_cmd(
803856
}
804857
}
805858

806-
if pollfds[0].revents & POLLHUP == POLLHUP && pollfds[1].revents & POLLHUP == POLLHUP {
859+
if (stdin_finished || pollfds[0].revents & POLLHUP == POLLHUP)
860+
&& pollfds[1].revents & POLLHUP == POLLHUP
861+
&& pollfds[2].revents & POLLHUP == POLLHUP
862+
{
807863
break;
808864
}
809865
}
@@ -861,7 +917,14 @@ fn run_cmd(
861917
}
862918
}
863919
};
864-
(status, cap_stderr, cap_stdout)
920+
921+
let stdin_remaining = if stdin_finished {
922+
0
923+
} else {
924+
let stdin_str = test.stdin.as_ref().unwrap();
925+
stdin_str.len() - stdin_off
926+
};
927+
(status, stdin_remaining, cap_stderr, cap_stdout)
865928
}
866929

867930
fn set_nonblock(fd: c_int) -> Result<(), io::Error> {

0 commit comments

Comments
 (0)