The CLI could do everything with a host except FIND one, so every headless consumer grew its own mDNS: the Decky plugin parses ~120 lines of avahi TXT escaping in Python, which drifts from the host's advert every time a key is added and makes the plugin depend on Avahi being the resolver. `discovery::discover_for(timeout)` is the bounded collector beside the streaming `browse()` the UI uses — same service type, same TXT keys, folded to one row per host. A refreshed advert wins (it carries the newer address), a removal drops the row, and dropping the receiver on the way out stops the worker so a one-shot call can't leak a browse per invocation. The verb annotates each hit against the saved-hosts store rather than handing back two lists to join: `saved`/`paired` are answered by fingerprint first and address second — the same rule every other surface uses. That is what stops a host that moved DHCP lease from reading as new, and stops a different box that inherited the old address from reading as paired. punktfunk discover [--json] [--timeout SECS] Default 3 s, capped at 30 — this is called from a Quick Access panel, and a typo'd `--timeout 3000` would hang that panel with no way to cancel. An empty LAN exits 0: a caller branching on the code is asking whether the browse ran, and it did.
85 lines
3.1 KiB
Rust
85 lines
3.1 KiB
Rust
//! Runs the REAL `punktfunk` binary and pins its self-documentation contract: help goes
|
|
//! to stdout and exits 0, an unknown verb refuses on stderr with the not-found code.
|
|
//!
|
|
//! This exists so CI *executes* the shipped binary at least once per platform. A binary
|
|
//! that compiles but is the wrong program passes every build/clippy/fmt gate — that is
|
|
//! exactly how 0.22.0 shipped a stub as `punktfunk-session` — and only a gate that runs
|
|
//! the thing catches the class. The help paths are the right probe: they touch no config
|
|
//! stores and no network, so they are safe on any runner.
|
|
#![cfg(any(target_os = "linux", windows))]
|
|
|
|
use std::process::Command;
|
|
|
|
fn punktfunk(args: &[&str]) -> std::process::Output {
|
|
Command::new(env!("CARGO_BIN_EXE_punktfunk"))
|
|
.args(args)
|
|
.output()
|
|
.expect("run punktfunk")
|
|
}
|
|
|
|
#[test]
|
|
fn bare_and_help_print_the_overview_on_stdout() {
|
|
for args in [&[][..], &["help"][..], &["--help"][..], &["-h"][..]] {
|
|
let out = punktfunk(args);
|
|
assert!(out.status.success(), "{args:?} must exit 0");
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(
|
|
stdout.contains("punktfunk pair"),
|
|
"{args:?} overview lists the verbs"
|
|
);
|
|
assert!(
|
|
stdout.contains("help <command>"),
|
|
"{args:?} overview points at per-verb help"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn per_verb_help_answers_both_spellings() {
|
|
for args in [
|
|
&["help", "launch"][..],
|
|
&["launch", "--help"][..],
|
|
&["launch", "-h"][..],
|
|
] {
|
|
let out = punktfunk(args);
|
|
assert!(out.status.success(), "{args:?} must exit 0");
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(
|
|
stdout.contains("--exec"),
|
|
"{args:?} documents launch's flags"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn version_prints_the_crate_version() {
|
|
let out = punktfunk(&["--version"]);
|
|
assert!(out.status.success());
|
|
assert!(String::from_utf8_lossy(&out.stdout).contains(env!("CARGO_PKG_VERSION")));
|
|
}
|
|
|
|
#[test]
|
|
fn unknown_verbs_refuse_with_the_not_found_code() {
|
|
let out = punktfunk(&["frobnicate"]);
|
|
assert_eq!(out.status.code(), Some(5), "unknown verb exits 5");
|
|
assert!(String::from_utf8_lossy(&out.stderr).contains("unknown command"));
|
|
|
|
let out = punktfunk(&["help", "frobnicate"]);
|
|
assert_eq!(out.status.code(), Some(5), "unknown help topic exits 5");
|
|
}
|
|
|
|
/// `discover` documents itself. Help only — the verb itself browses the LAN, which no runner
|
|
/// may be asked to do.
|
|
///
|
|
/// The Decky panel detects a too-old client by exactly the signature the test above pins
|
|
/// (exit 5 + `unknown command`), so this is the other half of that contract: on a client new
|
|
/// enough, `discover` is a verb with help rather than an unknown word.
|
|
#[test]
|
|
fn discover_documents_itself() {
|
|
let out = punktfunk(&["help", "discover"]);
|
|
assert!(out.status.success(), "discover has its own help topic");
|
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
|
assert!(stdout.contains("--timeout"), "discover documents --timeout");
|
|
assert!(stdout.contains("--json"), "discover documents --json");
|
|
}
|