Skip to content

Commit

Permalink
Minimal platform support (#1)
Browse files Browse the repository at this point in the history
* Add another process

* List processes

* Print before exit

* Print before exit and show processes after expected launch

* Log input when waiter receives it

* Explicitly make stdin piped

* Get to the tests quicker

* stdin piped, out inherit

* Don't build binaries during test

* Use prebuilt binaries

* Tidy

* Re-enable all CI checks

* Fix resolve_pid include logic and remove unused deps

* Ensure retry is included

* Check formatting

* Add a toolchain definition

* Update Linux only tests

* Remove unwraps that aren't needed

* Fix lint errors

* Lint errors

* Lint with default features

* Add cache
  • Loading branch information
ThetaSinner authored Oct 28, 2024
1 parent 7fb6787 commit ac08483
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 50 deletions.
16 changes: 13 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,24 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Build
run: cargo build --release
- uses: Swatinem/rust-cache@v2

- name: Check formatting
run: cargo fmt --all -- --check

- name: Lint
run: cargo clippy --all-features --all-targets -- -Dwarnings
run: |
cargo clippy --all-targets -- -Dwarnings
cargo clippy --no-default-features --all-targets -- -Dwarnings
cargo clippy --features resilience --all-targets -- -Dwarnings
cargo clippy --features async --all-targets -- -Dwarnings
cargo clippy --all-features --all-targets -- -Dwarnings
- name: Run tests
run: |-
# Create test binaries
cargo build --release --bins
cargo test -- --test-threads=1
cargo test --no-default-features --test lib_test -- --test-threads=1
cargo test --features resilience -- --test-threads=1
Expand Down
14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ repository = "https://github.com/EphyraSoftware/proc-ctl"
[[bin]]
name = "port-binder"
path = "./sample/port-binder/main.rs"
test = false
doc = false
doctest = false
bench = false

[[bin]]
name = "proc-runner"
path = "./sample/proc-runner/main.rs"
test = false
doc = false
doctest = false
bench = false

[[bin]]
name = "waiter"
path = "./sample/waiter/main.rs"
test = false
doc = false
doctest = false
bench = false

[dependencies]
thiserror = "1"
Expand All @@ -29,10 +41,8 @@ sysinfo = { version = "0.32.0", optional = true }
procfs = "0.17"

[dev-dependencies]
assert_cmd = "2.0.11"
retry = "2.0.0"
tokio = { version = "1", features = ["time", "rt", "macros"] }
escargot = "0.5"

[features]
default = ["proc"]
Expand Down
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.82.0"
components = ["rustfmt", "clippy"]
profile = "minimal"
4 changes: 3 additions & 1 deletion sample/waiter/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ use std::io::stdin;

fn main() {
println!("Waiting");
stdin().read_line(&mut String::new()).unwrap();
let buf = &mut String::new();
stdin().read_line(buf).unwrap();
println!("Waiting done with input: [{}]", buf);
}
13 changes: 7 additions & 6 deletions src/common.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use crate::ProcCtlError::ConfigurationError;
use crate::{Pid, ProcCtlResult};

#[cfg(any(target_os = "linux", feature = "proc"))]
pub(crate) trait MaybeHasPid {
fn get_pid(&self) -> Option<Pid>;
fn get_pid(&self) -> Option<crate::Pid>;
}

pub(crate) fn resolve_pid(maybe_has_pid: &dyn MaybeHasPid) -> ProcCtlResult<Pid> {
#[cfg(any(target_os = "linux", feature = "proc"))]
pub(crate) fn resolve_pid(maybe_has_pid: &dyn MaybeHasPid) -> crate::ProcCtlResult<crate::Pid> {
match &maybe_has_pid.get_pid() {
Some(pid) => Ok(*pid),
None => Err(ConfigurationError("unable to resolve a pid".to_string())),
None => Err(crate::ProcCtlError::ConfigurationError(
"unable to resolve a pid".to_string(),
)),
}
}
4 changes: 2 additions & 2 deletions src/port_query.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use crate::common::MaybeHasPid;
use crate::error::{ProcCtlError, ProcCtlResult};
use crate::types::{Pid, ProtocolPort};
use std::process::Child;
Expand Down Expand Up @@ -181,7 +180,8 @@ fn list_ports_for_pid(query: &PortQuery, pid: Pid) -> ProcCtlResult<Vec<Protocol
Ok(out)
}

impl MaybeHasPid for PortQuery {
#[cfg(any(target_os = "linux", feature = "proc"))]
impl crate::common::MaybeHasPid for PortQuery {
fn get_pid(&self) -> Option<Pid> {
self.process_id
}
Expand Down
11 changes: 6 additions & 5 deletions src/proc_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,13 @@ impl ProcQuery {
/// List all processes matching the current filters.
pub fn list_processes(&self) -> ProcCtlResult<Vec<ProcInfo>> {
let mut sys_handle = sys_handle().lock().unwrap();
sys_handle.refresh_processes(ProcessesToUpdate::All, true);
let processes = sys_handle.processes();
println!(
"Found processes: {:?}, \r\n\r\n while looking for {:?}",
processes, self.name
sys_handle.refresh_processes_specifics(
ProcessesToUpdate::All,
true,
ProcessRefreshKind::everything(),
);
let processes = sys_handle.processes();

let infos: Vec<ProcInfo> = processes
.values()
.filter(|p| {
Expand Down
95 changes: 64 additions & 31 deletions tests/lib_test.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
use assert_cmd::cargo::CommandCargoExt;
use retry::delay::Fixed;
use retry::retry;
use std::process::Command;
#[cfg(any(target_os = "linux", feature = "proc"))]
fn create_command_for_sample(name: &str) -> std::process::Command {
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("target")
.join("release")
.join(name);
#[cfg(target_os = "windows")]
let path = path.with_extension("exe");

if !path.exists() {
panic!(
"{} does not exist, try running `cargo build --release --bins`",
path.display()
);
}

std::process::Command::new(path)
}

#[cfg(any(target_os = "linux", feature = "proc"))]
struct DropChild(std::process::Child);

#[cfg(any(target_os = "linux", feature = "proc"))]
impl DropChild {
fn spawn(mut cmd: Command) -> Self {
fn spawn(mut cmd: std::process::Command) -> Self {
DropChild(cmd.spawn().expect("Failed to spawn child process"))
}
}

#[cfg(any(target_os = "linux", feature = "proc"))]
impl Drop for DropChild {
fn drop(&mut self) {
self.0.kill().expect("Failed to kill child process");
}
}

#[cfg(any(target_os = "linux", feature = "proc"))]
impl std::ops::Deref for DropChild {
type Target = std::process::Child;

Expand All @@ -25,6 +43,7 @@ impl std::ops::Deref for DropChild {
}
}

#[cfg(any(target_os = "linux", feature = "proc"))]
impl std::ops::DerefMut for DropChild {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
Expand All @@ -34,16 +53,18 @@ impl std::ops::DerefMut for DropChild {
#[cfg(target_os = "linux")]
#[test]
fn port_query() {
let mut binder = Command::cargo_bin("port-binder").unwrap();
let mut handle = binder.spawn().unwrap();
use retry::delay::Fixed;

let binder = create_command_for_sample("port-binder");
let mut handle = DropChild::spawn(binder);

let query = proc_ctl::PortQuery::new()
.tcp_only()
.ip_v4_only()
.process_id(handle.id())
.expect_min_num_ports(1);

let ports = retry(Fixed::from_millis(100).take(10), move || query.execute()).unwrap();
let ports = retry::retry(Fixed::from_millis(100).take(10), move || query.execute()).unwrap();

handle.kill().unwrap();

Expand All @@ -53,8 +74,10 @@ fn port_query() {
#[cfg(target_os = "linux")]
#[test]
fn port_query_which_expects_too_many_ports() {
let mut binder = Command::cargo_bin("port-binder").unwrap();
let mut handle = binder.spawn().unwrap();
use retry::delay::Fixed;

let binder = create_command_for_sample("port-binder");
let mut handle = DropChild::spawn(binder);

let query = proc_ctl::PortQuery::new()
.tcp_only()
Expand All @@ -63,7 +86,7 @@ fn port_query_which_expects_too_many_ports() {
.expect_min_num_ports(2);

// Only retry once, getting no ports is still a valid test if the child program hasn't bound yet
let result = retry(Fixed::from_millis(100).take(1), move || query.execute());
let result = retry::retry(Fixed::from_millis(100).take(1), move || query.execute());

handle.kill().unwrap();

Expand All @@ -75,8 +98,8 @@ fn port_query_which_expects_too_many_ports() {
fn port_query_with_sync_retry() {
use std::time::Duration;

let mut binder = Command::cargo_bin("port-binder").unwrap();
let mut handle = binder.spawn().unwrap();
let binder = create_command_for_sample("port-binder");
let mut handle = DropChild::spawn(binder);

let query = proc_ctl::PortQuery::new()
.tcp_only()
Expand All @@ -98,8 +121,8 @@ fn port_query_with_sync_retry() {
async fn port_query_with_async_retry() {
use std::time::Duration;

let mut binder = Command::cargo_bin("port-binder").unwrap();
let mut handle = binder.spawn().unwrap();
let binder = create_command_for_sample("port-binder");
let mut handle = DropChild::spawn(binder);

let query = proc_ctl::PortQuery::new()
.tcp_only()
Expand All @@ -121,18 +144,19 @@ async fn port_query_with_async_retry() {
#[test]
fn proc_query_by_name() {
use proc_ctl::ProcQuery;
use std::process::Stdio;

let _cmd = escargot::CargoBuild::new().bin("waiter").run().unwrap().command().spawn().unwrap();
let mut cmd = create_command_for_sample("waiter")
.stdin(Stdio::piped())
.stdout(Stdio::inherit())
.spawn()
.unwrap();

let query = ProcQuery::new().process_name("waiter");

let processes = retry(Fixed::from_millis(100).take(10), move || {
match query.list_processes().ok() {
Some(processes) if !processes.is_empty() => Ok(processes),
_ => Err("No processes found"),
}
})
.expect("Failed to find process in time");
let processes = query.list_processes().unwrap();

cmd.kill().unwrap();

assert_eq!(1, processes.len());
}
Expand All @@ -141,24 +165,25 @@ fn proc_query_by_name() {
#[test]
fn proc_query_for_children() {
use proc_ctl::ProcQuery;
use retry::delay::Fixed;

let binder = Command::cargo_bin("port-binder").unwrap();
let binder = create_command_for_sample("port-binder");
let port_binder_path = binder.get_program();

let mut runner = Command::cargo_bin("proc-runner").unwrap();
let mut runner = create_command_for_sample("proc-runner");
runner.args([port_binder_path]);
let mut handle = DropChild::spawn(runner);

let query = ProcQuery::new()
.process_id_from_child(&handle)
.expect_min_num_children(1);

let process_names = retry(Fixed::from_millis(100).take(10), move || {
let process_names = retry::retry(Fixed::from_millis(100).take(10), move || {
query
.children()
.map(|v| v.into_iter().map(|p| p.name).collect::<Vec<String>>())
})
.unwrap();
.unwrap();

handle.kill().unwrap();

Expand All @@ -176,10 +201,10 @@ fn proc_query_for_children_with_retry() {
use proc_ctl::ProcQuery;
use std::time::Duration;

let binder = Command::cargo_bin("port-binder").unwrap();
let binder = create_command_for_sample("port-binder");
let port_binder_path = binder.get_program();

let mut runner = Command::cargo_bin("proc-runner").unwrap();
let mut runner = create_command_for_sample("proc-runner");
runner.args([port_binder_path]);
let mut handle = DropChild::spawn(runner);

Expand All @@ -195,6 +220,10 @@ fn proc_query_for_children_with_retry() {
handle.kill().unwrap();

assert_eq!(1, process_names.len());

#[cfg(target_os = "windows")]
assert_eq!("port-binder.exe", process_names.first().unwrap());
#[cfg(not(target_os = "windows"))]
assert_eq!("port-binder", process_names.first().unwrap());
}

Expand All @@ -204,10 +233,10 @@ async fn proc_query_for_children_async_with_retry() {
use proc_ctl::ProcQuery;
use std::time::Duration;

let binder = Command::cargo_bin("port-binder").unwrap();
let binder = create_command_for_sample("port-binder");
let port_binder_path = binder.get_program();

let mut runner = Command::cargo_bin("proc-runner").unwrap();
let mut runner = create_command_for_sample("proc-runner");
runner.args([port_binder_path]);
let mut handle = DropChild::spawn(runner);

Expand All @@ -224,5 +253,9 @@ async fn proc_query_for_children_async_with_retry() {
handle.kill().unwrap();

assert_eq!(1, process_names.len());

#[cfg(target_os = "windows")]
assert_eq!("port-binder.exe", process_names.first().unwrap());
#[cfg(not(target_os = "windows"))]
assert_eq!("port-binder", process_names.first().unwrap());
}

0 comments on commit ac08483

Please sign in to comment.