Repository: uutils/procps
Pull request #208 (2024-09-04 08:31:07)

pmap: add support for multiple PIDs
This PR adds support for multiple PIDs so that it can be called like `pmap <PID_A> <PID_B>`.


Patch:
diff --git a/src/uu/pmap/src/pmap.rs b/src/uu/pmap/src/pmap.rs
index ed7929e..53dc79f 100644
--- a/src/uu/pmap/src/pmap.rs
+++ b/src/uu/pmap/src/pmap.rs
@@ -7,8 +7,8 @@ use clap::{crate_version, Arg, ArgAction, Command};
 use std::env;
 use std::fs;
 use std::io::Error;
-use std::process;
-use uucore::{error::UResult, format_usage, help_about, help_usage};
+use uucore::error::{set_exit_code, UResult};
+use uucore::{format_usage, help_about, help_usage};
 
 const ABOUT: &str = help_about!("pmap.md");
 const USAGE: &str = help_usage!("pmap.md");
@@ -16,21 +16,24 @@ const USAGE: &str = help_usage!("pmap.md");
 #[uucore::main]
 pub fn uumain(args: impl uucore::Args) -> UResult<()> {
     let matches = uu_app().try_get_matches_from(args)?;
-    let pid = matches.get_one::<String>("pid").expect("PID required");
+    let pids = matches.get_many::<String>("pid").expect("PID required");
 
-    match parse_cmdline(pid) {
-        Ok(cmdline) => {
-            println!("{}:   {}", pid, cmdline);
+    for pid in pids {
+        match parse_cmdline(pid) {
+            Ok(cmdline) => {
+                println!("{pid}:   {cmdline}");
+            }
+            Err(_) => {
+                set_exit_code(42);
+                continue;
+            }
         }
-        Err(_) => {
-            process::exit(42);
-        }
-    }
 
-    match parse_maps(pid) {
-        Ok(total) => println!(" total {total:>16}K"),
-        Err(_) => {
-            process::exit(1);
+        match parse_maps(pid) {
+            Ok(total) => println!(" total {total:>16}K"),
+            Err(_) => {
+                set_exit_code(1);
+            }
         }
     }
 
@@ -134,7 +137,7 @@ pub fn uu_app() -> Command {
             Arg::new("pid")
                 .help("Process ID")
                 .required_unless_present_any(["create-rc", "create-rc-to"]) // Adjusted for -n, -N note
-                .action(ArgAction::Set)
+                .action(ArgAction::Append)
                 .conflicts_with_all(["create-rc", "create-rc-to"]),
         ) // Ensure pid is not used with -n, -N
         .arg(


Test patch:
diff --git a/tests/by-util/test_pmap.rs b/tests/by-util/test_pmap.rs
index bd91be0..fab05f8 100644
--- a/tests/by-util/test_pmap.rs
+++ b/tests/by-util/test_pmap.rs
@@ -4,6 +4,12 @@
 // file that was distributed with this source code.
 
 use crate::common::util::TestScenario;
+#[cfg(target_os = "linux")]
+use regex::Regex;
+#[cfg(target_os = "linux")]
+use std::process;
+
+const NON_EXISTING_PID: &str = "999999";
 
 #[test]
 fn test_no_args() {
@@ -13,36 +19,81 @@ fn test_no_args() {
 #[test]
 #[cfg(target_os = "linux")]
 fn test_existing_pid() {
-    use std::process;
+    let pid = process::id();
 
-    use regex::Regex;
+    let result = new_ucmd!()
+        .arg(pid.to_string())
+        .succeeds()
+        .stdout_move_str();
 
+    assert_format(pid, &result);
+}
+
+#[test]
+#[cfg(target_os = "linux")]
+fn test_multiple_existing_pids() {
     let pid = process::id();
 
     let result = new_ucmd!()
+        .arg(pid.to_string())
         .arg(pid.to_string())
         .succeeds()
         .stdout_move_str();
 
-    let (first_line, rest) = result.split_once('\n').unwrap();
-    let re = Regex::new(&format!("^{pid}:   .+[^ ]$")).unwrap();
-    assert!(re.is_match(first_line));
+    let result: Vec<_> = result.lines().collect();
+    let (left, right) = result.split_at(result.len() / 2);
 
-    let rest = rest.trim_end();
-    let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
-    let re = Regex::new("(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)- (  $$[ (anon|stack) $$]|[a-zA-Z0-9._-]+)$").unwrap();
-    assert!(re.is_match(memory_map));
+    assert_format(pid, &left.join("\n"));
+    assert_format(pid, &right.join("\n"));
+}
 
-    let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap();
-    assert!(re.is_match(last_line));
+#[test]
+#[cfg(target_os = "linux")]
+fn test_non_existing_and_existing_pid() {
+    let pid = process::id();
+
+    let result = new_ucmd!()
+        .arg(NON_EXISTING_PID)
+        .arg(pid.to_string())
+        .fails();
+    let result = result.code_is(42).no_stderr().stdout_str();
+
+    assert_format(pid, result);
 }
 
 #[test]
 fn test_non_existing_pid() {
-    new_ucmd!().arg("999999").fails().code_is(42).no_output();
+    new_ucmd!()
+        .arg(NON_EXISTING_PID)
+        .fails()
+        .code_is(42)
+        .no_output();
 }
 
 #[test]
 fn test_invalid_arg() {
     new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
 }
+
+// Ensure `s` has the following format:
+//
+// 1234:   /some/path
+// 00007ff01c6aa000      8K rw--- ld-linux-x86-64.so.2
+// 00007fffa80a6000    132K rw---   [ stack ]
+// ffffffffff600000      4K --x--   [ anon ]
+// ...
+//  total          1040320K
+#[cfg(target_os = "linux")]
+fn assert_format(pid: u32, s: &str) {
+    let (first_line, rest) = s.split_once('\n').unwrap();
+    let re = Regex::new(&format!("^{pid}:   .+[^ ]$")).unwrap();
+    assert!(re.is_match(first_line));
+
+    let rest = rest.trim_end();
+    let (memory_map, last_line) = rest.rsplit_once('\n').unwrap();
+    let re = Regex::new("(?m)^[0-9a-f]{16} +[1-9][0-9]*K (-|r)(-|w)(-|x)(-|s)- (  $$[ (anon|stack) $$]|[a-zA-Z0-9._-]+)$").unwrap();
+    assert!(re.is_match(memory_map));
+
+    let re = Regex::new("^ total +[1-9][0-9]*K$").unwrap();
+    assert!(re.is_match(last_line));
+}