Skip to content

Commit 5cb1a97

Browse files
committed
Merge branch 'test-manager-fixes'
2 parents 4c137b4 + 4ef27f8 commit 5cb1a97

17 files changed

+561
-490
lines changed

test/scripts/ssh-setup.sh

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ echo "Copying test-runner to $RUNNER_DIR"
1717
mkdir -p "$RUNNER_DIR"
1818

1919
for file in test-runner connection-checker $APP_PACKAGE $PREVIOUS_APP $UI_RUNNER; do
20-
echo "Moving $file to $RUNNER_DIR"
20+
echo "Moving $SCRIPT_DIR/$file to $RUNNER_DIR"
2121
cp -f "$SCRIPT_DIR/$file" "$RUNNER_DIR"
2222
done
2323

@@ -106,6 +106,7 @@ fi
106106
setup_systemd
107107

108108
function install_packages_apt {
109+
echo "Installing required apt packages"
109110
apt update
110111
apt install -yf xvfb wireguard-tools curl
111112
curl -fsSL https://get.docker.com | sh

test/scripts/test-utils.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function get_package_dir {
5050
local package_dir
5151
if [[ -n "${PACKAGE_DIR+x}" ]]; then
5252
# Resolve the package dir to an absolute path since cargo must be invoked from the test directory
53-
package_dir=$(cd "$PACKAGE_DIR" > /dev/null && pwd)
53+
package_dir=$(realpath "$PACKAGE_DIR")
5454
elif [[ ("$(uname -s)" == "Darwin") ]]; then
5555
package_dir="$HOME/Library/Caches/mullvad-test/packages"
5656
elif [[ ("$(uname -s)" == "Linux") ]]; then

test/test-manager/src/logging.rs

+78-17
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ use colored::Colorize;
33
use std::sync::{Arc, Mutex};
44
use test_rpc::logging::{LogOutput, Output};
55

6+
use crate::summary;
7+
68
/// Logger that optionally supports logging records to a buffer
79
#[derive(Clone)]
810
pub struct Logger {
@@ -109,25 +111,94 @@ impl log::Log for Logger {
109111
fn flush(&self) {}
110112
}
111113

114+
/// Encapsulate caught unwound panics, such that we can catch tests that panic and differentiate
115+
/// them from tests that just fail.
112116
#[derive(Debug, thiserror::Error)]
113-
#[error("Test panic: {0}")]
114-
pub struct PanicMessage(String);
117+
#[error("Test panic: {}", self.as_string())]
118+
pub struct Panic(Box<dyn std::any::Any + Send + 'static>);
119+
120+
impl Panic {
121+
/// Create a new [`Panic`] from a caught unwound panic.
122+
pub fn new(result: Box<dyn std::any::Any + Send + 'static>) -> Self {
123+
Self(result)
124+
}
125+
126+
/// Convert this panic to a [`String`] representation.
127+
pub fn as_string(&self) -> String {
128+
if let Some(result) = self.0.downcast_ref::<String>() {
129+
return result.clone();
130+
}
131+
match self.0.downcast_ref::<&str>() {
132+
Some(s) => String::from(*s),
133+
None => String::from("unknown message"),
134+
}
135+
}
136+
}
115137

116138
pub struct TestOutput {
117139
pub error_messages: Vec<Output>,
118140
pub test_name: &'static str,
119-
pub result: Result<Result<(), Error>, PanicMessage>,
141+
pub result: TestResult,
120142
pub log_output: Option<LogOutput>,
121143
}
122144

145+
// Convert this unwieldy return type to a workable `TestResult`.
146+
// What we are converting from is the acutal return type of the test execution.
147+
impl From<Result<Result<(), Error>, Panic>> for TestResult {
148+
fn from(value: Result<Result<(), Error>, Panic>) -> Self {
149+
match value {
150+
Ok(Ok(())) => TestResult::Pass,
151+
Ok(Err(e)) => TestResult::Fail(e),
152+
Err(e) => TestResult::Panic(e),
153+
}
154+
}
155+
}
156+
157+
/// Result from a test execution. This may carry information in case the test failed during
158+
/// execution.
159+
pub enum TestResult {
160+
/// Test passed.
161+
Pass,
162+
/// Test failed during execution. Contains the source error which caused the test to fail.
163+
Fail(Error),
164+
/// Test panicked during execution. Contains the caught unwound panic.
165+
Panic(Panic),
166+
}
167+
168+
impl TestResult {
169+
/// Returns `true` if test failed or panicked, i.e. when `TestResult` is `Fail` or `Panic`.
170+
pub const fn failure(&self) -> bool {
171+
matches!(self, TestResult::Fail(_) | TestResult::Panic(_))
172+
}
173+
174+
/// Convert `self` to a [`summary::TestResult`], which is used for creating fancy exports of
175+
/// the results for a test run.
176+
pub const fn summary(&self) -> summary::TestResult {
177+
match self {
178+
TestResult::Pass => summary::TestResult::Pass,
179+
TestResult::Fail(_) | TestResult::Panic(_) => summary::TestResult::Fail,
180+
}
181+
}
182+
183+
/// Consume `self` and convert into a [`Result`] where [`TestResult::Pass`] is mapped to [`Ok`]
184+
/// while [`TestResult::Fail`] & [`TestResult::Panic`] is mapped to [`Err`].
185+
pub fn anyhow(self) -> anyhow::Result<()> {
186+
match self {
187+
TestResult::Pass => Ok(()),
188+
TestResult::Fail(error) => anyhow::bail!(error),
189+
TestResult::Panic(error) => anyhow::bail!(error.to_string()),
190+
}
191+
}
192+
}
193+
123194
impl TestOutput {
124195
pub fn print(&self) {
125196
match &self.result {
126-
Ok(Ok(_)) => {
197+
TestResult::Pass => {
127198
println!("{}", format!("TEST {} SUCCEEDED!", self.test_name).green());
128199
return;
129200
}
130-
Ok(Err(e)) => {
201+
TestResult::Fail(e) => {
131202
println!(
132203
"{}",
133204
format!(
@@ -138,13 +209,13 @@ impl TestOutput {
138209
.red()
139210
);
140211
}
141-
Err(panic_msg) => {
212+
TestResult::Panic(panic_msg) => {
142213
println!(
143214
"{}",
144215
format!(
145216
"TEST {} PANICKED WITH MESSAGE: {}",
146217
self.test_name,
147-
panic_msg.0.bold()
218+
panic_msg.as_string().bold()
148219
)
149220
.red()
150221
);
@@ -191,13 +262,3 @@ impl TestOutput {
191262
println!("{}", format!("TEST {} END OF OUTPUT", self.test_name).red());
192263
}
193264
}
194-
195-
pub fn panic_as_string(error: Box<dyn std::any::Any + Send + 'static>) -> PanicMessage {
196-
if let Some(result) = error.downcast_ref::<String>() {
197-
return PanicMessage(result.clone());
198-
}
199-
match error.downcast_ref::<&str>() {
200-
Some(s) => PanicMessage(String::from(*s)),
201-
None => PanicMessage(String::from("unknown message")),
202-
}
203-
}

test/test-manager/src/main.rs

+33-33
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use std::{net::SocketAddr, path::PathBuf};
1313

1414
use anyhow::{Context, Result};
1515
use clap::Parser;
16+
use tests::{config::TEST_CONFIG, get_filtered_tests};
1617
use vm::provision;
1718

1819
use crate::tests::config::OpenVPNCertificate;
@@ -92,8 +93,9 @@ enum Commands {
9293
#[arg(long)]
9394
app_package: String,
9495

95-
/// App package to upgrade from when running `test_install_previous_app`, can be left empty
96-
/// if this test is not ran. Parsed the same way as `--app-package`.
96+
/// Given this argument, the `test_upgrade_app` test will run, which installs the previous
97+
/// version then upgrades to the version specified in by `--app-package`. If left empty,
98+
/// the test will be skipped. Parsed the same way as `--app-package`.
9799
///
98100
/// # Note
99101
///
@@ -117,7 +119,8 @@ enum Commands {
117119
#[arg(long)]
118120
openvpn_certificate: Option<PathBuf>,
119121

120-
/// Only run tests matching substrings
122+
/// Names of tests to run. The order given will be respected. If not set, all tests will be
123+
/// run.
121124
test_filters: Vec<String>,
122125

123126
/// Print results live
@@ -293,6 +296,28 @@ async fn main() -> Result<()> {
293296
.await
294297
.context("Failed to run provisioning for VM")?;
295298

299+
TEST_CONFIG.init(tests::config::TestConfig::new(
300+
account,
301+
artifacts_dir,
302+
manifest
303+
.app_package_path
304+
.file_name()
305+
.unwrap()
306+
.to_string_lossy()
307+
.into_owned(),
308+
manifest
309+
.app_package_to_upgrade_from_path
310+
.map(|path| path.file_name().unwrap().to_string_lossy().into_owned()),
311+
manifest
312+
.gui_package_path
313+
.map(|path| path.file_name().unwrap().to_string_lossy().into_owned()),
314+
mullvad_host,
315+
vm::network::bridge()?,
316+
test_rpc::meta::Os::from(vm_config.os_type),
317+
openvpn_certificate,
318+
));
319+
let tests = get_filtered_tests(&test_filters)?;
320+
296321
// For convenience, spawn a SOCKS5 server that is reachable for tests that need it
297322
let socks = socks_server::spawn(SocketAddr::new(
298323
crate::vm::network::NON_TUN_GATEWAY.into(),
@@ -302,41 +327,16 @@ async fn main() -> Result<()> {
302327

303328
let skip_wait = vm_config.provisioner != config::Provisioner::Noop;
304329

305-
let result = run_tests::run(
306-
tests::config::TestConfig::new(
307-
account,
308-
artifacts_dir,
309-
manifest
310-
.app_package_path
311-
.file_name()
312-
.unwrap()
313-
.to_string_lossy()
314-
.into_owned(),
315-
manifest
316-
.app_package_to_upgrade_from_path
317-
.map(|path| path.file_name().unwrap().to_string_lossy().into_owned()),
318-
manifest
319-
.gui_package_path
320-
.map(|path| path.file_name().unwrap().to_string_lossy().into_owned()),
321-
mullvad_host,
322-
vm::network::bridge()?,
323-
test_rpc::meta::Os::from(vm_config.os_type),
324-
openvpn_certificate,
325-
),
326-
&*instance,
327-
&test_filters,
328-
skip_wait,
329-
!verbose,
330-
summary_logger,
331-
)
332-
.await
333-
.context("Tests failed");
330+
let result = run_tests::run(&*instance, tests, skip_wait, !verbose, summary_logger)
331+
.await
332+
.context("Tests failed");
334333

335334
if display {
336335
instance.wait().await;
337336
}
338337
socks.close();
339-
result
338+
// Propagate any error from the test run if applicable
339+
result?.anyhow()
340340
}
341341
Commands::FormatTestReports { reports } => {
342342
summary::print_summary_table(&reports).await;

test/test-manager/src/network_monitor.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl Codec {
9898
payload = seg.payload().to_vec();
9999
}
100100
IpHeaderProtocols::Icmp => {}
101-
proto => log::debug!("ignoring v4 packet, transport/protocol type {proto}"),
101+
proto => log::warn!("ignoring v4 packet, transport/protocol type {proto}"),
102102
}
103103

104104
Some(ParsedPacket {
@@ -140,7 +140,7 @@ impl Codec {
140140
payload = seg.payload().to_vec();
141141
}
142142
IpHeaderProtocols::Icmpv6 => {}
143-
proto => log::debug!("ignoring v6 packet, transport/protocol type {proto}"),
143+
proto => log::warn!("ignoring v6 packet, transport/protocol type {proto}"),
144144
}
145145

146146
Some(ParsedPacket {

test/test-manager/src/package.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ pub fn get_app_manifest(
6969
})
7070
}
7171

72-
fn get_version_from_path(app_package_path: &Path) -> Result<String, anyhow::Error> {
72+
pub fn get_version_from_path(app_package_path: &Path) -> Result<String, anyhow::Error> {
7373
static VERSION_REGEX: Lazy<Regex> =
7474
Lazy::new(|| Regex::new(r"\d{4}\.\d+((-beta\d+)?(-dev)?-([0-9a-z])+)?").unwrap());
7575

0 commit comments

Comments
 (0)