diff options
| -rw-r--r-- | mullvad-tests/src/bin/mock_openvpn.rs | 35 | ||||
| -rw-r--r-- | mullvad-tests/src/lib.rs | 119 | ||||
| -rw-r--r-- | mullvad-tests/tests/connection.rs | 27 |
3 files changed, 119 insertions, 62 deletions
diff --git a/mullvad-tests/src/bin/mock_openvpn.rs b/mullvad-tests/src/bin/mock_openvpn.rs index ac7e2c2b39..cbc5b6caf6 100644 --- a/mullvad-tests/src/bin/mock_openvpn.rs +++ b/mullvad-tests/src/bin/mock_openvpn.rs @@ -1,30 +1,32 @@ -extern crate notify; +extern crate mullvad_tests; use std::env; use std::fs::{self, File}; use std::io::{self, Read, Write}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; use std::sync::mpsc; use std::thread; +use std::time::Duration; -use notify::{raw_watcher, RawEvent, RecursiveMode, Watcher}; +use mullvad_tests::{watch_event, PathWatcher}; + +const MAX_EVENT_TIME: Duration = Duration::from_secs(60); fn main() { let (file, path) = create_args_file(); - let path_to_wait_for = path; - let path_to_remove = path_to_wait_for.clone(); let (finished_tx, finished_rx) = mpsc::channel(); + let watcher = PathWatcher::watch(&path).expect("Failed to watch file for events"); write_command_line(file); wait_thread(wait_for_stdin_to_be_closed, finished_tx.clone()); wait_thread( - move || wait_for_file_to_be_deleted(path_to_wait_for), + move || wait_for_file_to_be_deleted(watcher, MAX_EVENT_TIME), finished_tx, ); let _ = finished_rx.recv(); - let _ = fs::remove_file(path_to_remove); + let _ = fs::remove_file(path); } fn create_args_file() -> (File, PathBuf) { @@ -61,19 +63,8 @@ fn wait_for_stdin_to_be_closed() { let _ignore_bytes = io::stdin().bytes().last(); } -fn wait_for_file_to_be_deleted<P: AsRef<Path>>(file: P) { - let file = file.as_ref(); - let (tx, rx) = mpsc::channel(); - - if let Ok(mut watcher) = raw_watcher(tx) { - if watcher.watch(&file, RecursiveMode::NonRecursive).is_ok() { - for event in rx { - if let RawEvent { op: Ok(op), .. } = event { - if op.contains(notify::op::REMOVE) { - break; - } - } - } - } - } +fn wait_for_file_to_be_deleted(mut watcher: PathWatcher, timeout: Duration) { + let _ignore_event = watcher + .set_timeout(timeout) + .find(|&event| event == watch_event::REMOVE); } diff --git a/mullvad-tests/src/lib.rs b/mullvad-tests/src/lib.rs index 9bc4755827..534bf11003 100644 --- a/mullvad-tests/src/lib.rs +++ b/mullvad-tests/src/lib.rs @@ -23,7 +23,7 @@ use std::thread; use std::time::{Duration, Instant}; use mullvad_ipc_client::DaemonRpcClient; -use notify::{op, RawEvent, RecursiveMode, Watcher}; +use notify::{RawEvent, RecommendedWatcher, RecursiveMode, Watcher}; use openvpn_plugin::types::OpenVpnPluginEvent; use os_pipe::{pipe, PipeReader}; use talpid_ipc::WsIpcClient; @@ -32,6 +32,8 @@ use tempfile::TempDir; use self::mock_openvpn::MOCK_OPENVPN_ARGS_FILE; use self::platform_specific::*; +pub use self::notify::op::{self as watch_event, Op as WatchEvent}; + type Result<T> = ::std::result::Result<T, String>; #[cfg(unix)] @@ -53,44 +55,97 @@ mod platform_specific { pub const TALPID_OPENVPN_PLUGIN_FILE: &str = "talpid_openvpn_plugin.dll"; } -pub fn wait_for_file_write_finish<P: AsRef<Path>>(file_path: P, timeout: Duration) { - let file_path = file_path.as_ref(); - let parent_dir = file_path.parent().expect("Missing file parent directory"); +pub struct PathWatcher { + events: mpsc::Receiver<RawEvent>, + path: PathBuf, + timeout: Duration, + _watcher: RecommendedWatcher, +} + +impl PathWatcher { + pub fn watch<P: AsRef<Path>>(file_path: P) -> Result<Self> { + let file_path = file_path.as_ref(); + let parent_dir = file_path + .parent() + .ok_or_else(|| "Missing file parent directory")?; + + let absolute_parent_dir = parent_dir + .canonicalize() + .map_err(|_| "Failed to get absolute path to watch")?; + let file_name = file_path + .file_name() + .ok_or_else(|| "Missing file name of file path to watch")?; + let absolute_file_path = absolute_parent_dir.join(file_name); - let absolute_parent_dir = parent_dir - .canonicalize() - .expect("Failed to get absolute path to watch"); - let file_name = file_path - .file_name() - .expect("Missing file name of file path to watch"); - let absolute_file_path = absolute_parent_dir.join(file_name); + let (tx, rx) = mpsc::channel(); + let mut watcher = notify::raw_watcher(tx).map_err(|_| { + format!( + "Failed to create watcher of file system events to watch {}", + file_path.display() + ) + })?; - let (tx, rx) = mpsc::channel(); - let mut watcher = notify::raw_watcher(tx).expect("Failed to listen for file system events"); - let start = Instant::now(); - let mut remaining_time = Some(timeout); + watcher + .watch(absolute_parent_dir, RecursiveMode::Recursive) + .map_err(|_| { + format!( + "Failed to start watching for file system events from {}", + file_path.display() + ) + })?; + + Ok(PathWatcher { + events: rx, + path: absolute_file_path, + timeout: Duration::from_secs(5), + _watcher: watcher, + }) + } - watcher - .watch(absolute_parent_dir, RecursiveMode::NonRecursive) - .expect("Failed to listen for file system events on directory"); + pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { + self.timeout = timeout; + self + } - if !file_path.exists() { - while let Some(wait_time) = remaining_time { - let event = rx.recv_timeout(wait_time); + pub fn assert_create_write_close_sequence(&mut self) { + assert_eq!(self.next(), Some(watch_event::CREATE)); + assert_eq!(self.next(), Some(watch_event::WRITE)); - if let Ok(RawEvent { - path: Some(path), - op: Ok(op), - .. - }) = event - { - if op.contains(op::CLOSE_WRITE) && path == absolute_file_path { + loop { + match self.next() { + Some(watch_event::WRITE) => continue, + event => { + assert_eq!(event, Some(watch_event::CLOSE_WRITE)); break; } } + } + } +} - remaining_time = timeout.checked_sub(start.elapsed()); +impl Iterator for PathWatcher { + type Item = WatchEvent; + + fn next(&mut self) -> Option<Self::Item> { + let start = Instant::now(); + + while let Some(remaining_time) = self.timeout.checked_sub(start.elapsed()) { + match self.events.recv_timeout(remaining_time) { + Ok(RawEvent { + path: Some(path), + op: Ok(op), + .. + }) => if path == self.path { + return Some(op); + } else { + continue; + }, + Ok(_) => continue, + Err(_) => return None, + } } + + None } } @@ -232,7 +287,11 @@ impl DaemonRunner { pub fn rpc_client(&mut self) -> Result<DaemonRpcClient> { if !self.rpc_address_file.exists() { - wait_for_file_write_finish(&self.rpc_address_file, Duration::from_secs(10)); + let _ = PathWatcher::watch(&self.rpc_address_file).map(|mut events| { + events + .set_timeout(Duration::from_secs(10)) + .find(|&event| event == watch_event::CLOSE_WRITE) + }); } DaemonRpcClient::with_insecure_rpc_address_file(&self.rpc_address_file) diff --git a/mullvad-tests/tests/connection.rs b/mullvad-tests/tests/connection.rs index 91a110fd8a..7ae5efca21 100644 --- a/mullvad-tests/tests/connection.rs +++ b/mullvad-tests/tests/connection.rs @@ -10,7 +10,7 @@ use std::sync::mpsc; use std::time::Duration; use mullvad_tests::mock_openvpn::search_openvpn_args; -use mullvad_tests::{wait_for_file_write_finish, DaemonRunner, MockOpenVpnPluginRpcClient}; +use mullvad_tests::{watch_event, DaemonRunner, MockOpenVpnPluginRpcClient, PathWatcher}; use mullvad_types::states::{DaemonState, SecurityState, TargetState}; #[cfg(target_os = "linux")] @@ -44,15 +44,14 @@ fn spawns_openvpn() { let mut daemon = DaemonRunner::spawn(); let mut rpc_client = daemon.rpc_client().unwrap(); let openvpn_args_file = daemon.mock_openvpn_args_file(); + let mut openvpn_args_file_events = PathWatcher::watch(&openvpn_args_file).unwrap(); assert!(!openvpn_args_file.exists()); rpc_client.set_account(Some("123456".to_owned())).unwrap(); rpc_client.connect().unwrap(); - wait_for_file_write_finish(&openvpn_args_file, Duration::from_secs(5)); - - assert!(openvpn_args_file.exists()); + openvpn_args_file_events.assert_create_write_close_sequence(); } #[test] @@ -60,20 +59,22 @@ fn respawns_openvpn_if_it_crashes() { let mut daemon = DaemonRunner::spawn(); let mut rpc_client = daemon.rpc_client().unwrap(); let openvpn_args_file = daemon.mock_openvpn_args_file(); + let mut openvpn_args_file_events = PathWatcher::watch(&openvpn_args_file).unwrap(); + + openvpn_args_file_events.set_timeout(Duration::from_secs(10)); assert!(!openvpn_args_file.exists()); rpc_client.set_account(Some("123456".to_owned())).unwrap(); rpc_client.connect().unwrap(); - wait_for_file_write_finish(&openvpn_args_file, Duration::from_secs(5)); + openvpn_args_file_events.assert_create_write_close_sequence(); // Stop OpenVPN by removing the mock OpenVPN arguments file fs::remove_file(&openvpn_args_file).expect("Failed to remove the mock OpenVPN arguments file"); + assert_eq!(openvpn_args_file_events.next(), Some(watch_event::REMOVE)); - wait_for_file_write_finish(&openvpn_args_file, Duration::from_secs(5)); - - assert!(openvpn_args_file.exists()); + openvpn_args_file_events.assert_create_write_close_sequence(); } #[test] @@ -189,12 +190,14 @@ fn returns_to_connecting_state() { let mut daemon = DaemonRunner::spawn(); let mut rpc_client = daemon.rpc_client().unwrap(); let openvpn_args_file = daemon.mock_openvpn_args_file(); + let mut openvpn_args_file_events = PathWatcher::watch(&openvpn_args_file).unwrap(); let state_events = rpc_client.new_state_subscribe().unwrap(); rpc_client.set_account(Some("123456".to_owned())).unwrap(); rpc_client.connect().unwrap(); assert_state_event(&state_events, CONNECTING_STATE); + openvpn_args_file_events.assert_create_write_close_sequence(); let mut mock_plugin_client = create_mock_openvpn_plugin_client(openvpn_args_file); @@ -206,7 +209,8 @@ fn returns_to_connecting_state() { mock_plugin_client.route_predown().unwrap(); // Wait for new OpenVPN instance - wait_for_file_write_finish(&openvpn_args_file, Duration::from_secs(5)); + assert_eq!(openvpn_args_file_events.next(), Some(watch_event::REMOVE)); + openvpn_args_file_events.assert_create_write_close_sequence(); assert_state_event(&state_events, CONNECTING_STATE); assert_eq!(rpc_client.get_state().unwrap(), CONNECTING_STATE); @@ -265,7 +269,10 @@ fn create_mock_openvpn_plugin_client<P: AsRef<Path>>( fn get_plugin_arguments<P: AsRef<Path>>(openvpn_args_file_path: P) -> (String, String) { let args_file_path = openvpn_args_file_path.as_ref(); - wait_for_file_write_finish(&args_file_path, Duration::from_secs(5)); + if !args_file_path.exists() { + let _wait_for_args_file = PathWatcher::watch(&args_file_path) + .map(|mut events| events.find(|&event| event == watch_event::CLOSE_WRITE)); + } let mut arguments = search_openvpn_args(&args_file_path, OPENVPN_PLUGIN_NAME).skip(1); |
