diff options
| author | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-07-03 11:18:59 -0300 |
|---|---|---|
| committer | Janito Vaqueiro Ferreira Filho <janito@mullvad.net> | 2018-07-03 11:18:59 -0300 |
| commit | d9e32ed3b33a146dd33d2b92bb35cd20b9922453 (patch) | |
| tree | 485e51a71b032f098c6e6ead50ef8a78496b071c | |
| parent | 5d17110366adb7fa1e69c5b350e28523b0fb85d4 (diff) | |
| parent | 9865af5d41906adadf3e081f785134424397db43 (diff) | |
| download | mullvadvpn-d9e32ed3b33a146dd33d2b92bb35cd20b9922453.tar.xz mullvadvpn-d9e32ed3b33a146dd33d2b92bb35cd20b9922453.zip | |
Merge branch 'account-tests'
| -rw-r--r-- | mullvad-tests/src/bin/mock_openvpn.rs | 37 | ||||
| -rw-r--r-- | mullvad-tests/src/lib.rs | 174 | ||||
| -rw-r--r-- | mullvad-tests/src/mock_openvpn/mod.rs | 22 | ||||
| -rw-r--r-- | mullvad-tests/tests/account.rs | 83 | ||||
| -rw-r--r-- | mullvad-tests/tests/connection.rs | 53 |
5 files changed, 267 insertions, 102 deletions
diff --git a/mullvad-tests/src/bin/mock_openvpn.rs b/mullvad-tests/src/bin/mock_openvpn.rs index 2786dff3f9..cbc5b6caf6 100644 --- a/mullvad-tests/src/bin/mock_openvpn.rs +++ b/mullvad-tests/src/bin/mock_openvpn.rs @@ -1,24 +1,32 @@ -extern crate notify; +extern crate mullvad_tests; use std::env; -use std::fs::File; +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 (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), finished_tx); + wait_thread( + move || wait_for_file_to_be_deleted(watcher, MAX_EVENT_TIME), + finished_tx, + ); let _ = finished_rx.recv(); + let _ = fs::remove_file(path); } fn create_args_file() -> (File, PathBuf) { @@ -55,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 a9af2b9c10..534bf11003 100644 --- a/mullvad-tests/src/lib.rs +++ b/mullvad-tests/src/lib.rs @@ -12,6 +12,8 @@ extern crate os_pipe; extern crate talpid_ipc; extern crate tempfile; +pub mod mock_openvpn; + use std::collections::HashMap; use std::fs::{self, File}; use std::io::{BufRead, BufReader}; @@ -21,15 +23,18 @@ 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; use tempfile::TempDir; +use self::mock_openvpn::MOCK_OPENVPN_ARGS_FILE; use self::platform_specific::*; -pub const MOCK_OPENVPN_ARGS_FILE: &str = "mock_openvpn_args"; +pub use self::notify::op::{self as watch_event, Op as WatchEvent}; + +type Result<T> = ::std::result::Result<T, String>; #[cfg(unix)] mod platform_specific { @@ -50,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, +} - 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); +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 (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); + 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); - watcher - .watch(absolute_parent_dir, RecursiveMode::NonRecursive) - .expect("Failed to listen for file system events on directory"); + 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() + ) + })?; - if !file_path.exists() { - while let Some(wait_time) = remaining_time { - let event = rx.recv_timeout(wait_time); + 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, + }) + } + + pub fn set_timeout(&mut self, timeout: Duration) -> &mut Self { + self.timeout = timeout; + self + } - if let Ok(RawEvent { - path: Some(path), - op: Ok(op), - .. - }) = event - { - if op.contains(op::CLOSE_WRITE) && path == absolute_file_path { + 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)); + + 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 } } @@ -117,21 +175,25 @@ fn prepare_relay_list<T: AsRef<Path>>(path: T) { path, r#"{ "countries": [{ - "name": "Mockland", - "code": "fake", - "latitude": -91, - "longitude": 0, - "relays": [{ - "hostname": "fake-mockland", - "ipv4_addr_in": "192.168.0.100", - "ipv4_addr_exit": "192.168.0.101", - "include_in_country": true, - "weight": 100, - "tunnels": { - "openvpn": [ { "port": 10000, "protocol": "udp" } ], - "wireguard": [], - }, - }], + "name": "Sweden", + "code": "se", + "cities": [{ + "name": "Gothenburg", + "code": "got", + "latitude": 57.70887, + "longitude": 11.97456, + "relays": [{ + "hostname": "fakehost", + "ipv4_addr_in": "192.168.0.100", + "ipv4_addr_exit": "192.168.0.101", + "include_in_country": true, + "weight": 100, + "tunnels": { + "openvpn": [ { "port": 1000, "protocol": "udp" } ], + "wireguard": [] + } + }] + }] }] }"#, ).expect("Failed to create mock relay list file"); @@ -223,9 +285,13 @@ impl DaemonRunner { } } - pub fn rpc_client(&mut self) -> Result<DaemonRpcClient, String> { + 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) @@ -283,7 +349,7 @@ pub struct MockOpenVpnPluginRpcClient { } impl MockOpenVpnPluginRpcClient { - pub fn new(address: String, credentials: String) -> Result<Self, String> { + pub fn new(address: String, credentials: String) -> Result<Self> { let rpc = WsIpcClient::connect(&address).map_err(|error| { format!("Failed to create Mock OpenVPN plugin RPC client: {}", error) })?; @@ -291,29 +357,29 @@ impl MockOpenVpnPluginRpcClient { Ok(MockOpenVpnPluginRpcClient { rpc, credentials }) } - pub fn authenticate(&mut self) -> Result<bool, String> { + pub fn authenticate(&mut self) -> Result<bool> { self.rpc .call("authenticate", &[&self.credentials]) .map_err(|error| format!("Failed to authenticate mock OpenVPN IPC client: {}", error)) } - pub fn authenticate_with(&mut self, credentials: &str) -> Result<bool, String> { + pub fn authenticate_with(&mut self, credentials: &str) -> Result<bool> { self.rpc .call("authenticate", &[credentials]) .map_err(|error| format!("Failed to authenticate mock OpenVPN IPC client: {}", error)) } - pub fn up(&mut self) -> Result<(), String> { + pub fn up(&mut self) -> Result<()> { let mut env: HashMap<String, String> = HashMap::new(); - env.insert("dev".to_owned(), "dummy".to_owned()); + env.insert("dev".to_owned(), "lo".to_owned()); env.insert("ifconfig_local".to_owned(), "10.0.0.10".to_owned()); env.insert("route_vpn_gateway".to_owned(), "10.0.0.1".to_owned()); self.send_event(OpenVpnPluginEvent::Up, env) } - pub fn route_predown(&mut self) -> Result<(), String> { + pub fn route_predown(&mut self) -> Result<()> { self.send_event(OpenVpnPluginEvent::RoutePredown, HashMap::new()) } @@ -321,7 +387,7 @@ impl MockOpenVpnPluginRpcClient { &mut self, event: OpenVpnPluginEvent, env: HashMap<String, String>, - ) -> Result<(), String> { + ) -> Result<()> { self.rpc .call("openvpn_event", &(event, env)) .map_err(|error| format!("Failed to send mock OpenVPN event {:?}: {}", event, error)) diff --git a/mullvad-tests/src/mock_openvpn/mod.rs b/mullvad-tests/src/mock_openvpn/mod.rs new file mode 100644 index 0000000000..56e2514970 --- /dev/null +++ b/mullvad-tests/src/mock_openvpn/mod.rs @@ -0,0 +1,22 @@ +pub const MOCK_OPENVPN_ARGS_FILE: &str = "mock_openvpn_args"; + +use std::fs::File; +use std::io::{self, BufRead, BufReader}; +use std::path::Path; + +pub fn search_openvpn_args<P: AsRef<Path>>( + openvpn_args_file_path: P, + search_item: &'static str, +) -> impl Iterator<Item = io::Result<String>> { + let args_file_path = openvpn_args_file_path.as_ref(); + let args_file = File::open(&args_file_path).expect(&format!( + "Failed to open mock OpenVPN arguments file: {}", + args_file_path.display(), + )); + + let args = BufReader::new(args_file).lines(); + + args.skip_while(move |element| { + element.is_ok() && !element.as_ref().unwrap().contains(search_item) + }) +} diff --git a/mullvad-tests/tests/account.rs b/mullvad-tests/tests/account.rs new file mode 100644 index 0000000000..4d7c2bdcea --- /dev/null +++ b/mullvad-tests/tests/account.rs @@ -0,0 +1,83 @@ +#![cfg(all(target_os = "linux", feature = "integration-tests"))] + +extern crate mullvad_tests; + +use std::fs::File; +use std::io::{BufRead, BufReader}; +use std::path::Path; + +use mullvad_tests::mock_openvpn::search_openvpn_args; +use mullvad_tests::{watch_event, DaemonRunner, PathWatcher}; + +#[test] +fn uses_account_token() { + 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 specified_account = "123456"; + rpc_client + .set_account(Some(specified_account.to_owned())) + .unwrap(); + rpc_client.connect().unwrap(); + + openvpn_args_file_events.assert_create_write_close_sequence(); + + let account_token_sent_to_plugin = read_account_token(openvpn_args_file).unwrap(); + + assert_eq!(account_token_sent_to_plugin, specified_account); +} + +#[test] +fn uses_updated_account_token() { + 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 first_account_specified = "123456"; + rpc_client + .set_account(Some(first_account_specified.to_owned())) + .unwrap(); + rpc_client.connect().unwrap(); + + openvpn_args_file_events.assert_create_write_close_sequence(); + + let second_account_specified = "654321"; + rpc_client + .set_account(Some(second_account_specified.to_owned())) + .unwrap(); + + assert_eq!(openvpn_args_file_events.next(), Some(watch_event::REMOVE)); + openvpn_args_file_events.assert_create_write_close_sequence(); + + let account_token_sent_to_plugin = read_account_token(openvpn_args_file).unwrap(); + + assert_eq!(account_token_sent_to_plugin, second_account_specified); +} + +fn read_account_token<P: AsRef<Path>>(openvpn_args_file_path: P) -> Result<String, String> { + let account_token_file_path = search_openvpn_args(openvpn_args_file_path, "--auth-user-pass") + .skip(1) + .next() + .ok_or_else(|| "Missing account token file parameter to Talpid OpenVPN plugin".to_owned())? + .map_err(|error| { + format!( + "Failed to read from mock OpenVPN command line file: {}", + error + ) + })?; + + let account_token_file = File::open(account_token_file_path) + .map_err(|error| format!("Failed to open account token file: {}", error))?; + + let mut reader = BufReader::new(account_token_file); + let mut account = String::new(); + + reader + .read_line(&mut account) + .map_err(|error| format!("Failed to read from account token file: {}", error))?; + + Ok(account.trim().to_owned()) +} diff --git a/mullvad-tests/tests/connection.rs b/mullvad-tests/tests/connection.rs index c0d512056f..77f08fab14 100644 --- a/mullvad-tests/tests/connection.rs +++ b/mullvad-tests/tests/connection.rs @@ -4,13 +4,13 @@ extern crate mullvad_ipc_client; extern crate mullvad_tests; extern crate mullvad_types; -use std::fs::{self, File}; -use std::io::{BufRead, BufReader}; +use std::fs; use std::path::Path; use std::sync::mpsc; use std::time::Duration; -use mullvad_tests::{wait_for_file_write_finish, DaemonRunner, MockOpenVpnPluginRpcClient}; +use mullvad_tests::mock_openvpn::search_openvpn_args; +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); @@ -240,7 +244,7 @@ fn disconnects() { fn assert_state_event(receiver: &mpsc::Receiver<DaemonState>, expected_state: DaemonState) { let received_state = receiver - .recv_timeout(Duration::from_secs(1)) + .recv_timeout(Duration::from_secs(3)) .expect("Failed to receive new state event from daemon"); assert_eq!(received_state, expected_state); @@ -256,28 +260,21 @@ fn assert_no_state_event(receiver: &mpsc::Receiver<DaemonState>) { fn create_mock_openvpn_plugin_client<P: AsRef<Path>>( openvpn_args_file_path: P, ) -> MockOpenVpnPluginRpcClient { - let (address, credentials) = get_plugin_arguments(openvpn_args_file_path); + let args_file_path = openvpn_args_file_path.as_ref(); + + 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 (address, credentials) = get_plugin_arguments(&args_file_path); MockOpenVpnPluginRpcClient::new(address, credentials) .expect("Failed to create mock RPC client to connect to OpenVPN plugin event listener") } 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)); - - let args_file = File::open(&args_file_path).expect(&format!( - "Failed to open mock OpenVPN command-line file: {}", - args_file_path.display(), - )); - - let args_reader = BufReader::new(args_file).lines(); - let mut arguments = args_reader - .skip_while(|element| { - element.is_ok() && !element.as_ref().unwrap().contains(OPENVPN_PLUGIN_NAME) - }) - .skip(1); + let mut arguments = search_openvpn_args(openvpn_args_file_path, OPENVPN_PLUGIN_NAME).skip(1); let address = arguments .next() |
