refactor(app): clarify application and binary roles
Rename the application Cargo package from irtt-cli to irtt-rs (crates/irtt-cli -> crates/irtt-app), and hard-rename its scriptable client binary from irtt-cli to irtt-client, now that the application also ships a first-class server and the old name reads as an umbrella CLI for both roles. Give each dedicated binary (irtt-client, irtt-tui, irtt-server) its own entry point instead of funneling through the multicall dispatcher's argv0/subcommand logic, so a dedicated binary only links the applet code for the role it implements and always runs that role regardless of the name it's invoked or copied under. irtt-rs remains the feature-selectable multicall dispatcher and is now the only binary that inspects argv0 or subcommands. Move future application releases to the irtt-rs/vX.Y.Z tag namespace (regenerated via `dist generate`), and bump the application package to 0.6.0 as a breaking distribution change. Library crate versions are unaffected.
This commit is contained in:
49 files changed
+365
-213
No files matched your search
@@ -42,7 +42,7 @@ jobs:
|
||||
- name: actionlint generated release workflow
|
||||
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8
|
||||
with:
|
||||
files: .github/workflows/irtt-cli-release.yml
|
||||
files: .github/workflows/irtt-rs-release.yml
|
||||
shellcheck: false
|
||||
zizmor:
|
||||
name: zizmor
|
||||
|
||||
@@ -46,8 +46,8 @@ jobs:
|
||||
go install "github.com/heistp/irtt/cmd/irtt@${IRTT_UPSTREAM_VERSION}"
|
||||
"$GOBIN/irtt" version
|
||||
|
||||
- name: Build irtt-rs CLI
|
||||
run: cargo build -p irtt-cli --locked
|
||||
- name: Build irtt-rs client
|
||||
run: cargo build -p irtt-rs --bin irtt-client --locked
|
||||
|
||||
- name: Run netem interop comparisons
|
||||
run: |
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
--all \
|
||||
--skip-build \
|
||||
--upstream-irtt "$GOBIN/irtt" \
|
||||
--irtt-rs-command "$PWD/target/debug/irtt-cli"
|
||||
--irtt-rs-command "$PWD/target/debug/irtt-client"
|
||||
|
||||
- name: Append netem interop summary
|
||||
if: always()
|
||||
|
||||
@@ -42,7 +42,7 @@ on:
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- 'irtt-cli**[0-9]+.[0-9]+.[0-9]+*'
|
||||
- 'irtt-rs**[0-9]+.[0-9]+.[0-9]+*'
|
||||
|
||||
jobs:
|
||||
# Run 'dist plan' (or host) to determine what tasks we need to do
|
||||
@@ -8,7 +8,7 @@ on:
|
||||
- "irtt-client/v*.*.*"
|
||||
- "irtt-server/v*.*.*"
|
||||
- "irtt-stats/v*.*.*"
|
||||
- "irtt-cli/v*.*.*"
|
||||
- "irtt-rs/v*.*.*"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@@ -75,5 +75,5 @@ jobs:
|
||||
- name: actionlint generated release workflow
|
||||
uses: raven-actions/actionlint@205b530c5d9fa8f44ae9ed59f341a0db994aa6f8
|
||||
with:
|
||||
files: .github/workflows/irtt-cli-release.yml
|
||||
files: .github/workflows/irtt-rs-release.yml
|
||||
shellcheck: false
|
||||
@@ -41,7 +41,7 @@ For compatibility questions, the verified spec and black-box observations define
|
||||
|
||||
Main boundary:
|
||||
|
||||
> `irtt-proto` — wire semantics shared by client/server. `irtt-client` — client/session behavior. `irtt-server` — server/session behavior and Tokio runtime/orchestration. `irtt-stats` — statistics over client-facing events/results as currently designed. `irtt-cli` — presentation/orchestration.
|
||||
> `irtt-proto` — wire semantics shared by client/server. `irtt-client` — client/session behavior. `irtt-server` — server/session behavior and Tokio runtime/orchestration. `irtt-stats` — statistics over client-facing events/results as currently designed. `irtt-rs` (application package, `crates/irtt-app`) — presentation/orchestration.
|
||||
|
||||
- `irtt-proto`: pure protocol encoding/decoding, flags, layouts, params, varints/zigzag, HMAC-MD5, validation. No sockets, runtimes, lifecycle, stats, or presentation.
|
||||
- `irtt-client`: socket/session lifecycle, negotiation, probe send/receive, timing metadata, classification, bounded state, and events.
|
||||
@@ -51,7 +51,7 @@ Main boundary:
|
||||
- `BlockingManagedClient`: synchronous owner using a dedicated current-thread Tokio runtime.
|
||||
- `irtt-server`: exists; see "Server architecture (`irtt-server`)" below.
|
||||
- `irtt-stats`: cumulative/rolling/finite statistics over client events. No wire/session state machine logic.
|
||||
- `irtt-cli`: argument parsing, managed orchestration, output, summaries, TUI, server applet orchestration, diagnostics, and exit behavior. Do not duplicate protocol/session state machines here.
|
||||
- `irtt-rs` (application package, `crates/irtt-app`): argument parsing, managed orchestration, output, summaries, TUI, server applet orchestration, diagnostics, and exit behavior. Produces the `irtt-rs` multicall dispatcher plus the dedicated `irtt-client`, `irtt-tui`, and `irtt-server` binaries. Do not duplicate protocol/session state machines here.
|
||||
|
||||
Tokio must remain optional for `irtt-client`; default builds must remain runtime-free and Tokio-free unless an explicit architectural change says otherwise. This rule is specific to `irtt-client` — do not generalize it to `irtt-server`, which has the opposite policy below.
|
||||
|
||||
@@ -66,7 +66,7 @@ Tokio must remain optional for `irtt-client`; default builds must remain runtime
|
||||
- Protocol/session logic should be separated from socket/runtime orchestration where that materially improves deterministic testing, ownership clarity, state-machine reasoning, or maintainability. A `ServerCore`-like internal boundary is therefore desirable, but its exact name/API is not prescribed, it need not be public, it is not a promise of runtime independence, and simple code should not be tortured merely to make the core artificially pure.
|
||||
- Runtime/server orchestration belongs around that core and may freely use Tokio for UDP sockets, `recv_from`/`send_to`, IPv4/IPv6 listeners, timers, session expiry, max-duration deadlines, shutdown, bounded control/config channels where useful, and `select!`-style orchestration.
|
||||
- Ownership is layered and the layers do not blur: `Server` is the one-listener primitive (one socket, one `ServerCore`, one sequential loop), and `ServerSet` supervises one or more of them without owning protocol or session state itself. A common `ServerConfig` is cloned into each listener, so its bounds — `max_sessions` included — are per listener and not per process. Listeners share nothing else: no shared `ServerCore`, no global session table or token namespace, no shared clock, and one sequential sender per socket, which is what keeps the socket-wide reply traffic class race-free.
|
||||
- The CLI server applet exists behind the `irtt-cli` `server` feature, which is part of that crate's default build. It is thin orchestration/configuration over reusable `irtt-server` — one current-thread Tokio runtime, one repeatable `--bind`, one `ServerSet` — and must not grow a second server state machine. **It always uses `ServerSet`, including for a single bind**: there is no direct CLI-to-`Server` path and no bind-count branch, so startup, shutdown and listener-failure semantics are identical whatever was configured, and the singleton case exercises the same orchestration in normal use. Hostname resolution, interface expansion, daemonization, and configuration files are deliberately absent.
|
||||
- The server applet exists behind the `irtt-rs` application package's `server` feature, which is part of that crate's default build. It is thin orchestration/configuration over reusable `irtt-server` — one current-thread Tokio runtime, one repeatable `--bind`, one `ServerSet` — and must not grow a second server state machine. **It always uses `ServerSet`, including for a single bind**: there is no direct CLI-to-`Server` path and no bind-count branch, so startup, shutdown and listener-failure semantics are identical whatever was configured, and the singleton case exercises the same orchestration in normal use. Hostname resolution, interface expansion, daemonization, and configuration files are deliberately absent.
|
||||
- Resource policy: the server should use bounded session/resource policy. Upstream's observed lack of useful default session/per-peer bounds, and its never expiring a session that has not carried an echo request (see `IRTT_SERVER_PROTOCOL_SPEC.md`), are not interoperability requirements and must not be reproduced merely for compatibility.
|
||||
- Testing: prefer `irtt-server` for normal compliant fake-peer behavior where practical, as far as its implemented behavior reaches. Keep narrow adversarial/raw peers for intentionally malformed, ambiguous, or non-compliant wire behavior.
|
||||
|
||||
|
||||
+16
-3
@@ -88,6 +88,17 @@ release. The format is loosely based on
|
||||
- `LateReplyMode` (`Measure` / `CountOnly`) lets a consumer choose whether replies that arrive after their probe is considered late are still fully measured into the running statistics or only counted, without affecting the other samples.
|
||||
- `StatsConfig::estimated_retained_bytes(probe_count)` gives callers an API to estimate the memory a stats configuration will retain for a given probe count, ahead of actually running a session (used by `irtt-cli`'s multi-target memory-usage warning, see below).
|
||||
|
||||
## irtt-rs
|
||||
|
||||
### 0.6.0
|
||||
|
||||
#### Changed
|
||||
|
||||
- The application Cargo package was renamed from `irtt-cli` to `irtt-rs`, moving to `crates/irtt-app`. This is the application distribution package that produces the `irtt-rs`, `irtt-client`, `irtt-tui`, and `irtt-server` binaries; it is unrelated to the reusable `irtt-client` library crate, which keeps its existing name and version.
|
||||
- The `irtt-cli` executable was hard-renamed to `irtt-client` to avoid reading as an umbrella CLI now that the application also ships a first-class server. There is no `irtt-cli` compatibility binary or alias.
|
||||
- Dedicated binaries now have role-specific entry points (`irtt-client`, `irtt-tui`, `irtt-server`) instead of all funneling through the multicall dispatcher's argv0/subcommand logic. Each dedicated binary always runs its own role regardless of the name it is invoked or copied under, and no longer links code for the other applet roles it doesn't implement. The `irtt-rs` binary remains the feature-selectable multicall dispatcher and is the only binary that inspects argv0 or subcommands.
|
||||
- Future application releases move to the `irtt-rs/vX.Y.Z` tag namespace (previously `irtt-cli/vX.Y.Z`); see [Releasing](#releasing).
|
||||
|
||||
## irtt-cli
|
||||
|
||||
### 0.5.0
|
||||
@@ -125,6 +136,8 @@ rather than in lockstep. Each crate's release is tagged as `<crate>/vX.Y.Z`
|
||||
from. `irtt-proto`, `irtt-client`, `irtt-server`, and `irtt-stats` are
|
||||
libraries published to [crates.io](https://crates.io); their tags exist to
|
||||
identify the exact source for a given crates.io release and do not produce a
|
||||
GitHub Release. `irtt-cli` is the only crate with prebuilt binary artifacts:
|
||||
pushing an `irtt-cli/vX.Y.Z` tag triggers [cargo-dist](https://opensource.axo.dev/cargo-dist/)
|
||||
to build and publish a GitHub Release with platform binaries.
|
||||
GitHub Release. The application package (`irtt-cli` through 0.5.0, `irtt-rs`
|
||||
from 0.6.0 onward) is the only package with prebuilt binary artifacts: pushing
|
||||
an `irtt-rs/vX.Y.Z` tag triggers [cargo-dist](https://opensource.axo.dev/cargo-dist/)
|
||||
to build and publish a GitHub Release with platform binaries. The historical
|
||||
`irtt-cli/v0.5.0` tag remains as released; it is not rewritten or replaced.
|
||||
Generated
+15
-15
@@ -713,21 +713,6 @@ dependencies = [
|
||||
"syn 3.0.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "irtt-cli"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"irtt-client",
|
||||
"irtt-proto",
|
||||
"irtt-server",
|
||||
"irtt-stats",
|
||||
"ratatui",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "irtt-client"
|
||||
version = "0.5.0"
|
||||
@@ -762,6 +747,21 @@ dependencies = [
|
||||
"thiserror 2.0.20",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "irtt-rs"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"crossterm",
|
||||
"ctrlc",
|
||||
"irtt-client",
|
||||
"irtt-proto",
|
||||
"irtt-server",
|
||||
"irtt-stats",
|
||||
"ratatui",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "irtt-server"
|
||||
version = "0.5.0"
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ members = [
|
||||
"crates/irtt-client",
|
||||
"crates/irtt-server",
|
||||
"crates/irtt-stats",
|
||||
"crates/irtt-cli",
|
||||
"crates/irtt-app",
|
||||
"crates/irtt-integration-tests",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
@@ -31,19 +31,19 @@ From a local checkout:
|
||||
```sh
|
||||
git clone https://github.com/Lochnair/irtt-rs.git
|
||||
cd irtt-rs
|
||||
cargo install --path crates/irtt-cli
|
||||
cargo install --path crates/irtt-app
|
||||
```
|
||||
|
||||
This installs:
|
||||
|
||||
* `irtt-rs`, the canonical multi-applet dispatcher
|
||||
* `irtt-cli`, the stream and text client
|
||||
* `irtt-client`, the stream and text client
|
||||
* `irtt-server`, the UDP server
|
||||
|
||||
To also install the optional terminal UI:
|
||||
|
||||
```sh
|
||||
cargo install --path crates/irtt-cli --features tui
|
||||
cargo install --path crates/irtt-app --features tui
|
||||
```
|
||||
|
||||
## Quick start
|
||||
@@ -51,13 +51,13 @@ cargo install --path crates/irtt-cli --features tui
|
||||
Probe a server using the default settings:
|
||||
|
||||
```sh
|
||||
irtt-cli netperf-eu.bufferbloat.net:2112
|
||||
irtt-client netperf-eu.bufferbloat.net:2112
|
||||
```
|
||||
|
||||
Set the test duration and probe interval:
|
||||
|
||||
```sh
|
||||
irtt-cli netperf-eu.bufferbloat.net:2112 \
|
||||
irtt-client netperf-eu.bufferbloat.net:2112 \
|
||||
--duration 30s \
|
||||
--interval 100ms
|
||||
```
|
||||
@@ -65,7 +65,7 @@ irtt-cli netperf-eu.bufferbloat.net:2112 \
|
||||
Run continuously until interrupted:
|
||||
|
||||
```sh
|
||||
irtt-cli netperf-eu.bufferbloat.net:2112 --duration 0
|
||||
irtt-client netperf-eu.bufferbloat.net:2112 --duration 0
|
||||
```
|
||||
|
||||
Use `Ctrl-C` to stop gracefully.
|
||||
@@ -75,7 +75,7 @@ Use `Ctrl-C` to stop gracefully.
|
||||
Probe several targets concurrently:
|
||||
|
||||
```sh
|
||||
irtt-cli host-a:2112 host-b:2112
|
||||
irtt-client host-a:2112 host-b:2112
|
||||
```
|
||||
|
||||
Every target argument accepts `[LABEL=]TARGET`. An optional `LABEL=` prefix assigns the logical
|
||||
@@ -83,19 +83,19 @@ target name used in output and the TUI; without it, the target string itself is
|
||||
stable labels to targets:
|
||||
|
||||
```sh
|
||||
irtt-cli ams=ams.example.com:2112 sg=sg.example.com:2112
|
||||
irtt-client ams=ams.example.com:2112 sg=sg.example.com:2112
|
||||
```
|
||||
|
||||
Labeled and unlabeled targets can be freely mixed in one argument list:
|
||||
|
||||
```sh
|
||||
irtt-cli host-a:2112 ams=ams.example.com:2112
|
||||
irtt-client host-a:2112 ams=ams.example.com:2112
|
||||
```
|
||||
|
||||
Multi-target sessions use staggered pacing by default. To send each target's probes together:
|
||||
|
||||
```sh
|
||||
irtt-cli host-a:2112 host-b:2112 --pacing burst
|
||||
irtt-client host-a:2112 host-b:2112 --pacing burst
|
||||
```
|
||||
|
||||
Target labels are included in the default multi-target output.
|
||||
@@ -112,16 +112,16 @@ The CLI supports four event-row formats:
|
||||
Examples:
|
||||
|
||||
```sh
|
||||
irtt-cli <server> --format table
|
||||
irtt-cli <server> --format jsonl
|
||||
irtt-cli <server> --format csv \
|
||||
irtt-client <server> --format table
|
||||
irtt-client <server> --format jsonl
|
||||
irtt-client <server> --format csv \
|
||||
--columns event,seq,remote,effective_rtt_us
|
||||
```
|
||||
|
||||
For a stream containing only effective RTT values in microseconds:
|
||||
|
||||
```sh
|
||||
irtt-cli <server> \
|
||||
irtt-client <server> \
|
||||
--format tsv \
|
||||
--columns effective_rtt_us \
|
||||
--header never
|
||||
@@ -130,7 +130,7 @@ irtt-cli <server> \
|
||||
List all available columns with:
|
||||
|
||||
```sh
|
||||
irtt-cli --list-columns
|
||||
irtt-client --list-columns
|
||||
```
|
||||
|
||||
Useful measurement fields include:
|
||||
@@ -164,7 +164,7 @@ It runs continuously by default. A finite duration can be selected explicitly:
|
||||
irtt-tui <server> --duration 30s
|
||||
```
|
||||
|
||||
Multiple targets and pacing options work the same way as in `irtt-cli`:
|
||||
Multiple targets and pacing options work the same way as in `irtt-client`:
|
||||
|
||||
```sh
|
||||
irtt-tui host-a:2112 host-b:2112 --pacing burst
|
||||
@@ -315,18 +315,18 @@ synchronous callers. Callers that already own Tokio can drive
|
||||
| ----------------------------------------- | ------------------------------------------------- |
|
||||
| `--no-default-features` | `irtt-rs` |
|
||||
| `--no-default-features --features server` | `irtt-rs`, `irtt-server` |
|
||||
| Default features | `irtt-rs`, `irtt-cli`, `irtt-server` |
|
||||
| `--features tui` | `irtt-rs`, `irtt-cli`, `irtt-server`, `irtt-tui` |
|
||||
| `--all-features` | `irtt-rs`, `irtt-cli`, `irtt-server`, `irtt-tui` |
|
||||
| Default features | `irtt-rs`, `irtt-client`, `irtt-server` |
|
||||
| `--features tui` | `irtt-rs`, `irtt-client`, `irtt-server`, `irtt-tui` |
|
||||
| `--all-features` | `irtt-rs`, `irtt-client`, `irtt-server`, `irtt-tui` |
|
||||
|
||||
`irtt-cli` requires the `client` feature, `irtt-server` the `server` feature,
|
||||
`irtt-client` requires the `client` feature, `irtt-server` the `server` feature,
|
||||
and `irtt-tui` the `tui` feature. The `irtt-rs` dispatcher is always built, and
|
||||
reports which applets the build actually contains.
|
||||
|
||||
For available command-line options:
|
||||
|
||||
```sh
|
||||
irtt-cli --help
|
||||
irtt-client --help
|
||||
irtt-server --help
|
||||
irtt-tui --help
|
||||
```
|
||||
@@ -339,7 +339,7 @@ Common verification commands:
|
||||
cargo fmt --check
|
||||
cargo test --workspace
|
||||
cargo clippy --workspace --all-targets -- -D warnings
|
||||
cargo build -p irtt-cli --all-features --release
|
||||
cargo build -p irtt-rs --all-features --release
|
||||
```
|
||||
|
||||
## Project status
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "irtt-cli"
|
||||
version = "0.5.0"
|
||||
description = "Command-line IRTT-compatible client and server"
|
||||
name = "irtt-rs"
|
||||
version = "0.6.0"
|
||||
description = "IRTT-compatible client, TUI, and server applications"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
rust-version.workspace = true
|
||||
@@ -12,6 +12,7 @@ keywords = ["irtt", "networking", "latency", "rtt"]
|
||||
categories = ["command-line-utilities", "network-programming"]
|
||||
|
||||
[lib]
|
||||
name = "irtt_app"
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
@@ -19,9 +20,12 @@ name = "irtt-rs"
|
||||
path = "src/bin/irtt-rs.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "irtt-cli"
|
||||
path = "src/bin/irtt-cli.rs"
|
||||
name = "irtt-client"
|
||||
path = "src/bin/irtt-client.rs"
|
||||
required-features = ["client"]
|
||||
# This applet shares its name with the irtt-client library crate, whose rustdoc
|
||||
# output it would otherwise collide with.
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "irtt-tui"
|
||||
@@ -20,7 +20,7 @@ pub enum AppletDispatch {
|
||||
|
||||
pub fn detect_applet_from_argv0(argv0: &str) -> Option<RequestedApplet> {
|
||||
match applet_basename(argv0) {
|
||||
"irtt-cli" => Some(RequestedApplet::Client),
|
||||
"irtt-client" => Some(RequestedApplet::Client),
|
||||
"irtt-tui" => Some(RequestedApplet::Tui),
|
||||
"irttd" | "irtt-server" => Some(RequestedApplet::Server),
|
||||
"irtt-rs" => None,
|
||||
@@ -58,7 +58,7 @@ pub fn dispatch_from_argv(argv: Vec<OsString>) -> Result<AppletDispatch, String>
|
||||
"-h" | "--help" => Ok(AppletDispatch::Help(dispatcher_help())),
|
||||
"client" => Ok(AppletDispatch::Run {
|
||||
applet: RequestedApplet::Client,
|
||||
argv: applet_argv("irtt-cli", &argv[2..]),
|
||||
argv: applet_argv("irtt-client", &argv[2..]),
|
||||
}),
|
||||
"tui" => Ok(AppletDispatch::Run {
|
||||
applet: RequestedApplet::Tui,
|
||||
@@ -111,7 +111,7 @@ fn server_applet_about() -> &'static str {
|
||||
}
|
||||
|
||||
fn known_applet_binary_names() -> &'static str {
|
||||
"irtt-rs, irtt-cli, irtt-tui, irtt-server"
|
||||
"irtt-rs, irtt-client, irtt-tui, irtt-server"
|
||||
}
|
||||
|
||||
fn applet_command_names() -> &'static str {
|
||||
@@ -119,11 +119,11 @@ fn applet_command_names() -> &'static str {
|
||||
}
|
||||
|
||||
fn dispatcher_after_help() -> String {
|
||||
let mut help = "Recognized applet binary names: irtt-cli, irtt-tui, irtt-server".to_owned();
|
||||
let mut help = "Recognized applet binary names: irtt-client, irtt-tui, irtt-server".to_owned();
|
||||
|
||||
let mut missing = Vec::new();
|
||||
if !cfg!(feature = "client") {
|
||||
missing.push("irtt-cli requires the client feature");
|
||||
missing.push("irtt-client requires the client feature");
|
||||
}
|
||||
if !cfg!(feature = "server") {
|
||||
missing.push("irtt-server requires the server feature");
|
||||
@@ -188,7 +188,7 @@ mod tests {
|
||||
#[test]
|
||||
fn applet_detection_maps_binary_names() {
|
||||
assert_eq!(
|
||||
detect_applet_from_argv0("/usr/bin/irtt-cli"),
|
||||
detect_applet_from_argv0("/usr/bin/irtt-client"),
|
||||
Some(RequestedApplet::Client)
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -203,6 +203,12 @@ mod tests {
|
||||
assert_eq!(detect_applet_from_argv0("custom-name"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_irtt_cli_binary_name_is_no_longer_a_recognized_applet() {
|
||||
assert_eq!(detect_applet_from_argv0("irtt-cli"), None);
|
||||
assert_eq!(detect_applet_from_argv0("/usr/bin/irtt-cli"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn applet_detection_ignores_the_platform_executable_suffix() {
|
||||
let exe = std::env::consts::EXE_SUFFIX;
|
||||
@@ -212,7 +218,7 @@ mod tests {
|
||||
Some(RequestedApplet::Server)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_applet_from_argv0(&format!("irtt-cli{exe}")),
|
||||
detect_applet_from_argv0(&format!("irtt-client{exe}")),
|
||||
Some(RequestedApplet::Client)
|
||||
);
|
||||
|
||||
@@ -288,7 +294,7 @@ mod tests {
|
||||
.unwrap(),
|
||||
AppletDispatch::Run {
|
||||
applet: RequestedApplet::Client,
|
||||
argv: vec!["irtt-cli".into(), "host:2112".into()],
|
||||
argv: vec!["irtt-client".into(), "host:2112".into()],
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -311,7 +317,7 @@ mod tests {
|
||||
fn unknown_irtt_binary_name_is_an_error() {
|
||||
let err = dispatch_from_argv(vec!["/usr/bin/irtt-typo".into()]).unwrap_err();
|
||||
assert!(err.contains("unknown applet name 'irtt-typo'"));
|
||||
assert!(err.contains("irtt-cli"));
|
||||
assert!(err.contains("irtt-client"));
|
||||
if cfg!(feature = "tui") {
|
||||
assert!(err.contains("irtt-tui"));
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
irtt_cli::entry::main()
|
||||
irtt_app::entry::client_main()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
irtt_cli::entry::main()
|
||||
irtt_app::entry::dispatcher_main()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
irtt_cli::entry::main()
|
||||
irtt_app::entry::server_main()
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use std::process::ExitCode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
irtt_cli::entry::main()
|
||||
irtt_app::entry::tui_main()
|
||||
}
|
||||
@@ -9,14 +9,14 @@ use clap::{Parser, ValueEnum};
|
||||
pub const DEFAULT_CLIENT_DURATION: Duration = Duration::from_secs(10);
|
||||
|
||||
#[derive(Debug, Clone, Parser)]
|
||||
#[command(name = "irtt-cli", about = "Minimal IRTT-compatible stream client")]
|
||||
#[command(name = "irtt-client", about = "Minimal IRTT-compatible stream client")]
|
||||
pub struct ClientArgs {
|
||||
/// Server address/host, optionally prefixed with LABEL=. Repeat for multi-target mode.
|
||||
#[arg(
|
||||
value_name = "TARGET",
|
||||
num_args = 0..,
|
||||
value_parser = parse_target,
|
||||
long_help = "Server address/host, optionally prefixed with LABEL=. Repeat for multi-target mode.\n\nExamples:\n irtt-cli host-a:2112 host-b:2112\n irtt-cli eu=host-a:2112 us=host-b:2112\n irtt-cli host-a:2112 eu=host-b:2112"
|
||||
long_help = "Server address/host, optionally prefixed with LABEL=. Repeat for multi-target mode.\n\nExamples:\n irtt-client host-a:2112 host-b:2112\n irtt-client eu=host-a:2112 us=host-b:2112\n irtt-client host-a:2112 eu=host-b:2112"
|
||||
)]
|
||||
pub targets: Vec<TargetArg>,
|
||||
|
||||
@@ -149,7 +149,7 @@ mod tests {
|
||||
use irtt_proto::{Clock, ReceivedStats, StampAt};
|
||||
|
||||
fn parse(args: &[&str]) -> Result<ClientArgs, clap::Error> {
|
||||
let mut argv = vec!["irtt-cli"];
|
||||
let mut argv = vec!["irtt-client"];
|
||||
argv.extend_from_slice(args);
|
||||
ClientArgs::try_parse_from(argv)
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
@@ -392,7 +392,7 @@ mod tests {
|
||||
use clap::Parser;
|
||||
|
||||
ClientArgs::try_parse_from([
|
||||
"irtt-cli",
|
||||
"irtt-client",
|
||||
"--duration",
|
||||
"4200s",
|
||||
"--interval",
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -0,0 +1,136 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
process::ExitCode,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
applet::{dispatch_from_argv, AppletDispatch, RequestedApplet},
|
||||
signal::install_signal_handler,
|
||||
};
|
||||
|
||||
/// Entry point for the `irtt-rs` multicall dispatcher binary.
|
||||
///
|
||||
/// Chooses an applet by subcommand or by recognized argv0 and may reference
|
||||
/// every applet enabled by the active feature set.
|
||||
pub fn dispatcher_main() -> ExitCode {
|
||||
run_with_exit_code("irtt-rs", run_dispatcher_from_env)
|
||||
}
|
||||
|
||||
/// Entry point for the dedicated `irtt-client` binary.
|
||||
///
|
||||
/// Runs the stream client directly; it never consults argv0 or the applet
|
||||
/// dispatcher, so a renamed or copied executable still runs the client.
|
||||
#[cfg(feature = "client")]
|
||||
pub fn client_main() -> ExitCode {
|
||||
run_with_exit_code("irtt-client", || {
|
||||
run_client_applet(env::args_os().collect())
|
||||
})
|
||||
}
|
||||
|
||||
/// Entry point for the dedicated `irtt-tui` binary.
|
||||
///
|
||||
/// Runs the terminal UI directly; it never consults argv0 or the applet
|
||||
/// dispatcher, so a renamed or copied executable still runs the TUI.
|
||||
#[cfg(feature = "tui")]
|
||||
pub fn tui_main() -> ExitCode {
|
||||
run_with_exit_code("irtt-tui", || run_tui_applet(env::args_os().collect()))
|
||||
}
|
||||
|
||||
/// Entry point for the dedicated `irtt-server` binary.
|
||||
///
|
||||
/// Runs the server directly; it never consults argv0 or the applet
|
||||
/// dispatcher, so a renamed or copied executable still runs the server.
|
||||
#[cfg(feature = "server")]
|
||||
pub fn server_main() -> ExitCode {
|
||||
run_with_exit_code("irtt-server", || {
|
||||
run_server_applet(env::args_os().collect())
|
||||
})
|
||||
}
|
||||
|
||||
fn run_with_exit_code(
|
||||
program: &str,
|
||||
f: impl FnOnce() -> Result<(), Box<dyn std::error::Error>>,
|
||||
) -> ExitCode {
|
||||
match f() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
eprintln!("{program}: {err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_dispatcher_from_env() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let argv: Vec<OsString> = env::args_os().collect();
|
||||
let (requested, argv) = match dispatch_from_argv(argv)? {
|
||||
AppletDispatch::Run { applet, argv } => (applet, argv),
|
||||
AppletDispatch::Help(help) => {
|
||||
print!("{help}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
match requested {
|
||||
RequestedApplet::Client => run_client_applet(argv),
|
||||
RequestedApplet::Tui => run_tui_applet(argv),
|
||||
RequestedApplet::Server => run_server_applet(argv),
|
||||
}
|
||||
}
|
||||
|
||||
/// Installs the shutdown signal handler shared by every applet and hands the
|
||||
/// resulting flag to `f`.
|
||||
fn with_shutdown_flag(
|
||||
f: impl FnOnce(&AtomicBool) -> Result<(), Box<dyn std::error::Error>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let shutdown_requested = Arc::new(AtomicBool::new(false));
|
||||
install_signal_handler(Arc::clone(&shutdown_requested))
|
||||
.map_err(|err| format!("failed to install signal handler: {err}"))?;
|
||||
f(shutdown_requested.as_ref())
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn run_client_applet(argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
with_shutdown_flag(|shutdown_requested| {
|
||||
let args = crate::cmd::client::ClientArgs::parse_from(argv);
|
||||
crate::cmd::client::run_stream(args, shutdown_requested)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "client"))]
|
||||
fn run_client_applet(_argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("client applet is not available; rebuild with the client feature".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn run_server_applet(argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
with_shutdown_flag(|shutdown_requested| {
|
||||
let args = crate::cmd::server::ServerArgs::parse_from(argv);
|
||||
crate::cmd::server::run_server(args, shutdown_requested)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
fn run_server_applet(_argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("server applet is not available; rebuild with the server feature".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
fn run_tui_applet(argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
with_shutdown_flag(|shutdown_requested| {
|
||||
let args = crate::cmd::tui::TuiArgs::parse_from(argv);
|
||||
crate::cmd::tui::run_tui(args, shutdown_requested)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tui"))]
|
||||
fn run_tui_applet(_argv: Vec<OsString>) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("TUI applet is not available; rebuild with the tui feature".into())
|
||||
}
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+93
-4
@@ -13,6 +13,31 @@ fn irtt_rs_with_arg0(arg0: &str) -> Command {
|
||||
command
|
||||
}
|
||||
|
||||
/// Copies a dedicated binary to `name` in a temp dir and returns a `Command`
|
||||
/// invoking it under that name, proving dedicated binaries decide their role
|
||||
/// from the entry point they were built with, not from argv0.
|
||||
#[cfg(all(unix, any(feature = "client", feature = "server")))]
|
||||
fn copied_binary(cargo_bin_exe: &str, name: &str) -> Command {
|
||||
let dir = std::env::temp_dir().join(format!(
|
||||
"irtt-copied-binary-test-{}-{}",
|
||||
std::process::id(),
|
||||
name
|
||||
));
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
let dest = dir.join(name);
|
||||
std::fs::copy(cargo_bin_exe, &dest).unwrap();
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let mut perms = std::fs::metadata(&dest).unwrap().permissions();
|
||||
perms.set_mode(0o755);
|
||||
std::fs::set_permissions(&dest, perms).unwrap();
|
||||
}
|
||||
|
||||
Command::new(dest)
|
||||
}
|
||||
|
||||
fn output_text(output: &Output) -> String {
|
||||
let mut text = String::new();
|
||||
text.push_str(&String::from_utf8_lossy(&output.stdout));
|
||||
@@ -97,9 +122,37 @@ fn canonical_server_subcommand_reports_unavailable_when_disabled() {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
#[test]
|
||||
fn dedicated_client_binary_always_enters_the_client_role() {
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
let text = output_text(&output);
|
||||
|
||||
assert!(output.status.success(), "{text}");
|
||||
assert!(text.contains("Minimal IRTT-compatible stream client"));
|
||||
assert!(text.contains("--format <FORMAT>"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
#[test]
|
||||
fn dedicated_tui_binary_always_enters_the_tui_role() {
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_irtt-tui"))
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
let text = output_text(&output);
|
||||
|
||||
assert!(output.status.success(), "{text}");
|
||||
assert!(text.contains("Minimal IRTT-compatible TUI client"));
|
||||
assert!(text.contains("--duration <DURATION>"));
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
#[test]
|
||||
fn standalone_server_binary_exposes_the_same_arguments() {
|
||||
fn dedicated_server_binary_always_enters_the_server_role() {
|
||||
let output = std::process::Command::new(env!("CARGO_BIN_EXE_irtt-server"))
|
||||
.arg("--help")
|
||||
.output()
|
||||
@@ -111,11 +164,37 @@ fn standalone_server_binary_exposes_the_same_arguments() {
|
||||
assert!(text.contains("--bind <ADDR>"));
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "client"))]
|
||||
#[test]
|
||||
fn renaming_the_dedicated_client_binary_does_not_change_its_role() {
|
||||
let output = copied_binary(env!("CARGO_BIN_EXE_irtt-client"), "whatever")
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
let text = output_text(&output);
|
||||
|
||||
assert!(output.status.success(), "{text}");
|
||||
assert!(text.contains("Minimal IRTT-compatible stream client"));
|
||||
}
|
||||
|
||||
#[cfg(all(unix, feature = "server"))]
|
||||
#[test]
|
||||
fn renaming_the_dedicated_server_binary_does_not_change_its_role() {
|
||||
let output = copied_binary(env!("CARGO_BIN_EXE_irtt-server"), "irtt-cli")
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
let text = output_text(&output);
|
||||
|
||||
assert!(output.status.success(), "{text}");
|
||||
assert!(text.contains("Minimal IRTT-compatible UDP server"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[cfg(feature = "client")]
|
||||
#[test]
|
||||
fn client_applet_name_dispatches_to_client() {
|
||||
let output = irtt_rs_with_arg0("irtt-cli")
|
||||
let output = irtt_rs_with_arg0("irtt-client")
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -130,7 +209,7 @@ fn client_applet_name_dispatches_to_client() {
|
||||
#[cfg(all(unix, not(feature = "client")))]
|
||||
#[test]
|
||||
fn client_applet_name_reports_unavailable_when_disabled() {
|
||||
let output = irtt_rs_with_arg0("irtt-cli")
|
||||
let output = irtt_rs_with_arg0("irtt-client")
|
||||
.arg("--help")
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -205,7 +284,17 @@ fn unknown_irtt_applet_name_errors() {
|
||||
|
||||
assert!(!output.status.success());
|
||||
assert!(text.contains("unknown applet name 'irtt-typo'"), "{text}");
|
||||
assert!(text.contains("irtt-cli"));
|
||||
assert!(text.contains("irtt-client"));
|
||||
assert!(text.contains("irtt-tui"));
|
||||
assert!(text.contains("irtt-server"));
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn old_irtt_cli_binary_name_is_no_longer_a_recognized_applet() {
|
||||
let output = irtt_rs_with_arg0("irtt-cli").output().unwrap();
|
||||
let text = output_text(&output);
|
||||
|
||||
assert!(!output.status.success());
|
||||
assert!(text.contains("unknown applet name 'irtt-cli'"), "{text}");
|
||||
}
|
||||
+22
-22
@@ -55,7 +55,7 @@ impl InterruptibleFakeServer {
|
||||
|
||||
#[test]
|
||||
fn old_target_option_is_rejected_as_an_unknown_argument() {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args(["--target", "eu=127.0.0.1:2112"])
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -67,7 +67,7 @@ fn old_target_option_is_rejected_as_an_unknown_argument() {
|
||||
|
||||
#[test]
|
||||
fn list_columns_succeeds_without_target() {
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.arg("--list-columns")
|
||||
.output()
|
||||
.unwrap();
|
||||
@@ -88,7 +88,7 @@ fn single_target_default_table_includes_target_and_accepts_pacing() {
|
||||
let server = InTreeServer::start();
|
||||
let target = server.addr.to_string();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -125,7 +125,7 @@ fn single_target_default_table_includes_target_and_accepts_pacing() {
|
||||
fn single_labeled_target_default_table_includes_target() {
|
||||
let server = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -160,7 +160,7 @@ fn single_labeled_target_default_table_includes_target() {
|
||||
fn single_labeled_target_default_csv_includes_target_and_event_wall_ns() {
|
||||
let server = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -199,7 +199,7 @@ fn single_positional_target_default_csv_includes_target_as_positional_label() {
|
||||
let server = InTreeServer::start();
|
||||
let target = server.addr.to_string();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -237,7 +237,7 @@ fn multi_target_default_table_includes_both_labels() {
|
||||
let a = InTreeServer::start();
|
||||
let b = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"40ms",
|
||||
@@ -287,7 +287,7 @@ fn multi_target_final_summary_preserves_cli_argument_order() {
|
||||
let a = InTreeServer::start();
|
||||
let m = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"40ms",
|
||||
@@ -331,7 +331,7 @@ fn mixed_labeled_and_unlabeled_positional_targets_both_appear_in_output() {
|
||||
let b = InTreeServer::start();
|
||||
let unlabeled_addr = a.addr.to_string();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"40ms",
|
||||
@@ -376,7 +376,7 @@ fn single_target_custom_target_column_renders_positional_label() {
|
||||
let server = InTreeServer::start();
|
||||
let target = server.addr.to_string();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -414,7 +414,7 @@ fn single_target_custom_target_column_renders_positional_label() {
|
||||
fn custom_columns_remain_authoritative_and_omit_target() {
|
||||
let server = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -448,7 +448,7 @@ fn custom_columns_remain_authoritative_and_omit_target() {
|
||||
fn columns_default_keyword_includes_target_for_single_target() {
|
||||
let server = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -484,7 +484,7 @@ fn columns_default_keyword_includes_target_for_single_target() {
|
||||
fn single_labeled_target_default_jsonl_includes_target() {
|
||||
let server = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"30ms",
|
||||
@@ -517,7 +517,7 @@ fn single_labeled_target_default_jsonl_includes_target() {
|
||||
#[test]
|
||||
fn continuous_single_target_peer_close_exits_nonzero() {
|
||||
let server = start_peer_close_server(test_params(None, Duration::from_millis(10)));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -553,7 +553,7 @@ fn continuous_single_target_peer_close_exits_nonzero() {
|
||||
fn continuous_single_target_interruption_drains_final_events_and_succeeds() {
|
||||
let params = test_params(None, Duration::from_millis(10));
|
||||
let server = start_interruptible_echo_server(params);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -611,7 +611,7 @@ fn multi_target_csv_emits_rows_for_both_labels() {
|
||||
let a = InTreeServer::start();
|
||||
let b = InTreeServer::start();
|
||||
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-cli"))
|
||||
let output = Command::new(env!("CARGO_BIN_EXE_irtt-client"))
|
||||
.args([
|
||||
"--duration",
|
||||
"40ms",
|
||||
@@ -654,7 +654,7 @@ fn finite_multi_target_peer_close_is_accepted_as_completion() {
|
||||
let params = test_params(Some(Duration::from_millis(40)), Duration::from_millis(10));
|
||||
let a = start_peer_close_server(params.clone());
|
||||
let b = start_peer_close_server(params);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"40ms",
|
||||
@@ -716,7 +716,7 @@ fn continuous_all_peer_closed_targets_exit_nonzero() {
|
||||
.send(())
|
||||
.expect("target B stopped waiting for peer-close release");
|
||||
});
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -766,7 +766,7 @@ fn continuous_partial_peer_close_preserves_queued_rows_and_reports_peer_close()
|
||||
let (healthy_reply_tx, healthy_reply_rx) = mpsc::channel();
|
||||
let healthy = start_echo_server_with_first_reply(params.clone(), healthy_reply_tx);
|
||||
let peer_closed = start_gated_peer_close_server(params, vec![healthy_reply_rx]);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -820,7 +820,7 @@ fn continuous_mixed_peer_close_and_open_failure_exits_nonzero() {
|
||||
let failure = start_open_failure_server_with_signal(params.clone(), failure_tx);
|
||||
let healthy = start_echo_server_with_first_reply(params.clone(), healthy_reply_tx);
|
||||
let peer_closed = start_gated_peer_close_server(params, vec![failure_rx, healthy_reply_rx]);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -872,7 +872,7 @@ fn explicit_group_interruption_succeeds_without_peer_close_error() {
|
||||
let params = test_params(None, Duration::from_millis(10));
|
||||
let a = start_interruptible_echo_server(params.clone());
|
||||
let b = start_interruptible_echo_server(params);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
@@ -932,7 +932,7 @@ fn all_open_failures_exit_nonzero_with_diagnostics() {
|
||||
let params = test_params(None, Duration::from_millis(10));
|
||||
let a = start_open_failure_server(params.clone());
|
||||
let b = start_open_failure_server(params);
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-cli"));
|
||||
let mut command = Command::new(env!("CARGO_BIN_EXE_irtt-client"));
|
||||
command.args([
|
||||
"--duration",
|
||||
"0s",
|
||||
File renamed without changes.
@@ -1,99 +0,0 @@
|
||||
use std::{
|
||||
env,
|
||||
ffi::OsString,
|
||||
process::ExitCode,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
applet::{dispatch_from_argv, AppletDispatch, RequestedApplet},
|
||||
signal::install_signal_handler,
|
||||
};
|
||||
|
||||
pub fn main() -> ExitCode {
|
||||
match run_from_env() {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(err) => {
|
||||
eprintln!("irtt-rs: {err}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_from_env() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let argv: Vec<OsString> = env::args_os().collect();
|
||||
let (requested, argv) = match dispatch_from_argv(argv)? {
|
||||
AppletDispatch::Run { applet, argv } => (applet, argv),
|
||||
AppletDispatch::Help(help) => {
|
||||
print!("{help}");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let shutdown_requested = Arc::new(AtomicBool::new(false));
|
||||
install_signal_handler(Arc::clone(&shutdown_requested))
|
||||
.map_err(|err| format!("failed to install signal handler: {err}"))?;
|
||||
|
||||
match requested {
|
||||
RequestedApplet::Client => run_client_applet(argv, shutdown_requested.as_ref()),
|
||||
RequestedApplet::Tui => run_tui_applet(argv, shutdown_requested.as_ref()),
|
||||
RequestedApplet::Server => run_server_applet(argv, shutdown_requested.as_ref()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
fn run_client_applet(
|
||||
argv: Vec<OsString>,
|
||||
shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
let args = crate::cmd::client::ClientArgs::parse_from(argv);
|
||||
crate::cmd::client::run_stream(args, shutdown_requested)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "client"))]
|
||||
fn run_client_applet(
|
||||
_argv: Vec<OsString>,
|
||||
_shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("client applet is not available; rebuild with the client feature".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
fn run_server_applet(
|
||||
argv: Vec<OsString>,
|
||||
shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
let args = crate::cmd::server::ServerArgs::parse_from(argv);
|
||||
crate::cmd::server::run_server(args, shutdown_requested)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "server"))]
|
||||
fn run_server_applet(
|
||||
_argv: Vec<OsString>,
|
||||
_shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("server applet is not available; rebuild with the server feature".into())
|
||||
}
|
||||
|
||||
#[cfg(feature = "tui")]
|
||||
fn run_tui_applet(
|
||||
argv: Vec<OsString>,
|
||||
shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use clap::Parser;
|
||||
|
||||
let args = crate::cmd::tui::TuiArgs::parse_from(argv);
|
||||
crate::cmd::tui::run_tui(args, shutdown_requested)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "tui"))]
|
||||
fn run_tui_applet(
|
||||
_argv: Vec<OsString>,
|
||||
_shutdown_requested: &AtomicBool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
Err("TUI applet is not available; rebuild with the tui feature".into())
|
||||
}
|
||||
+2
-2
@@ -11,7 +11,7 @@ ci = "github"
|
||||
installers = []
|
||||
# Target platforms to build apps for (Rust target-triple syntax)
|
||||
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-apple-darwin", "x86_64-unknown-linux-gnu", "x86_64-unknown-linux-musl", "x86_64-pc-windows-msvc"]
|
||||
# Scope dist's tag matching to irtt-cli releases only: irtt-proto/irtt-client/
|
||||
# Scope dist's tag matching to irtt-rs releases only: irtt-proto/irtt-client/
|
||||
# irtt-server/irtt-stats are libraries published to crates.io and tagged for
|
||||
# provenance, but must not trigger cargo-dist or produce a GitHub Release.
|
||||
tag-namespace = "irtt-cli"
|
||||
tag-namespace = "irtt-rs"
|
||||
@@ -13,7 +13,7 @@ upstream executable.
|
||||
Build `irtt-rs` and ensure an upstream `irtt` executable is available:
|
||||
|
||||
```sh
|
||||
cargo build -p irtt-cli
|
||||
cargo build -p irtt-rs --bin irtt-client
|
||||
go install github.com/heistp/irtt/cmd/irtt@v0.9.1
|
||||
```
|
||||
|
||||
@@ -118,13 +118,13 @@ python3 scripts/run_netem_interop_ci.py \
|
||||
On Linux:
|
||||
|
||||
```sh
|
||||
cargo build -p irtt-cli
|
||||
cargo build -p irtt-rs --bin irtt-client
|
||||
go install github.com/heistp/irtt/cmd/irtt@v0.9.1
|
||||
python3 scripts/run_netem_interop_ci.py \
|
||||
--all \
|
||||
--skip-build \
|
||||
--upstream-irtt "$HOME/go/bin/irtt" \
|
||||
--irtt-rs-command "$PWD/target/debug/irtt-cli"
|
||||
--irtt-rs-command "$PWD/target/debug/irtt-client"
|
||||
```
|
||||
|
||||
Run one scenario:
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
|
||||
"labels": ["dependencies"],
|
||||
|
||||
"ignorePaths": [".github/workflows/irtt-cli-release.yml"],
|
||||
"ignorePaths": [".github/workflows/irtt-rs-release.yml"],
|
||||
|
||||
"lockFileMaintenance": {
|
||||
"enabled": true,
|
||||
|
||||
@@ -32,7 +32,7 @@ DEFAULT_REMOTE_TARGETS = [
|
||||
]
|
||||
DEFAULT_LOCAL_TARGET = ("127.0.0.1", "10s", "100ms", "local")
|
||||
DEFAULT_OUTPUT_ROOT = Path("target/interop-comparisons")
|
||||
DEFAULT_IRTT_RS_COMMAND = "target/debug/irtt-cli"
|
||||
DEFAULT_IRTT_RS_COMMAND = "target/debug/irtt-client"
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -163,7 +163,7 @@ def parse_args() -> argparse.Namespace:
|
||||
parser.add_argument(
|
||||
"--skip-build",
|
||||
action="store_true",
|
||||
help="Do not run cargo build -p irtt-cli before comparisons.",
|
||||
help="Do not run cargo build -p irtt-rs --bin irtt-client before comparisons.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--localhost-port",
|
||||
@@ -335,8 +335,8 @@ def check_local_server(args: argparse.Namespace) -> tuple[bool, str]:
|
||||
)
|
||||
|
||||
|
||||
def preflight_build_irtt_cli(run_dir: Path) -> bool:
|
||||
command = ["cargo", "build", "-p", "irtt-cli"]
|
||||
def preflight_build_irtt_client(run_dir: Path) -> bool:
|
||||
command = ["cargo", "build", "-p", "irtt-rs", "--bin", "irtt-client"]
|
||||
started = utc_now()
|
||||
print(f"[{started}] preflight build: {shlex.join(command)}", flush=True)
|
||||
completed = subprocess.run(
|
||||
@@ -1117,7 +1117,7 @@ def main() -> int:
|
||||
print(message, file=sys.stderr)
|
||||
cases = [case for case in cases if not case.is_local]
|
||||
|
||||
if cases and not args.skip_build and not preflight_build_irtt_cli(run_dir):
|
||||
if cases and not args.skip_build and not preflight_build_irtt_client(run_dir):
|
||||
write_index(run_dir, [], local_error)
|
||||
return 1
|
||||
|
||||
|
||||
@@ -105,13 +105,13 @@ def parse_args() -> argparse.Namespace:
|
||||
)
|
||||
parser.add_argument(
|
||||
"--irtt-rs-command",
|
||||
default=str(REPO_ROOT / "target" / "debug" / "irtt-cli"),
|
||||
help="Path or command for irtt-rs client (default: target/debug/irtt-cli).",
|
||||
default=str(REPO_ROOT / "target" / "debug" / "irtt-client"),
|
||||
help="Path or command for irtt-rs client (default: target/debug/irtt-client).",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip-build",
|
||||
action="store_true",
|
||||
help="Do not run cargo build -p irtt-cli before comparisons.",
|
||||
help="Do not run cargo build -p irtt-rs --bin irtt-client before comparisons.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sudo",
|
||||
@@ -176,7 +176,10 @@ def main() -> int:
|
||||
upstream_irtt = resolve_command(args.upstream_irtt)
|
||||
irtt_rs_command = resolve_irtt_rs_command(args.irtt_rs_command)
|
||||
if not args.skip_build:
|
||||
run_checked(["cargo", "build", "-p", "irtt-cli"], run_dir / "cargo-build")
|
||||
run_checked(
|
||||
["cargo", "build", "-p", "irtt-rs", "--bin", "irtt-client"],
|
||||
run_dir / "cargo-build",
|
||||
)
|
||||
|
||||
cleanup(args, quiet=True)
|
||||
setup_namespaces(args, run_dir)
|
||||
|
||||
Reference in new issue
Block a user