diff --git a/src/app.rs b/src/app.rs index b86a6e6..7e33a54 100644 --- a/src/app.rs +++ b/src/app.rs @@ -717,9 +717,6 @@ impl LauncherApp { if phase == launch::Phase::StartingServices { return (launch::Readiness::Busy, "Starting…".into()); } - if self.config.fifa17_tools_dir.trim().is_empty() { - return (launch::Readiness::Attention, "Not configured".into()); - } let runtimes = self.observe_services(); let ready = runtimes.iter().filter(|(_, r)| r.ready()).count(); let blocked = runtimes @@ -764,8 +761,6 @@ impl LauncherApp { /// OpenFUT is actively being reverse engineered, so independent control of /// each moving part stays available — it is just no longer the front door. fn ui_advanced(&mut self, ui: &mut Ui) { - let tools_configured = !self.config.fifa17_tools_dir.trim().is_empty(); - ui.label( RichText::new( "Everything here happens automatically when you press Launch. These \ @@ -789,87 +784,84 @@ impl LauncherApp { ); ui.add_space(6.0); - if !tools_configured { - ui.colored_label( - theme::WARN, - "FIFA 17 tools dir not set — configure it in Settings.", - ); - } else { - let runtimes = self.observe_services(); - egui::Grid::new("advanced_services_grid") - .num_columns(4) - .spacing([12.0, 10.0]) - .min_col_width(90.0) - .show(ui, |ui| { - for (service, runtime) in runtimes { - ui.label(RichText::new(service.label()).color(theme::TEXT).strong()); - if self.controller.services.lock().stopping(service) { - theme::status_pill(ui, "Stopping", Status::Busy); - } else if runtime.running && runtime.started_by_launcher { - theme::status_pill(ui, "Running", Status::Ok); - } else if runtime.running { - theme::status_pill(ui, "Running (foreign)", Status::Warn); - } else if runtime.detail.is_some() { - theme::status_pill(ui, "Blocked", Status::Error); - } else { - theme::status_pill(ui, "Stopped", Status::Idle); - } - - // Only ever facts the launcher established. - let mut facts = Vec::new(); - if let Some(pid) = runtime.pid { - facts.push(format!("pid {pid}")); - } - if let Some(detail) = &runtime.detail { - facts.push(detail.clone()); - } - ui.label( - RichText::new(if facts.is_empty() { - "—".to_string() - } else { - facts.join(" · ") - }) - .color(theme::TEXT_FAINT) - .small(), - ); - - ui.horizontal(|ui| { - if runtime.running { - if ui - .add_enabled( - runtime.started_by_launcher, - egui::Button::new("Restart"), - ) - .clicked() - { - self.advanced_stop_service(service); - self.restart_queue.push(service); - } - if ui - .add_enabled( - runtime.started_by_launcher, - egui::Button::new("Stop"), - ) - .on_disabled_hover_text( - "Started outside this launcher — stop it where it \ - was started.", - ) - .clicked() - { - self.advanced_stop_service(service); - } - } else if ui.button("Start").clicked() { - self.advanced_start_service(service); - } - }); - ui.end_row(); + // Rendered unconditionally: the companions are workspace binaries + // resolved relative to this launcher, so there is no configuration that + // could make this panel inapplicable. A binary that is genuinely absent + // surfaces as that service's own spawn error, not as a hidden panel. + let runtimes = self.observe_services(); + egui::Grid::new("advanced_services_grid") + .num_columns(4) + .spacing([12.0, 10.0]) + .min_col_width(90.0) + .show(ui, |ui| { + for (service, runtime) in runtimes { + ui.label(RichText::new(service.label()).color(theme::TEXT).strong()); + if self.controller.services.lock().stopping(service) { + theme::status_pill(ui, "Stopping", Status::Busy); + } else if runtime.running && runtime.started_by_launcher { + theme::status_pill(ui, "Running", Status::Ok); + } else if runtime.running { + theme::status_pill(ui, "Running (foreign)", Status::Warn); + } else if runtime.detail.is_some() { + theme::status_pill(ui, "Blocked", Status::Error); + } else { + theme::status_pill(ui, "Stopped", Status::Idle); } - }); - if let Some((ok, msg)) = &self.local_services_message { - ui.add_space(6.0); - ui.colored_label(if *ok { theme::SUCCESS } else { theme::ERROR }, msg); - } + // Only ever facts the launcher established. + let mut facts = Vec::new(); + if let Some(pid) = runtime.pid { + facts.push(format!("pid {pid}")); + } + if let Some(detail) = &runtime.detail { + facts.push(detail.clone()); + } + ui.label( + RichText::new(if facts.is_empty() { + "—".to_string() + } else { + facts.join(" · ") + }) + .color(theme::TEXT_FAINT) + .small(), + ); + + ui.horizontal(|ui| { + if runtime.running { + if ui + .add_enabled( + runtime.started_by_launcher, + egui::Button::new("Restart"), + ) + .clicked() + { + self.advanced_stop_service(service); + self.restart_queue.push(service); + } + if ui + .add_enabled( + runtime.started_by_launcher, + egui::Button::new("Stop"), + ) + .on_disabled_hover_text( + "Started outside this launcher — stop it where it \ + was started.", + ) + .clicked() + { + self.advanced_stop_service(service); + } + } else if ui.button("Start").clicked() { + self.advanced_start_service(service); + } + }); + ui.end_row(); + } + }); + + if let Some((ok, msg)) = &self.local_services_message { + ui.add_space(6.0); + ui.colored_label(if *ok { theme::SUCCESS } else { theme::ERROR }, msg); } ui.add_space(14.0); @@ -1079,8 +1071,6 @@ impl LauncherApp { /// sequence uses, so the two can never disagree about what is running. fn advanced_start_service(&mut self, service: crate::local_services::Service) { let spec = crate::local_services::SpawnSpec { - python: self.config.fifa17_python.clone(), - tools_dir: self.config.fifa17_tools_dir.clone(), persona_id: self.config.fut_persona_id, persona_name: self.config.fut_persona_name.clone(), capability: match service { @@ -1467,24 +1457,6 @@ impl LauncherApp { .text_edit_singleline(&mut self.config.fifa_game_dir) .changed(); ui.end_row(); - - ui.label(RichText::new("FIFA17 tools dir:").color(theme::TEXT_WEAK)); - changed |= ui - .add( - egui::TextEdit::singleline(&mut self.config.fifa17_tools_dir) - .hint_text("fifa17-recon/tools (LSX + autopatch scripts)"), - ) - .changed(); - ui.end_row(); - - ui.label(RichText::new("Python:").color(theme::TEXT_WEAK)); - changed |= ui - .add( - egui::TextEdit::singleline(&mut self.config.fifa17_python) - .hint_text("python3"), - ) - .changed(); - ui.end_row(); }); }); diff --git a/src/config.rs b/src/config.rs index 49dbdb8..6be3c54 100644 --- a/src/config.rs +++ b/src/config.rs @@ -168,26 +168,6 @@ pub struct LauncherConfig { /// Dead EA hostnames that must resolve to `openfut_server_host`. #[serde(default)] pub ea_hostnames: Vec, - - // ── FIFA 17 local companion services (client-side, run on THIS machine) ── - // FIFA 17's FUT flow needs two pieces that are inherently local to the game - // box and cannot move to the server: the LSX Origin emulator (the game dials - // it on the hardcoded loopback 127.0.0.1:4216) and autopatch (patches - // FIFA17.exe process memory for ProtoSSL cert-verify). The launcher manages - // both as child processes. The heavy responders (Blaze/UTAS/roster/POW) run - // in the server container; these two stay here. - /// Directory holding the FIFA 17 Python responders (fifa17-recon `tools/`). - /// Empty means the local-services feature is unconfigured and its controls - /// stay disabled. - #[serde(default)] - pub fifa17_tools_dir: String, - /// Python interpreter used to run the local companion services. - #[serde(default = "default_python")] - pub fifa17_python: String, -} - -fn default_python() -> String { - "python3".to_string() } fn default_https_port() -> u16 { @@ -271,11 +251,6 @@ impl Default for LauncherConfig { game_profile: GameProfile::default(), ea_redirect_probe_ip: String::new(), ea_hostnames: Vec::new(), - fifa17_tools_dir: base - .join("fifa17-recon/tools") - .to_string_lossy() - .into(), - fifa17_python: default_python(), } } } @@ -354,19 +329,6 @@ impl LauncherConfig { self.server_config().validate().map_err(|e| e.to_string()) } - /// Validate the client-local FIFA 17 service configuration. Filesystem - /// existence is checked by the process launcher immediately before spawn; - /// this ensures required user configuration is never silently invented. - pub fn validate_local_services(&self) -> Result<(), String> { - if self.fifa17_tools_dir.trim().is_empty() { - return Err("No FIFA 17 tools dir configured. Set it in Settings.".into()); - } - if self.fifa17_python.trim().is_empty() { - return Err("No Python interpreter configured. Set it in Settings.".into()); - } - Ok(()) - } - /// Validate every configuration value required by the one-button FIFA 17 /// launch path. Runtime state such as hook deployment is checked by the UI. pub fn validate_launch_config(&self) -> Result<(), String> { @@ -384,7 +346,7 @@ impl LauncherConfig { .into(), ); } - self.validate_local_services() + Ok(()) } pub fn validate_account(&self) -> Result<(), String> { @@ -508,31 +470,7 @@ mod tests { } #[test] - fn local_services_require_tools_dir_and_python() { - let mut c = LauncherConfig::default(); - c.fifa17_tools_dir.clear(); - assert!(c - .validate_local_services() - .unwrap_err() - .contains("tools dir")); - - c.fifa17_tools_dir = "/tmp/fifa17-tools".into(); - c.fifa17_python.clear(); - assert!(c.validate_local_services().unwrap_err().contains("Python")); - } - - #[test] - fn local_services_accept_explicit_configuration() { - let c = LauncherConfig { - fifa17_tools_dir: "/tmp/fifa17-tools".into(), - fifa17_python: "/usr/bin/python3".into(), - ..LauncherConfig::default() - }; - assert!(c.validate_local_services().is_ok()); - } - - #[test] - fn launch_config_requires_server_local_services_and_command() { + fn launch_config_requires_server_account_and_command() { let mut c = LauncherConfig::default(); assert!(c.validate_launch_config().is_err()); @@ -545,14 +483,6 @@ mod tests { .contains("launch command")); c.game_launch_command = "/home/alex/Desktop/launch-fifa17.sh".into(); - c.fifa17_tools_dir.clear(); - assert!(c - .validate_launch_config() - .unwrap_err() - .contains("tools dir")); - - c.fifa17_tools_dir = "/home/alex/Documents/OpenFUT/fifa17-recon/tools".into(); - c.fifa17_python = "/usr/bin/python3".into(); assert!(c.validate_launch_config().is_ok()); } @@ -580,8 +510,6 @@ mod tests { openfut_server_host: "10.10.0.120".into(), fut_persona_id: 1, fut_persona_name: "X".into(), - fifa17_tools_dir: "/tmp/tools".into(), - fifa17_python: "/usr/bin/python3".into(), ..LauncherConfig::default() }; c.game_launch_command.clear(); @@ -609,8 +537,6 @@ mod tests { openfut_server_host: "10.10.0.120".into(), fut_persona_id: 1, fut_persona_name: "X".into(), - fifa17_tools_dir: "/tmp/tools".into(), - fifa17_python: "/usr/bin/python3".into(), game_launch_command: "/home/u/launch.sh".into(), ..LauncherConfig::default() }; diff --git a/src/launch.rs b/src/launch.rs index 9649ad9..4ecf806 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -481,8 +481,6 @@ impl LaunchOps for RealOps { fn ensure_service(&mut self, service: Service) -> Result { let spec = SpawnSpec { - python: self.config.fifa17_python.clone(), - tools_dir: self.config.fifa17_tools_dir.clone(), persona_id: self.config.fut_persona_id, persona_name: self.config.fut_persona_name.clone(), // Only autopatch advertises the verified resolver guard, so only it diff --git a/src/local_services.rs b/src/local_services.rs index 89945b4..6a923c6 100644 --- a/src/local_services.rs +++ b/src/local_services.rs @@ -16,7 +16,7 @@ use parking_lot::Mutex; use std::{ net::{Ipv4Addr, SocketAddr, SocketAddrV4, TcpListener}, - path::Path, + path::{Path, PathBuf}, process::{Child, Command, Stdio}, sync::{mpsc, Arc}, time::{Duration, Instant}, @@ -56,25 +56,48 @@ impl Service { } } - /// The responder script filename inside the tools dir. - fn script(self) -> &'static str { + /// The companion's executable name. + /// + /// These were Python responder scripts run through a configured interpreter. They + /// are now Rust binaries built from this workspace (`openfut-lsx`, + /// `openfut-autopatch`), which removes the interpreter and the tools directory + /// from the launch contract entirely: no `python3` to locate, no script path to + /// configure, and no chance of running a stale checkout's copy. + fn binary(self) -> &'static str { match self { - Service::Lsx => "lsx_responder_v2.py", - Service::Autopatch => "autopatch.py", + Service::Lsx => "openfut-lsx", + Service::Autopatch => "openfut-autopatch", } } } -fn command_parts(service: Service, python: &str, tools_dir: &Path) -> CommandParts { - let mut args = vec![tools_dir - .join(service.script()) - .to_string_lossy() - .into_owned()]; +/// Absolute path to a companion binary. +/// +/// Prefers a sibling of the running launcher, which is what a workspace build and any +/// sane install layout both produce, and falls back to the bare name so a +/// PATH-installed binary still works. Returning the bare name rather than failing +/// keeps `spawn` responsible for reporting a missing binary, with one error message +/// instead of two. +fn resolve_binary(service: Service) -> PathBuf { + let name = service.binary(); + if let Some(dir) = std::env::current_exe().ok().and_then(|p| p.parent().map(Path::to_path_buf)) { + let sibling = dir.join(name); + if sibling.is_file() { + return sibling; + } + } + PathBuf::from(name) +} + +fn command_parts(service: Service) -> CommandParts { + let mut args = Vec::new(); if service == Service::Autopatch { + // autopatch exits when the launcher does, so it cannot outlive its owner and + // keep writing to a client the launcher no longer manages. args.extend(["--launcher-pid".to_string(), std::process::id().to_string()]); } CommandParts { - program: python.to_string(), + program: resolve_binary(service).to_string_lossy().into_owned(), args, } } @@ -265,14 +288,17 @@ pub fn lsx_port_busy() -> bool { } } -/// PID of a process running `service`'s responder script that this launcher does +/// PID of a process running `service`'s companion binary that this launcher does /// not own, if there is one. /// /// Scans `/proc` — no extra dependency, no privilege, and no guessing: a service /// left running by a previous launcher instance or started by hand from a shell /// is a real state the UI has to be able to report, and cleanup has to respect. +/// +/// Matches argv entries rather than `comm`, because `comm` is truncated to 15 +/// characters by the kernel and would misreport these names. pub fn foreign_pid(service: Service, ours: Option) -> Option { - let script = service.script(); + let binary = service.binary(); let self_pid = std::process::id(); let entries = std::fs::read_dir("/proc").ok()?; for entry in entries.flatten() { @@ -285,10 +311,13 @@ pub fn foreign_pid(service: Service, ours: Option) -> Option { let Ok(cmdline) = std::fs::read(entry.path().join("cmdline")) else { continue; }; - if cmdline - .split(|b| *b == 0) - .any(|arg| String::from_utf8_lossy(arg).ends_with(script)) - { + if cmdline.split(|b| *b == 0).any(|arg| { + // Compare the file name, so `/path/to/openfut-lsx` matches while an + // unrelated argument that merely ends with the same text does not. + Path::new(&*String::from_utf8_lossy(arg)) + .file_name() + .is_some_and(|n| n == binary) + }) { return Some(pid); } } @@ -420,8 +449,6 @@ impl ServiceSupervisor { } let child = spawn( service, - &spec.python, - &spec.tools_dir, spec.persona_id, &spec.persona_name, spec.capability, @@ -450,24 +477,22 @@ impl ServiceSupervisor { /// Everything [`spawn`] needs, bundled so the launch sequence can hand it over /// as one value per service. pub struct SpawnSpec { - pub python: String, - pub tools_dir: String, pub persona_id: u64, pub persona_name: String, pub capability: Option, } -/// Spawn a companion service. `python` is the interpreter, `tools_dir` the -/// directory holding the responder scripts. Streams stdout+stderr into `log`. -/// Returns an error (without spawning) if the tools dir or script is missing. +/// Spawn a companion service and stream its stdout+stderr into `log`. +/// +/// Returns an error without spawning if the binary is missing, which is the only +/// precondition left now that the companions are workspace binaries rather than +/// Python scripts run from a configured tools directory. /// /// `capability` is the backend-registration wiring + shared per-FIFA-process /// capability sink — `Some(..)` for autopatch (whose stdout advertises the /// verified resolver guard) and `None` for LSX. pub fn spawn( service: Service, - python: &str, - tools_dir: &str, persona_id: u64, persona_name: &str, capability: Option, @@ -475,35 +500,28 @@ pub fn spawn( ) -> anyhow::Result { use std::io::{BufRead, BufReader}; - let dir = Path::new(tools_dir); - if !dir.is_dir() { - anyhow::bail!( - "FIFA 17 tools dir not found: {} (set it in Settings)", - dir.display() - ); - } - let script_path = dir.join(service.script()); - if !script_path.exists() { - anyhow::bail!( - "{} not found in tools dir: {}", - service.script(), - script_path.display() - ); - } - let label = service.label(); + let parts = command_parts(service); + let program = Path::new(&parts.program); + // Only a resolved absolute path can be checked up front; a bare name is left to + // the OS to resolve through PATH, and a failure there is reported by spawn below. + if program.is_absolute() && !program.is_file() { + anyhow::bail!( + "{label} binary not found: {} — build the workspace so it sits beside the launcher", + program.display() + ); + } - // Both services use the configured interpreter and absolute script path; - // neither invents a Python installation path. Autopatch receives launcher - // ownership and a per-user runtime log so stale root-owned /tmp files cannot - // block startup. - let parts = command_parts(service, python, dir); let mut cmd = Command::new(&parts.program); cmd.args(&parts.args); if service == Service::Lsx { + // The persona LSX reports has to equal what Blaze returns in + // LoginResponse.SESS.PDTL and what UTAS serves as userInfo.personaId; the + // constraint is cross-layer agreement, not any particular value. cmd.env("FUT_PERSONA_ID", persona_id.to_string()) .env("FUT_PERSONA_NAME", persona_name); } else if service == Service::Autopatch { + // A per-user runtime log, so a stale root-owned /tmp file cannot block startup. let log_path = std::env::var_os("XDG_RUNTIME_DIR") .map(std::path::PathBuf::from) .unwrap_or_else(std::env::temp_dir) @@ -512,19 +530,24 @@ pub fn spawn( } // Put each companion in its own process group for lifecycle isolation. cmd.process_group(0); - cmd.current_dir(dir) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()); + cmd.stdout(Stdio::piped()).stderr(Stdio::piped()); log.lock().push(format!( - "[launcher] starting {label}: {} {}", - python, - script_path.display(), + "[launcher] starting {label}: {}{}", + parts.program, + parts + .args + .iter() + .fold(String::new(), |mut acc, a| { + acc.push(' '); + acc.push_str(a); + acc + }), )); let mut child = cmd .spawn() - .map_err(|e| anyhow::anyhow!("failed to start {label} ({}): {e}", service.script()))?; + .map_err(|e| anyhow::anyhow!("failed to start {label} ({}): {e}", service.binary()))?; if let Some(out) = child.stdout.take() { let buf = Arc::clone(&log); @@ -606,27 +629,38 @@ mod tests { use super::*; #[test] - fn lsx_runs_python_directly() { - let parts = command_parts(Service::Lsx, "/usr/bin/python3", Path::new("/tmp/tools")); - assert_eq!(parts.program, "/usr/bin/python3"); - assert_eq!(parts.args, vec!["/tmp/tools/lsx_responder_v2.py"]); + fn lsx_runs_its_own_binary_with_no_arguments() { + let parts = command_parts(Service::Lsx); + assert_eq!( + Path::new(&parts.program).file_name().unwrap(), + "openfut-lsx" + ); + assert!(parts.args.is_empty(), "{:?}", parts.args); } #[test] - fn autopatch_runs_python_directly_with_launcher_ownership() { - let parts = command_parts( - Service::Autopatch, - "/usr/bin/python3", - Path::new("/tmp/tools"), + fn autopatch_runs_its_own_binary_with_launcher_ownership() { + let parts = command_parts(Service::Autopatch); + assert_eq!( + Path::new(&parts.program).file_name().unwrap(), + "openfut-autopatch" ); - assert_eq!(parts.program, "/usr/bin/python3"); + // The launcher pid is how autopatch learns to exit with its owner. assert_eq!( parts.args, - vec![ - "/tmp/tools/autopatch.py", - "--launcher-pid", - &std::process::id().to_string(), - ] + vec!["--launcher-pid", &std::process::id().to_string()] + ); + } + + #[test] + fn a_companion_binary_is_looked_up_by_file_name_not_a_suffix_match() { + // Guards the foreign-process scan: an argv entry that merely ends with the + // binary name (a log path, say) must not be mistaken for the service. + assert_eq!(Service::Lsx.binary(), "openfut-lsx"); + assert_eq!(Service::Autopatch.binary(), "openfut-autopatch"); + assert_eq!( + Path::new("/var/log/my-openfut-lsx").file_name().unwrap(), + "my-openfut-lsx" ); } diff --git a/src/preflight.rs b/src/preflight.rs index d9dccda..205dc78 100644 --- a/src/preflight.rs +++ b/src/preflight.rs @@ -88,7 +88,7 @@ impl Check { /// Run every applicable check. Order is the order the game exercises them. pub fn run(cfg: &LauncherConfig) -> Vec { vec![ - ptrace_scope(cfg), + ptrace_scope(), ea_redirect(cfg), hostname_mapping(cfg), backend_reachable(cfg), @@ -109,16 +109,12 @@ pub fn warnings(checks: &[Check]) -> usize { /// autopatch writes to FIFA's process memory; Yama blocks that unless /// `ptrace_scope` is 0. At 1 the patch silently does nothing and the game fails /// its TLS handshake much later, with no message naming the cause. -fn ptrace_scope(cfg: &LauncherConfig) -> Check { +/// +/// Unconditional. autopatch is a workspace binary that ships alongside the +/// launcher, so there is no configuration that could make this inapplicable — +/// every launch runs it. +fn ptrace_scope() -> Check { const NAME: &str = "ptrace_scope (autopatch)"; - // `fifa17_tools_dir` carries a conventional default, so a non-empty value - // does not mean the tools are installed. Key off the directory actually - // existing: that is what decides whether autopatch will run at all, and it - // keeps this from failing on a machine that never uses local services. - let tools = cfg.fifa17_tools_dir.trim(); - if tools.is_empty() || !std::path::Path::new(tools).is_dir() { - return Check::skip(NAME, "no local services installed"); - } match std::fs::read_to_string(PTRACE_SCOPE) { Ok(v) => ptrace_verdict(&v), // Not every kernel has Yama. Absent means unenforced, which is what we want. @@ -336,12 +332,19 @@ mod tests { fn an_unconfigured_launcher_skips_rather_than_passes() { // The distinction that matters: a fresh config must not display a column // of green ticks. "Not checked" is not "checked and fine". + // + // `ptrace_scope` is excluded because it is no longer configuration + // dependent: it reads this machine's Yama setting and reports a real + // verdict either way. `only_ptrace_scope_zero_lets_autopatch_work` + // covers it. let mut c = cfg(); - // `default()` points these at conventional paths whose existence varies - // by machine. Pin them so the assertion is about the code, not this box. - c.fifa17_tools_dir = "/nonexistent/openfut-tools".into(); + // `default()` points this at a conventional path whose existence varies + // by machine. Pin it so the assertion is about the code, not this box. c.fifa_game_dir = "/nonexistent/fifa-game-dir".into(); - let checks = run(&c); + let checks: Vec = run(&c) + .into_iter() + .filter(|k| k.name != "ptrace_scope (autopatch)") + .collect(); assert!( checks.iter().all(|k| k.state == State::Skipped), "{checks:#?}" @@ -360,16 +363,6 @@ mod tests { assert!(ptrace_verdict("1").detail.contains("Arm client")); } - #[test] - fn ptrace_is_skipped_when_the_tools_dir_does_not_exist() { - // Regression: the gate used to be "is the field non-empty", and the - // field has a default — so this check ran (and failed) on machines that - // never use autopatch at all. - let mut c = cfg(); - c.fifa17_tools_dir = "/nonexistent/openfut-tools".into(); - assert_eq!(ptrace_scope(&c).state, State::Skipped); - } - #[test] fn a_malformed_probe_ip_fails_loudly_instead_of_being_skipped() { let mut c = cfg(); @@ -432,13 +425,6 @@ mod tests { assert_eq!(hostname_mapping(&c).state, State::Pass); } - #[test] - fn ptrace_check_is_skipped_when_local_services_are_not_configured() { - let mut c = cfg(); - c.fifa17_tools_dir.clear(); - assert_eq!(ptrace_scope(&c).state, State::Skipped); - } - #[test] fn a_dead_backend_port_is_reported_as_a_failure() { let mut c = cfg();