summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-07-03 11:18:59 -0300
committerJanito Vaqueiro Ferreira Filho <janito@mullvad.net>2018-07-03 11:18:59 -0300
commitd9e32ed3b33a146dd33d2b92bb35cd20b9922453 (patch)
tree485e51a71b032f098c6e6ead50ef8a78496b071c
parent5d17110366adb7fa1e69c5b350e28523b0fb85d4 (diff)
parent9865af5d41906adadf3e081f785134424397db43 (diff)
downloadmullvadvpn-d9e32ed3b33a146dd33d2b92bb35cd20b9922453.tar.xz
mullvadvpn-d9e32ed3b33a146dd33d2b92bb35cd20b9922453.zip
Merge branch 'account-tests'
-rw-r--r--mullvad-tests/src/bin/mock_openvpn.rs37
-rw-r--r--mullvad-tests/src/lib.rs174
-rw-r--r--mullvad-tests/src/mock_openvpn/mod.rs22
-rw-r--r--mullvad-tests/tests/account.rs83
-rw-r--r--mullvad-tests/tests/connection.rs53
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()