Skip to content

Commit 6b0abb8

Browse files
author
Pietro Albini
authored
Merge pull request #20 from Zeegomo/actions
2 parents d3022e2 + d5e470b commit 6b0abb8

File tree

9 files changed

+142
-24
lines changed

9 files changed

+142
-24
lines changed

CHANGELOG.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55

66
## Unreleased
77

8+
### Added
9+
10+
- New struct `cmd::ProcessLinesActions` to expose actions available while reading live output from a command.
11+
812
### Changed
913

10-
- The file `.cargo/config` will be removed before starting the build
14+
- **BREAKING**: `Command::process_lines` now accepts a `FnMut(&str, &mut ProcessLinesActions)`.
15+
- The file `.cargo/config` will be removed before starting the build.
1116

1217
## [0.6.1] - 2020-05-04
1318

src/cmd/mod.rs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
//! Command execution and sandboxing.
22
3+
mod process_lines_actions;
34
mod sandbox;
45

6+
pub use process_lines_actions::ProcessLinesActions;
57
pub use sandbox::*;
68

79
use crate::native;
810
use crate::workspace::Workspace;
911
use failure::{Error, Fail};
1012
use futures::{future, Future, Stream};
1113
use log::{error, info};
14+
use process_lines_actions::InnerState;
1215
use std::convert::AsRef;
1316
use std::env::consts::EXE_SUFFIX;
1417
use std::ffi::{OsStr, OsString};
@@ -125,7 +128,7 @@ pub struct Command<'w, 'pl> {
125128
binary: Binary,
126129
args: Vec<OsString>,
127130
env: Vec<(OsString, OsString)>,
128-
process_lines: Option<&'pl mut dyn FnMut(&str)>,
131+
process_lines: Option<&'pl mut dyn FnMut(&str, &mut ProcessLinesActions)>,
129132
cd: Option<PathBuf>,
130133
timeout: Option<Duration>,
131134
no_output_timeout: Option<Duration>,
@@ -240,7 +243,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
240243
/// let mut ice = false;
241244
/// Command::new(&workspace, "cargo")
242245
/// .args(&["build", "--all"])
243-
/// .process_lines(&mut |line| {
246+
/// .process_lines(&mut |line, _| {
244247
/// if line.contains("internal compiler error") {
245248
/// ice = true;
246249
/// }
@@ -249,7 +252,7 @@ impl<'w, 'pl> Command<'w, 'pl> {
249252
/// # Ok(())
250253
/// # }
251254
/// ```
252-
pub fn process_lines(mut self, f: &'pl mut dyn FnMut(&str)) -> Self {
255+
pub fn process_lines(mut self, f: &'pl mut dyn FnMut(&str, &mut ProcessLinesActions)) -> Self {
253256
self.process_lines = Some(f);
254257
self
255258
}
@@ -480,7 +483,7 @@ impl OutputKind {
480483

481484
fn log_command(
482485
mut cmd: StdCommand,
483-
mut process_lines: Option<&mut dyn FnMut(&str)>,
486+
mut process_lines: Option<&mut dyn FnMut(&str, &mut ProcessLinesActions)>,
484487
capture: bool,
485488
timeout: Option<Duration>,
486489
no_output_timeout: Option<Duration>,
@@ -512,6 +515,7 @@ fn log_command(
512515
.map(|line| (OutputKind::Stderr, line));
513516

514517
let start = Instant::now();
518+
let mut actions = ProcessLinesActions::new();
515519

516520
let output = stdout
517521
.select(stderr)
@@ -533,21 +537,31 @@ fn log_command(
533537
return future::err(Error::from(CommandError::Timeout(timeout.as_secs())));
534538
}
535539

540+
if let Some(f) = &mut process_lines {
541+
f(&line, &mut actions);
542+
}
543+
// this is done here to avoid duplicating the output line
544+
let lines = match actions.take_lines() {
545+
InnerState::Removed => Vec::new(),
546+
InnerState::Original => vec![line],
547+
InnerState::Replaced(new_lines) => new_lines,
548+
};
549+
536550
if log_output {
537-
info!("[{}] {}", kind.prefix(), line);
551+
for line in &lines {
552+
info!("[{}] {}", kind.prefix(), line);
553+
}
538554
}
539-
future::ok((kind, line))
555+
556+
future::ok((kind, lines))
540557
})
541558
.fold(
542559
(Vec::new(), Vec::new()),
543-
move |mut res, (kind, line)| -> Result<_, Error> {
544-
if let Some(f) = &mut process_lines {
545-
f(&line);
546-
}
560+
move |mut res, (kind, mut lines)| -> Result<_, Error> {
547561
if capture {
548562
match kind {
549-
OutputKind::Stdout => res.0.push(line),
550-
OutputKind::Stderr => res.1.push(line),
563+
OutputKind::Stdout => res.0.append(&mut lines),
564+
OutputKind::Stderr => res.1.append(&mut lines),
551565
}
552566
}
553567
Ok(res)

src/cmd/process_lines_actions.rs

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use std::default::Default;
2+
3+
#[cfg_attr(test, derive(Debug, PartialEq))]
4+
pub(super) enum InnerState {
5+
Removed,
6+
Original,
7+
Replaced(Vec<String>),
8+
}
9+
10+
impl Default for InnerState {
11+
fn default() -> Self {
12+
InnerState::Original
13+
}
14+
}
15+
16+
/// Represents actions that are available while reading live output from a process.
17+
///
18+
/// This will be available inside the function you provide to [`Command::process_lines`](struct.Command.html#method.process_lines)
19+
pub struct ProcessLinesActions {
20+
state: InnerState,
21+
}
22+
23+
impl<'a> ProcessLinesActions {
24+
pub(super) fn new() -> Self {
25+
ProcessLinesActions {
26+
state: InnerState::default(),
27+
}
28+
}
29+
30+
pub(super) fn take_lines(&mut self) -> InnerState {
31+
std::mem::take(&mut self.state)
32+
}
33+
34+
/// Replace last read line from output with the lines provided.
35+
///
36+
/// The new lines will be logged instead of the original line.
37+
pub fn replace_with_lines(&mut self, new_lines: impl Iterator<Item = &'a str>) {
38+
self.state = InnerState::Replaced(new_lines.map(|str| str.to_string()).collect());
39+
}
40+
41+
/// Remove last read line from output.
42+
///
43+
/// This means that the line will not be logged.
44+
pub fn remove_line(&mut self) {
45+
self.state = InnerState::Removed;
46+
}
47+
}
48+
49+
#[cfg(test)]
50+
mod test {
51+
use super::InnerState;
52+
use super::ProcessLinesActions;
53+
#[test]
54+
fn test_replace() {
55+
let mut actions = ProcessLinesActions::new();
56+
57+
actions.replace_with_lines("ipsum".split("\n"));
58+
assert_eq!(
59+
actions.take_lines(),
60+
InnerState::Replaced(vec!["ipsum".to_string()])
61+
);
62+
63+
actions.replace_with_lines("lorem ipsum dolor".split(" "));
64+
assert_eq!(
65+
actions.take_lines(),
66+
InnerState::Replaced(vec![
67+
"lorem".to_string(),
68+
"ipsum".to_string(),
69+
"dolor".to_string()
70+
])
71+
);
72+
73+
// assert last input is discarded
74+
assert_eq!(actions.take_lines(), InnerState::Original);
75+
}
76+
77+
#[test]
78+
fn test_remove() {
79+
let mut actions = ProcessLinesActions::new();
80+
actions.remove_line();
81+
assert_eq!(actions.take_lines(), InnerState::Removed);
82+
}
83+
84+
#[test]
85+
fn test_no_actions() {
86+
let mut actions = ProcessLinesActions::new();
87+
assert_eq!(actions.take_lines(), InnerState::Original);
88+
}
89+
}

src/cmd/sandbox.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::cmd::{Command, CommandError};
1+
use crate::cmd::{Command, CommandError, ProcessLinesActions};
22
use crate::Workspace;
33
use failure::Error;
44
use log::{error, info};
@@ -262,7 +262,7 @@ impl SandboxBuilder {
262262
workspace: &Workspace,
263263
timeout: Option<Duration>,
264264
no_output_timeout: Option<Duration>,
265-
process_lines: Option<&mut dyn FnMut(&str)>,
265+
process_lines: Option<&mut dyn FnMut(&str, &mut ProcessLinesActions)>,
266266
) -> Result<(), Error> {
267267
let container = self.create(workspace)?;
268268

@@ -324,7 +324,7 @@ impl Container<'_> {
324324
&self,
325325
timeout: Option<Duration>,
326326
no_output_timeout: Option<Duration>,
327-
process_lines: Option<&mut dyn FnMut(&str)>,
327+
process_lines: Option<&mut dyn FnMut(&str, &mut ProcessLinesActions)>,
328328
) -> Result<(), Error> {
329329
let mut cmd = Command::new(self.workspace, "docker")
330330
.args(&["start", "-a", &self.id])

src/crates/git.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use super::CrateTrait;
2-
use crate::cmd::Command;
2+
use crate::cmd::{Command, ProcessLinesActions};
33
use crate::prepare::PrepareError;
44
use crate::Workspace;
55
use failure::{Error, ResultExt};
@@ -83,7 +83,7 @@ impl CrateTrait for GitRepo {
8383
// fata: credential helper '{path}' told us to quit
8484
//
8585
let mut private_repository = false;
86-
let mut detect_private_repositories = |line: &str| {
86+
let mut detect_private_repositories = |line: &str, _actions: &mut ProcessLinesActions| {
8787
if line.starts_with("fatal: credential helper") && line.ends_with("told us to quit") {
8888
private_repository = true;
8989
}

src/prepare.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ impl<'a> Prepare<'a> {
109109
}
110110
let res = cmd
111111
.cd(self.source_dir)
112-
.process_lines(&mut |line| {
112+
.process_lines(&mut |line, _| {
113113
if line.contains("failed to select a version for the requirement") {
114114
yanked_deps = true;
115115
}
@@ -130,7 +130,7 @@ impl<'a> Prepare<'a> {
130130
let res = Command::new(self.workspace, self.toolchain.cargo())
131131
.args(&["fetch", "--locked", "--manifest-path", "Cargo.toml"])
132132
.cd(&self.source_dir)
133-
.process_lines(&mut |line| {
133+
.process_lines(&mut |line, _| {
134134
if line.ends_with(
135135
"Cargo.lock needs to be updated but --locked was passed to prevent this",
136136
) {

src/toolchain.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ impl Toolchain {
324324
let result = Command::new(workspace, &RUSTUP)
325325
.args(&[thing.as_str(), "list", "--installed", "--toolchain", name])
326326
.log_output(false)
327-
.process_lines(&mut |line| {
327+
.process_lines(&mut |line, _| {
328328
if line.starts_with("error: toolchain ") && line.ends_with(" is not installed") {
329329
not_installed = true;
330330
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
fn main() {
22
println!("Hello, world!");
3+
println!("Hello, world again!");
34
}

tests/buildtest/mod.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use failure::Error;
22
use log::LevelFilter;
3-
use rustwide::cmd::SandboxBuilder;
3+
use rustwide::cmd::{ProcessLinesActions, SandboxBuilder};
44

55
#[macro_use]
66
mod runner;
@@ -17,6 +17,9 @@ fn test_hello_world() {
1717
})?;
1818

1919
assert!(storage.to_string().contains("[stdout] Hello, world!\n"));
20+
assert!(storage
21+
.to_string()
22+
.contains("[stdout] Hello, world again!\n"));
2023
Ok(())
2124
})?;
2225
Ok(())
@@ -32,9 +35,12 @@ fn test_process_lines() {
3235
rustwide::logging::capture(&storage, || -> Result<_, Error> {
3336
build
3437
.cargo()
35-
.process_lines(&mut |line: &str| {
36-
if line.contains("Hello, world!") {
38+
.process_lines(&mut |line: &str, actions: &mut ProcessLinesActions| {
39+
if line.contains("Hello, world again!") {
3740
ex = true;
41+
actions.replace_with_lines(line.split(","));
42+
} else if line.contains("Hello, world!") {
43+
actions.remove_line();
3844
}
3945
})
4046
.args(&["run"])
@@ -43,6 +49,9 @@ fn test_process_lines() {
4349
})?;
4450

4551
assert!(ex);
52+
assert!(!storage.to_string().contains("[stdout] Hello, world!\n"));
53+
assert!(storage.to_string().contains("[stdout] world again!\n"));
54+
assert!(storage.to_string().contains("[stdout] Hello\n"));
4655
Ok(())
4756
})?;
4857
Ok(())

0 commit comments

Comments
 (0)