Skip to content

More verbose Debug implementation of std::process:Command #97176

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

Merged
merged 1 commit into from
Dec 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions library/std/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,15 @@ impl fmt::Debug for Command {
/// Format the program and arguments of a Command for display. Any
/// non-utf8 data is lossily converted using the utf8 replacement
/// character.
///
/// The default format approximates a shell invocation of the program along with its
/// arguments. It does not include most of the other command properties. The output is not guaranteed to work
/// (e.g. due to lack of shell-escaping or differences in path resolution)
/// On some platforms you can use [the alternate syntax] to show more fields.
///
/// Note that the debug implementation is platform-specific.
///
/// [the alternate syntax]: fmt#sign0
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
Expand Down
94 changes: 94 additions & 0 deletions library/std/src/process/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,100 @@ fn env_empty() {
assert!(p.is_ok());
}

#[test]
#[cfg(not(windows))]
#[cfg_attr(any(target_os = "emscripten", target_env = "sgx"), ignore)]
fn main() {
const PIDFD: &'static str =
if cfg!(target_os = "linux") { " create_pidfd: false,\n" } else { "" };

let mut command = Command::new("some-boring-name");

assert_eq!(format!("{command:?}"), format!(r#""some-boring-name""#));

assert_eq!(
format!("{command:#?}"),
format!(
r#"Command {{
program: "some-boring-name",
args: [
"some-boring-name",
],
{PIDFD}}}"#
)
);

command.args(&["1", "2", "3"]);

assert_eq!(format!("{command:?}"), format!(r#""some-boring-name" "1" "2" "3""#));

assert_eq!(
format!("{command:#?}"),
format!(
r#"Command {{
program: "some-boring-name",
args: [
"some-boring-name",
"1",
"2",
"3",
],
{PIDFD}}}"#
)
);

crate::os::unix::process::CommandExt::arg0(&mut command, "exciting-name");

assert_eq!(
format!("{command:?}"),
format!(r#"["some-boring-name"] "exciting-name" "1" "2" "3""#)
);

assert_eq!(
format!("{command:#?}"),
format!(
r#"Command {{
program: "some-boring-name",
args: [
"exciting-name",
"1",
"2",
"3",
],
{PIDFD}}}"#
)
);

let mut command_with_env_and_cwd = Command::new("boring-name");
command_with_env_and_cwd.current_dir("/some/path").env("FOO", "bar");
assert_eq!(
format!("{command_with_env_and_cwd:?}"),
r#"cd "/some/path" && FOO="bar" "boring-name""#
);
assert_eq!(
format!("{command_with_env_and_cwd:#?}"),
format!(
r#"Command {{
program: "boring-name",
args: [
"boring-name",
],
env: CommandEnv {{
clear: false,
vars: {{
"FOO": Some(
"bar",
),
}},
}},
cwd: Some(
"/some/path",
),
{PIDFD}}}"#
)
);
}

// See issue #91991
#[test]
#[cfg(windows)]
Expand Down
67 changes: 60 additions & 7 deletions library/std/src/sys/unix/process/process_common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ pub enum ChildStdio {
Null,
}

#[derive(Debug)]
pub enum Stdio {
Inherit,
Null,
Expand Down Expand Up @@ -510,16 +511,68 @@ impl ChildStdio {
}

impl fmt::Debug for Command {
// show all attributes but `self.closures` which does not implement `Debug`
// and `self.argv` which is not useful for debugging
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.program != self.args[0] {
write!(f, "[{:?}] ", self.program)?;
}
write!(f, "{:?}", self.args[0])?;
if f.alternate() {
let mut debug_command = f.debug_struct("Command");
debug_command.field("program", &self.program).field("args", &self.args);
if !self.env.is_unchanged() {
debug_command.field("env", &self.env);
}

if self.cwd.is_some() {
debug_command.field("cwd", &self.cwd);
}
if self.uid.is_some() {
debug_command.field("uid", &self.uid);
}
if self.gid.is_some() {
debug_command.field("gid", &self.gid);
}

if self.groups.is_some() {
debug_command.field("groups", &self.groups);
}

if self.stdin.is_some() {
debug_command.field("stdin", &self.stdin);
}
if self.stdout.is_some() {
debug_command.field("stdout", &self.stdout);
}
if self.stderr.is_some() {
debug_command.field("stderr", &self.stderr);
}
if self.pgroup.is_some() {
debug_command.field("pgroup", &self.pgroup);
}

#[cfg(target_os = "linux")]
{
debug_command.field("create_pidfd", &self.create_pidfd);
}

for arg in &self.args[1..] {
write!(f, " {:?}", arg)?;
debug_command.finish()
} else {
if let Some(ref cwd) = self.cwd {
write!(f, "cd {cwd:?} && ")?;
}
for (key, value_opt) in self.get_envs() {
if let Some(value) = value_opt {
write!(f, "{}={value:?} ", key.to_string_lossy())?;
}
}
if self.program != self.args[0] {
write!(f, "[{:?}] ", self.program)?;
}
write!(f, "{:?}", self.args[0])?;

for arg in &self.args[1..] {
write!(f, " {:?}", arg)?;
}
Ok(())
}
Ok(())
}
}

Expand Down
11 changes: 10 additions & 1 deletion library/std/src/sys_common/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@
use crate::collections::BTreeMap;
use crate::env;
use crate::ffi::{OsStr, OsString};
use crate::fmt;
use crate::io;
use crate::sys::pipe::read2;
use crate::sys::process::{EnvKey, ExitStatus, Process, StdioPipes};

// Stores a set of changes to an environment
#[derive(Clone, Debug)]
#[derive(Clone)]
pub struct CommandEnv {
clear: bool,
saw_path: bool,
Expand All @@ -22,6 +23,14 @@ impl Default for CommandEnv {
}
}

impl fmt::Debug for CommandEnv {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut debug_command_env = f.debug_struct("CommandEnv");
debug_command_env.field("clear", &self.clear).field("vars", &self.vars);
debug_command_env.finish()
}
}

impl CommandEnv {
// Capture the current environment with these changes applied
pub fn capture(&self) -> BTreeMap<EnvKey, OsString> {
Expand Down
21 changes: 0 additions & 21 deletions src/test/ui/command/command-argv0-debug.rs

This file was deleted.