Add flag to override logic and supress terminate option. (--no-kill)
Flag specifically used to block feature on socktop.io. Will remain undocumented for standard usage.
This commit is contained in:
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## Unreleased
|
||||||
|
|
||||||
|
### TUI
|
||||||
|
|
||||||
|
- **`--no-kill` flag and `SOCKTOP_NO_KILL` env var** disable the local
|
||||||
|
process-kill feature regardless of agent locality, for shared terminals and
|
||||||
|
public demos (e.g. the socktop.io webterm). Either one forces the feature
|
||||||
|
off and suppresses the `t` kill hints; the env var covers every socktop
|
||||||
|
invocation under a deployment without touching command lines. `App`'s
|
||||||
|
builder renamed `with_local` → `with_kill_enabled` to match what it now
|
||||||
|
means (locality fact AND policy).
|
||||||
|
|
||||||
## 1.60.1 — unreleased
|
## 1.60.1 — unreleased
|
||||||
|
|
||||||
Identical to 1.60.0 plus rebuilt Debian packages: the 1.60.0 debs were linked
|
Identical to 1.60.0 plus rebuilt Debian packages: the 1.60.0 debs were linked
|
||||||
|
|||||||
+15
-13
@@ -194,9 +194,10 @@ pub struct App {
|
|||||||
// Security / status flags
|
// Security / status flags
|
||||||
pub is_tls: bool,
|
pub is_tls: bool,
|
||||||
pub has_token: bool,
|
pub has_token: bool,
|
||||||
// Whether the connected agent is on this machine. Gates the local
|
// Whether the local process-kill feature (t = SIGTERM, k = SIGKILL) is
|
||||||
// process-kill feature (t = SIGTERM, k = SIGKILL).
|
// available: the connected agent is on this machine AND no policy override
|
||||||
pub is_local: bool,
|
// (--no-kill / SOCKTOP_NO_KILL) has disabled it.
|
||||||
|
pub kill_enabled: bool,
|
||||||
// Pending kill awaiting confirmation: (pid, process name). Which signal is
|
// Pending kill awaiting confirmation: (pid, process name). Which signal is
|
||||||
// sent depends on the button chosen in the confirmation modal, so it isn't
|
// sent depends on the button chosen in the confirmation modal, so it isn't
|
||||||
// decided until then.
|
// decided until then.
|
||||||
@@ -295,7 +296,7 @@ impl App {
|
|||||||
verify_hostname: false,
|
verify_hostname: false,
|
||||||
is_tls: false,
|
is_tls: false,
|
||||||
has_token: false,
|
has_token: false,
|
||||||
is_local: false,
|
kill_enabled: false,
|
||||||
pending_kill: None,
|
pending_kill: None,
|
||||||
force_compact: false,
|
force_compact: false,
|
||||||
header_title: String::new(),
|
header_title: String::new(),
|
||||||
@@ -349,9 +350,10 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enable the local process-kill feature. Only set true when the agent has
|
/// Enable the local process-kill feature. Only set true when the agent has
|
||||||
/// been verified to be on this machine (see [`crate::local`]).
|
/// been verified to be on this machine (see [`crate::local`]) and no
|
||||||
pub fn with_local(mut self, is_local: bool) -> Self {
|
/// policy override (`--no-kill`, `SOCKTOP_NO_KILL`) forbids it.
|
||||||
self.is_local = is_local;
|
pub fn with_kill_enabled(mut self, kill_enabled: bool) -> Self {
|
||||||
|
self.kill_enabled = kill_enabled;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,11 +377,11 @@ impl App {
|
|||||||
.map(|p| p.name.clone())
|
.map(|p| p.name.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Raise the kill confirmation for `pid`. No-op unless the agent is on this
|
/// Raise the kill confirmation for `pid`. No-op unless the kill feature is
|
||||||
/// machine — the same gate the keybinding uses, repeated here because this
|
/// enabled — the same gate the keybinding uses, repeated here because this
|
||||||
/// is also reachable from the details modal.
|
/// is also reachable from the details modal.
|
||||||
fn prompt_kill(&mut self, pid: u32) {
|
fn prompt_kill(&mut self, pid: u32) {
|
||||||
if !self.is_local {
|
if !self.kill_enabled {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let name = self
|
let name = self
|
||||||
@@ -1182,7 +1184,7 @@ impl App {
|
|||||||
// the details modal so it could not be reused there, and one
|
// the details modal so it could not be reused there, and one
|
||||||
// key for both entry points is one thing to remember.
|
// key for both entry points is one thing to remember.
|
||||||
// SIGTERM vs SIGKILL is chosen in the confirmation modal.
|
// SIGTERM vs SIGKILL is chosen in the confirmation modal.
|
||||||
if self.is_local
|
if self.kill_enabled
|
||||||
&& !self.modal_manager.is_active()
|
&& !self.modal_manager.is_active()
|
||||||
&& matches!(k.code, KeyCode::Char('t') | KeyCode::Char('T'))
|
&& matches!(k.code, KeyCode::Char('t') | KeyCode::Char('T'))
|
||||||
&& let Some(pid) = self.selected_process_pid
|
&& let Some(pid) = self.selected_process_pid
|
||||||
@@ -1948,7 +1950,7 @@ impl App {
|
|||||||
filtered_indices: &self.procs_filtered,
|
filtered_indices: &self.procs_filtered,
|
||||||
cached_rows: &self.procs_row_cache,
|
cached_rows: &self.procs_row_cache,
|
||||||
peak_cpu: self.procs_row_peak_cpu,
|
peak_cpu: self.procs_row_peak_cpu,
|
||||||
is_local: self.is_local,
|
kill_enabled: self.kill_enabled,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1969,7 +1971,7 @@ impl App {
|
|||||||
},
|
},
|
||||||
max_mem_bytes: self.max_process_mem_bytes,
|
max_mem_bytes: self.max_process_mem_bytes,
|
||||||
unsupported: self.process_details_unsupported,
|
unsupported: self.process_details_unsupported,
|
||||||
is_local: self.is_local,
|
kill_enabled: self.kill_enabled,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-11
@@ -25,6 +25,18 @@ pub(crate) struct ParsedArgs {
|
|||||||
processes_interval_ms: Option<u64>,
|
processes_interval_ms: Option<u64>,
|
||||||
verify_hostname: bool,
|
verify_hostname: bool,
|
||||||
compact: bool,
|
compact: bool,
|
||||||
|
no_kill: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True when the `SOCKTOP_NO_KILL` environment variable disables the process-kill
|
||||||
|
/// feature. Any value other than empty, `0`, or `false` (case-insensitive) counts
|
||||||
|
/// as set, so a deployment can export `SOCKTOP_NO_KILL=1` once and every socktop
|
||||||
|
/// launched under it — whatever its command line — has the feature off.
|
||||||
|
pub(crate) fn no_kill_from_env() -> bool {
|
||||||
|
match env::var("SOCKTOP_NO_KILL") {
|
||||||
|
Ok(v) => !v.is_empty() && v != "0" && !v.eq_ignore_ascii_case("false"),
|
||||||
|
Err(_) => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<ParsedArgs, String> {
|
pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<ParsedArgs, String> {
|
||||||
@@ -40,11 +52,12 @@ pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Pars
|
|||||||
let mut processes_interval_ms: Option<u64> = None;
|
let mut processes_interval_ms: Option<u64> = None;
|
||||||
let mut verify_hostname = false;
|
let mut verify_hostname = false;
|
||||||
let mut compact = false;
|
let mut compact = false;
|
||||||
|
let mut no_kill = false;
|
||||||
while let Some(arg) = it.next() {
|
while let Some(arg) = it.next() {
|
||||||
match arg.as_str() {
|
match arg.as_str() {
|
||||||
"-h" | "--help" => {
|
"-h" | "--help" => {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Usage: {prog} [--tls-ca CERT_PEM|-t CERT_PEM] [--verify-hostname] [--profile NAME|-P NAME] [--save] [--demo] [--compact] [--metrics-interval-ms N] [--processes-interval-ms N] [ws://HOST:PORT/ws]\n"
|
"Usage: {prog} [--tls-ca CERT_PEM|-t CERT_PEM] [--verify-hostname] [--profile NAME|-P NAME] [--save] [--demo] [--compact] [--no-kill] [--metrics-interval-ms N] [--processes-interval-ms N] [ws://HOST:PORT/ws]\n"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
"--tls-ca" | "-t" => {
|
"--tls-ca" | "-t" => {
|
||||||
@@ -70,6 +83,12 @@ pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Pars
|
|||||||
// layout switches on its own once the window gets too short.
|
// layout switches on its own once the window gets too short.
|
||||||
compact = true;
|
compact = true;
|
||||||
}
|
}
|
||||||
|
"--no-kill" => {
|
||||||
|
// Disable the local process-kill feature even when the agent is
|
||||||
|
// local. For shared/kiosk deployments; SOCKTOP_NO_KILL=1 in the
|
||||||
|
// environment does the same without touching the command line.
|
||||||
|
no_kill = true;
|
||||||
|
}
|
||||||
"--dry-run" => {
|
"--dry-run" => {
|
||||||
// intentionally undocumented
|
// intentionally undocumented
|
||||||
dry_run = true;
|
dry_run = true;
|
||||||
@@ -109,7 +128,7 @@ pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Pars
|
|||||||
url = Some(arg);
|
url = Some(arg);
|
||||||
} else {
|
} else {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Unexpected argument. Usage: {prog} [--tls-ca CERT_PEM|-t CERT_PEM] [--verify-hostname] [--profile NAME|-P NAME] [--save] [--demo] [--compact] [ws://HOST:PORT/ws]"
|
"Unexpected argument. Usage: {prog} [--tls-ca CERT_PEM|-t CERT_PEM] [--verify-hostname] [--profile NAME|-P NAME] [--save] [--demo] [--compact] [--no-kill] [ws://HOST:PORT/ws]"
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,6 +145,7 @@ pub(crate) fn parse_args<I: IntoIterator<Item = String>>(args: I) -> Result<Pars
|
|||||||
processes_interval_ms,
|
processes_interval_ms,
|
||||||
verify_hostname,
|
verify_hostname,
|
||||||
compact,
|
compact,
|
||||||
|
no_kill,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,7 +166,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if parsed.demo || matches!(parsed.profile.as_deref(), Some("demo")) {
|
if parsed.demo || matches!(parsed.profile.as_deref(), Some("demo")) {
|
||||||
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact).await;
|
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact, parsed.no_kill).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
let profiles_file = load_profiles();
|
let profiles_file = load_profiles();
|
||||||
@@ -251,7 +271,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
if (1..=names.len()).contains(&idx) {
|
if (1..=names.len()).contains(&idx) {
|
||||||
let name = &names[idx - 1];
|
let name = &names[idx - 1];
|
||||||
if name == "demo" {
|
if name == "demo" {
|
||||||
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact).await;
|
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact, parsed.no_kill).await;
|
||||||
}
|
}
|
||||||
if let Some(entry) = profiles_mut.profiles.get(name) {
|
if let Some(entry) = profiles_mut.profiles.get(name) {
|
||||||
(
|
(
|
||||||
@@ -311,7 +331,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
);
|
);
|
||||||
eprintln!("If you don't have an agent running, you can try the demo mode.");
|
eprintln!("If you don't have an agent running, you can try the demo mode.");
|
||||||
if prompt_yes_no("Would you like to start the demo mode now? [Y/n]: ") {
|
if prompt_yes_no("Would you like to start the demo mode now? [Y/n]: ") {
|
||||||
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact).await;
|
return run_demo_mode(parsed.tls_ca.as_deref(), parsed.compact, parsed.no_kill).await;
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Aborting. You can run 'socktop --help' for usage information.");
|
eprintln!("Aborting. You can run 'socktop --help' for usage information.");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@@ -324,14 +344,16 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
let is_tls = url.starts_with("wss://");
|
let is_tls = url.starts_with("wss://");
|
||||||
let has_token = url.contains("token=");
|
let has_token = url.contains("token=");
|
||||||
// Only enable local process-kill when the agent is verified to be on this
|
// Only enable local process-kill when the agent is verified to be on this
|
||||||
// machine; otherwise on-screen PIDs refer to a remote host and acting on
|
// machine — otherwise on-screen PIDs refer to a remote host and acting on
|
||||||
// them locally would signal the wrong process. See local::agent_is_local.
|
// them locally would signal the wrong process (see local::agent_is_local) —
|
||||||
let is_local = local::agent_is_local(&url);
|
// AND neither --no-kill nor SOCKTOP_NO_KILL disables it as a matter of
|
||||||
|
// policy (shared terminals, public demos).
|
||||||
|
let kill_enabled = local::agent_is_local(&url) && !parsed.no_kill && !no_kill_from_env();
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
.with_intervals(metrics_interval_ms, processes_interval_ms)
|
.with_intervals(metrics_interval_ms, processes_interval_ms)
|
||||||
.with_status(is_tls, has_token)
|
.with_status(is_tls, has_token)
|
||||||
.with_compact(parsed.compact)
|
.with_compact(parsed.compact)
|
||||||
.with_local(is_local);
|
.with_kill_enabled(kill_enabled);
|
||||||
if parsed.dry_run {
|
if parsed.dry_run {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -398,6 +420,7 @@ fn gather_intervals(
|
|||||||
async fn run_demo_mode(
|
async fn run_demo_mode(
|
||||||
_tls_ca: Option<&str>,
|
_tls_ca: Option<&str>,
|
||||||
compact: bool,
|
compact: bool,
|
||||||
|
no_kill: bool,
|
||||||
) -> Result<(), Box<dyn std::error::Error>> {
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let port = 3231;
|
let port = 3231;
|
||||||
let url = format!("ws://127.0.0.1:{port}/ws");
|
let url = format!("ws://127.0.0.1:{port}/ws");
|
||||||
@@ -413,10 +436,11 @@ async fn run_demo_mode(
|
|||||||
};
|
};
|
||||||
// Demo mode runs the real agent on loopback, so its PIDs are real local
|
// Demo mode runs the real agent on loopback, so its PIDs are real local
|
||||||
// processes — enable the local process-kill feature, gated the same way as
|
// processes — enable the local process-kill feature, gated the same way as
|
||||||
// the normal connect path (loopback resolves local).
|
// the normal connect path (loopback resolves local, --no-kill and
|
||||||
|
// SOCKTOP_NO_KILL still override).
|
||||||
let mut app = App::new()
|
let mut app = App::new()
|
||||||
.with_compact(compact)
|
.with_compact(compact)
|
||||||
.with_local(local::agent_is_local(&url));
|
.with_kill_enabled(local::agent_is_local(&url) && !no_kill && !no_kill_from_env());
|
||||||
// Demo mode connects to localhost, so disable hostname verification
|
// Demo mode connects to localhost, so disable hostname verification
|
||||||
tokio::select! { res=app.run(&url,None,false)=>{ drop(child); res } _=tokio::signal::ctrl_c()=>{ drop(child); Ok(()) } }
|
tokio::select! { res=app.run(&url,None,false)=>{ drop(child); res } _=tokio::signal::ctrl_c()=>{ drop(child); Ok(()) } }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,8 +130,8 @@ impl ModalManager {
|
|||||||
])];
|
])];
|
||||||
|
|
||||||
// Kill from here too — same key as the processes pane, and only shown
|
// Kill from here too — same key as the processes pane, and only shown
|
||||||
// when the agent is local, since that is the only case where it works.
|
// when the kill feature is enabled (agent local, no policy override).
|
||||||
if data.is_local
|
if data.kill_enabled
|
||||||
&& let Some(line) = help_text.first_mut()
|
&& let Some(line) = help_text.first_mut()
|
||||||
{
|
{
|
||||||
line.spans.push(Span::styled(
|
line.spans.push(Span::styled(
|
||||||
|
|||||||
@@ -19,9 +19,10 @@ pub struct ProcessModalData<'a> {
|
|||||||
pub history: ProcessHistoryData<'a>,
|
pub history: ProcessHistoryData<'a>,
|
||||||
pub max_mem_bytes: u64,
|
pub max_mem_bytes: u64,
|
||||||
pub unsupported: bool,
|
pub unsupported: bool,
|
||||||
/// Whether the agent is on this machine. Only used to decide whether the
|
/// Whether the process-kill feature is available (agent local, no policy
|
||||||
/// `t` kill hint is shown — the kill itself is gated in `App`.
|
/// override). Only used to decide whether the `t` kill hint is shown —
|
||||||
pub is_local: bool,
|
/// the kill itself is gated in `App`.
|
||||||
|
pub kill_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parameters for rendering scatter plot
|
/// Parameters for rendering scatter plot
|
||||||
|
|||||||
@@ -87,9 +87,10 @@ pub struct ProcessDisplayParams<'a> {
|
|||||||
/// Peak cpu_usage from the most recent cache build; used to bold the
|
/// Peak cpu_usage from the most recent cache build; used to bold the
|
||||||
/// busiest process. -1.0 if no cache.
|
/// busiest process. -1.0 if no cache.
|
||||||
pub peak_cpu: f32,
|
pub peak_cpu: f32,
|
||||||
/// Agent is on this machine, so the `t` kill hint applies. Without it the
|
/// The process-kill feature is available (agent local, no policy
|
||||||
/// hint would advertise a key that deliberately does nothing.
|
/// override), so the `t` kill hint applies. Without it the hint would
|
||||||
pub is_local: bool,
|
/// advertise a key that deliberately does nothing.
|
||||||
|
pub kill_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
@@ -464,7 +465,7 @@ pub fn draw_top_processes(f: &mut ratatui::Frame<'_>, area: Rect, params: Proces
|
|||||||
Span::styled(" details", label),
|
Span::styled(" details", label),
|
||||||
Span::styled(" · ", label),
|
Span::styled(" · ", label),
|
||||||
];
|
];
|
||||||
if params.is_local {
|
if params.kill_enabled {
|
||||||
hints.push(Span::styled("t", key));
|
hints.push(Span::styled("t", key));
|
||||||
hints.push(Span::styled(" kill", label));
|
hints.push(Span::styled(" kill", label));
|
||||||
hints.push(Span::styled(" · ", label));
|
hints.push(Span::styled(" · ", label));
|
||||||
@@ -952,7 +953,7 @@ mod click_tests {
|
|||||||
filtered_indices: &idxs,
|
filtered_indices: &idxs,
|
||||||
cached_rows: &cache,
|
cached_rows: &cache,
|
||||||
peak_cpu: peak,
|
peak_cpu: peak,
|
||||||
is_local: false,
|
kill_enabled: false,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -1066,7 +1067,7 @@ mod tooltip_tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Render the pane with a selection and return the whole buffer as text.
|
/// Render the pane with a selection and return the whole buffer as text.
|
||||||
fn rendered(name: &str, width: u16, is_local: bool) -> String {
|
fn rendered(name: &str, width: u16, kill_enabled: bool) -> String {
|
||||||
let m = metrics(name);
|
let m = metrics(name);
|
||||||
let mut cache = Vec::new();
|
let mut cache = Vec::new();
|
||||||
let peak = rebuild_row_cache(&m, &mut cache);
|
let peak = rebuild_row_cache(&m, &mut cache);
|
||||||
@@ -1088,7 +1089,7 @@ mod tooltip_tests {
|
|||||||
filtered_indices: &idxs,
|
filtered_indices: &idxs,
|
||||||
cached_rows: &cache,
|
cached_rows: &cache,
|
||||||
peak_cpu: peak,
|
peak_cpu: peak,
|
||||||
is_local,
|
kill_enabled,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -106,3 +106,53 @@ fn test_compact_flag_documented_and_accepted() {
|
|||||||
String::from_utf8_lossy(&out2.stderr)
|
String::from_utf8_lossy(&out2.stderr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_kill_flag_documented_and_accepted() {
|
||||||
|
let exe = env!("CARGO_BIN_EXE_socktop");
|
||||||
|
let out = Command::new(exe)
|
||||||
|
.args(["--no-kill", "--help"])
|
||||||
|
.output()
|
||||||
|
.expect("run socktop --no-kill --help");
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"socktop --no-kill --help did not succeed"
|
||||||
|
);
|
||||||
|
let text = format!(
|
||||||
|
"{}{}",
|
||||||
|
String::from_utf8_lossy(&out.stdout),
|
||||||
|
String::from_utf8_lossy(&out.stderr)
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
text.contains("--no-kill"),
|
||||||
|
"help text missing --no-kill\n{text}"
|
||||||
|
);
|
||||||
|
|
||||||
|
// The flag must not be mistaken for the positional URL argument.
|
||||||
|
let out2 = Command::new(exe)
|
||||||
|
.args(["--no-kill", "--dry-run", "ws://127.0.0.1:3000/ws"])
|
||||||
|
.output()
|
||||||
|
.expect("run socktop --no-kill --dry-run");
|
||||||
|
assert!(
|
||||||
|
out2.status.success(),
|
||||||
|
"socktop --no-kill with a URL was rejected: {}",
|
||||||
|
String::from_utf8_lossy(&out2.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_kill_env_var_accepted() {
|
||||||
|
// SOCKTOP_NO_KILL must not break startup — the env-only path is how the
|
||||||
|
// webterm deployment disables the kill feature for every invocation.
|
||||||
|
let exe = env!("CARGO_BIN_EXE_socktop");
|
||||||
|
let out = Command::new(exe)
|
||||||
|
.env("SOCKTOP_NO_KILL", "1")
|
||||||
|
.args(["--dry-run", "ws://127.0.0.1:3000/ws"])
|
||||||
|
.output()
|
||||||
|
.expect("run socktop with SOCKTOP_NO_KILL=1");
|
||||||
|
assert!(
|
||||||
|
out.status.success(),
|
||||||
|
"socktop with SOCKTOP_NO_KILL=1 did not succeed: {}",
|
||||||
|
String::from_utf8_lossy(&out.stderr)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user