Skip to content

Commit 379a38c

Browse files
committed
reduce code duplication and join in reader threads cleanly
1 parent 3d93920 commit 379a38c

File tree

3 files changed

+88
-65
lines changed

3 files changed

+88
-65
lines changed

tests/by-util/test_env.rs

+28-15
Original file line numberDiff line numberDiff line change
@@ -250,13 +250,15 @@ fn test_fail_change_directory() {
250250
fn test_simulation_of_terminal_false() {
251251
let scene = TestScenario::new(util_name!());
252252

253-
let out = scene
254-
.ucmd()
255-
.arg("sh")
256-
.arg("is_atty.sh")
257-
.succeeds();
258-
assert_eq!(String::from_utf8_lossy(out.stdout()), "stdin is not atty\nstdout is not atty\nstderr is not atty\n");
259-
assert_eq!(String::from_utf8_lossy(out.stderr()), "This is an error message.\n");
253+
let out = scene.ucmd().arg("sh").arg("is_atty.sh").succeeds();
254+
assert_eq!(
255+
String::from_utf8_lossy(out.stdout()),
256+
"stdin is not atty\nstdout is not atty\nstderr is not atty\n"
257+
);
258+
assert_eq!(
259+
String::from_utf8_lossy(out.stderr()),
260+
"This is an error message.\n"
261+
);
260262
}
261263

262264
#[test]
@@ -269,8 +271,14 @@ fn test_simulation_of_terminal_true() {
269271
.arg("is_atty.sh")
270272
.terminal_simulation(true)
271273
.succeeds();
272-
assert_eq!(String::from_utf8_lossy(out.stdout()), "stdin is atty\r\nstdout is atty\r\nstderr is atty\r\n");
273-
assert_eq!(String::from_utf8_lossy(out.stderr()), "This is an error message.\r\n");
274+
assert_eq!(
275+
String::from_utf8_lossy(out.stdout()),
276+
"stdin is atty\r\nstdout is atty\r\nstderr is atty\r\n"
277+
);
278+
assert_eq!(
279+
String::from_utf8_lossy(out.stderr()),
280+
"This is an error message.\r\n"
281+
);
274282
}
275283

276284
#[test]
@@ -288,23 +296,26 @@ fn test_simulation_of_terminal_pty_sends_eot_automatically() {
288296
assert_eq!(String::from_utf8_lossy(out.stderr()), "");
289297
}
290298

291-
292299
#[test]
293300
fn test_simulation_of_terminal_pty_pipes_into_data_and_sends_eot_automatically() {
294301
let scene = TestScenario::new(util_name!());
295302

303+
let message = "Hello stdin forwarding!";
304+
296305
let mut cmd = scene.ucmd();
297306
cmd.args(&["cat", "-"]);
298307
cmd.terminal_simulation(true);
299-
cmd.pipe_in("Hello stdin forwarding!");
308+
cmd.pipe_in(message);
300309
let child = cmd.run_no_wait();
301310
let out = child.wait().unwrap();
302311

303-
assert_eq!(String::from_utf8_lossy(out.stdout()), "Hello stdin forwarding!\r\n");
312+
assert_eq!(
313+
String::from_utf8_lossy(out.stdout()),
314+
format!("{}\r\n", message)
315+
);
304316
assert_eq!(String::from_utf8_lossy(out.stderr()), "");
305317
}
306318

307-
308319
#[test]
309320
fn test_simulation_of_terminal_pty_write_in_data_and_sends_eot_automatically() {
310321
let scene = TestScenario::new(util_name!());
@@ -316,7 +327,9 @@ fn test_simulation_of_terminal_pty_write_in_data_and_sends_eot_automatically() {
316327
child.write_in("Hello stdin forwarding via write_in!");
317328
let out = child.wait().unwrap();
318329

319-
assert_eq!(String::from_utf8_lossy(out.stdout()), "Hello stdin forwarding via write_in!\r\n");
330+
assert_eq!(
331+
String::from_utf8_lossy(out.stdout()),
332+
"Hello stdin forwarding via write_in!\r\n"
333+
);
320334
assert_eq!(String::from_utf8_lossy(out.stderr()), "");
321335
}
322-

tests/by-util/test_nohup.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ fn test_nohup_multiple_args_and_flags() {
4141
target_vendor = "apple"
4242
))]
4343
fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced() {
44-
4544
let ts = TestScenario::new(util_name!());
46-
let result = ts.ucmd()
45+
let result = ts
46+
.ucmd()
4747
.terminal_simulation(true)
4848
.args(&["sh", "is_atty.sh"])
4949
.succeeds();
@@ -61,4 +61,3 @@ fn test_nohup_with_pseudo_terminal_emulation_on_stdin_stdout_stderr_get_replaced
6161
"stdin is not atty\nstdout is not atty\nstderr is not atty\n"
6262
);
6363
}
64-

tests/common/util.rs

+58-47
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized
6+
//spell-checker: ignore (linux) rlimit prlimit coreutil ggroups uchild uncaptured scmd SHLVL canonicalized openpty
77

88
#![allow(dead_code)]
99

@@ -38,7 +38,7 @@ use std::rc::Rc;
3838
use std::sync::mpsc::{self, RecvTimeoutError};
3939
use std::thread::{sleep, JoinHandle};
4040
use std::time::{Duration, Instant};
41-
use std::{env, hint, thread, mem};
41+
use std::{env, hint, mem, thread};
4242
use tempfile::{Builder, TempDir};
4343

4444
static TESTS_DIR: &str = "tests";
@@ -1429,6 +1429,32 @@ impl UCommand {
14291429
}
14301430
}
14311431

1432+
fn spawn_reader_thread(
1433+
&self,
1434+
captured_output: Option<CapturedOutput>,
1435+
pty_fd_master: OwnedFd,
1436+
name: String,
1437+
) -> Option<CapturedOutput> {
1438+
if let Some(mut captured_output_i) = captured_output {
1439+
let fd = captured_output_i.try_clone().unwrap();
1440+
1441+
let handle = std::thread::Builder::new()
1442+
.name(name)
1443+
.spawn(move || {
1444+
let mut buffer_out = String::default();
1445+
Self::read_string_from_pty(pty_fd_master, &mut buffer_out);
1446+
let mut out = std::fs::File::from(fd);
1447+
out.write_all(buffer_out.as_bytes()).unwrap();
1448+
})
1449+
.unwrap();
1450+
1451+
captured_output_i.reader_thread_handle = Some(handle);
1452+
Some(captured_output_i)
1453+
} else {
1454+
None
1455+
}
1456+
}
1457+
14321458
/// Build the `std::process::Command` and apply the defaults on fields which were not specified
14331459
/// by the user.
14341460
///
@@ -1448,7 +1474,14 @@ impl UCommand {
14481474
/// * `stderr_to_stdout`: `false`
14491475
/// * `bytes_into_stdin`: `None`
14501476
/// * `limits`: `None`.
1451-
fn build(&mut self) -> (Command, Option<CapturedOutput>, Option<CapturedOutput>, Option<OwnedFd>) {
1477+
fn build(
1478+
&mut self,
1479+
) -> (
1480+
Command,
1481+
Option<CapturedOutput>,
1482+
Option<CapturedOutput>,
1483+
Option<OwnedFd>,
1484+
) {
14521485
if self.bin_path.is_some() {
14531486
if let Some(util_name) = &self.util_name {
14541487
self.args.push_front(util_name.into());
@@ -1562,7 +1595,6 @@ impl UCommand {
15621595
};
15631596

15641597
if self.terminal_simulation {
1565-
15661598
let terminal_size = libc::winsize {
15671599
ws_col: 80,
15681600
ws_row: 30,
@@ -1585,41 +1617,12 @@ impl UCommand {
15851617

15861618
stdin_pty = Some(pi_master);
15871619

1588-
if let Some(mut captured_stdout_i) = captured_stdout {
1589-
let fd = captured_stdout_i.try_clone().unwrap();
1590-
1591-
std::thread::Builder::new()
1592-
.name("fwd_stdout".to_string())
1593-
.spawn(move ||{
1594-
let mut buffer_out = String::default();
1595-
Self::read_string_from_pty(po_master, &mut buffer_out);
1596-
let mut out = std::fs::File::from(fd);
1597-
out.write_all(buffer_out.as_bytes()).unwrap();
1598-
//std::mem::forget(_pi_master); // _pi_master needs to be kept alive otherwise stdin.is_terminal() fails
1599-
}).unwrap();
1600-
1601-
captured_stdout = Some(captured_stdout_i);
1602-
}
1603-
1604-
if let Some(mut captured_stderr_i) = captured_stderr {
1605-
let fd = captured_stderr_i.try_clone().unwrap();
1606-
1607-
std::thread::Builder::new()
1608-
.name("fwd_stderr".to_string())
1609-
.spawn(move ||{
1610-
let mut buffer_out = String::default();
1611-
Self::read_string_from_pty(pe_master, &mut buffer_out);
1612-
let mut out = std::fs::File::from(fd);
1613-
out.write_all(buffer_out.as_bytes()).unwrap();
1614-
}).unwrap();
1615-
1616-
captured_stderr = Some(captured_stderr_i);
1617-
}
1620+
captured_stdout =
1621+
self.spawn_reader_thread(captured_stdout, po_master, "stdout_reader".to_string());
1622+
captured_stderr =
1623+
self.spawn_reader_thread(captured_stderr, pe_master, "stderr_reader".to_string());
16181624

1619-
command
1620-
.stdin(pi_slave)
1621-
.stdout(po_slave)
1622-
.stderr(pe_slave);
1625+
command.stdin(pi_slave).stdout(po_slave).stderr(pe_slave);
16231626
}
16241627

16251628
(command, captured_stdout, captured_stderr, stdin_pty)
@@ -1631,10 +1634,7 @@ impl UCommand {
16311634
assert!(!self.has_run, "{}", ALREADY_RUN);
16321635
self.has_run = true;
16331636

1634-
let (mut command,
1635-
captured_stdout,
1636-
captured_stderr,
1637-
stdin_pty) = self.build();
1637+
let (mut command, captured_stdout, captured_stderr, stdin_pty) = self.build();
16381638
log_info("run", self.to_string());
16391639

16401640
let child = command.spawn().unwrap();
@@ -1715,6 +1715,7 @@ impl std::fmt::Display for UCommand {
17151715
struct CapturedOutput {
17161716
current_file: File,
17171717
output: tempfile::NamedTempFile, // drop last
1718+
reader_thread_handle: Option<thread::JoinHandle<()>>,
17181719
}
17191720

17201721
impl CapturedOutput {
@@ -1723,6 +1724,7 @@ impl CapturedOutput {
17231724
Self {
17241725
current_file: output.reopen().unwrap(),
17251726
output,
1727+
reader_thread_handle: None,
17261728
}
17271729
}
17281730

@@ -1799,6 +1801,7 @@ impl Default for CapturedOutput {
17991801
Self {
18001802
current_file: file.reopen().unwrap(),
18011803
output: file,
1804+
reader_thread_handle: None,
18021805
}
18031806
}
18041807
}
@@ -1946,7 +1949,7 @@ impl UChild {
19461949
child: Child,
19471950
captured_stdout: Option<CapturedOutput>,
19481951
captured_stderr: Option<CapturedOutput>,
1949-
stdin_pty: Option<OwnedFd>
1952+
stdin_pty: Option<OwnedFd>,
19501953
) -> Self {
19511954
Self {
19521955
raw: child,
@@ -2095,7 +2098,6 @@ impl UChild {
20952098
/// error.
20962099
#[deprecated = "Please use wait() -> io::Result<CmdResult> instead."]
20972100
pub fn wait_with_output(mut self) -> io::Result<Output> {
2098-
20992101
// some apps do not stop execution until their stdin gets closed.
21002102
// to prevent a endless waiting here, we close the stdin.
21012103
self.join(); // ensure that all pending async input is piped in
@@ -2107,7 +2109,8 @@ impl UChild {
21072109
let (sender, receiver) = mpsc::channel();
21082110
let handle = thread::Builder::new()
21092111
.name("wait_with_output".to_string())
2110-
.spawn(move || sender.send(child.wait_with_output())).unwrap();
2112+
.spawn(move || sender.send(child.wait_with_output()))
2113+
.unwrap();
21112114

21122115
match receiver.recv_timeout(timeout) {
21132116
Ok(result) => {
@@ -2139,9 +2142,17 @@ impl UChild {
21392142
};
21402143

21412144
if let Some(stdout) = self.captured_stdout.as_mut() {
2145+
stdout
2146+
.reader_thread_handle
2147+
.take()
2148+
.map(|handle| handle.join().unwrap());
21422149
output.stdout = stdout.output_bytes();
21432150
}
21442151
if let Some(stderr) = self.captured_stderr.as_mut() {
2152+
stderr
2153+
.reader_thread_handle
2154+
.take()
2155+
.map(|handle| handle.join().unwrap());
21452156
output.stderr = stderr.output_bytes();
21462157
}
21472158

@@ -2346,7 +2357,6 @@ impl UChild {
23462357
///
23472358
/// [`JoinHandle`]: std::thread::JoinHandle
23482359
pub fn pipe_in<T: Into<Vec<u8>>>(&mut self, content: T) -> &mut Self {
2349-
23502360
let ignore_stdin_write_error = self.ignore_stdin_write_error;
23512361
let mut content: Vec<u8> = content.into();
23522362
if self.stdin_pty.is_some() {
@@ -2369,7 +2379,8 @@ impl UChild {
23692379
Ok(()) | Err(_) => Ok(()),
23702380
};
23712381
result
2372-
}).unwrap();
2382+
})
2383+
.unwrap();
23732384

23742385
self.join_handle = Some(join_handle);
23752386
self

0 commit comments

Comments
 (0)