summaryrefslogtreecommitdiffhomepage
path: root/test/test-manager/src/package.rs
blob: 3f35163e5e7d18f637cb4c6fc035b3316905ade0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use crate::config::{Architecture, OsType, PackageType, VmConfig};
use anyhow::{Context, Result};
use once_cell::sync::Lazy;
use regex::Regex;
use std::path::{Path, PathBuf};
use tokio::fs;

static VERSION_REGEX: Lazy<Regex> =
    Lazy::new(|| Regex::new(r"\d{4}\.\d+(-beta\d+)?(-dev)?-([0-9a-z])+").unwrap());

#[derive(Debug, Clone)]
pub struct Manifest {
    pub current_app_path: PathBuf,
    pub previous_app_path: PathBuf,
    pub ui_e2e_tests_path: PathBuf,
}

/// Obtain app packages and their filenames
/// If it's a path, use the path.
/// If it corresponds to a file in packages/, use that package.
/// TODO: If it's a git tag or rev, download it.
pub async fn get_app_manifest(
    config: &VmConfig,
    current_app: String,
    previous_app: String,
) -> Result<Manifest> {
    let package_type = (config.os_type, config.package_type, config.architecture);

    let current_app_path = find_app(&current_app, false, package_type).await?;
    log::info!("Current app: {}", current_app_path.display());

    let previous_app_path = find_app(&previous_app, false, package_type).await?;
    log::info!("Previous app: {}", previous_app_path.display());

    let capture = VERSION_REGEX
        .captures(current_app_path.to_str().unwrap())
        .with_context(|| format!("Cannot parse version: {}", current_app_path.display()))?
        .get(0)
        .map(|c| c.as_str())
        .expect("Could not parse version from package name: {current_app}");

    let ui_e2e_tests_path = find_app(capture, true, package_type).await?;
    log::info!("Runner executable: {}", ui_e2e_tests_path.display());

    Ok(Manifest {
        current_app_path,
        previous_app_path,
        ui_e2e_tests_path,
    })
}

async fn find_app(
    app: &str,
    e2e_bin: bool,
    package_type: (OsType, Option<PackageType>, Option<Architecture>),
) -> Result<PathBuf> {
    // If it's a path, use that path
    let app_path = Path::new(app);
    if app_path.is_file() {
        // TODO: Copy to packages?
        return Ok(app_path.to_path_buf());
    }

    let mut app = app.to_owned();
    app.make_ascii_lowercase();

    let packages_dir = dirs::cache_dir()
        .context("Could not find cache directory")?
        .join("mullvad-test")
        .join("packages");
    fs::create_dir_all(&packages_dir).await?;
    let mut dir = fs::read_dir(packages_dir)
        .await
        .context("Failed to list packages")?;

    let mut matches = vec![];

    while let Ok(Some(entry)) = dir.next_entry().await {
        let path = entry.path();
        if !path.is_file() {
            continue;
        }

        // Filter out irrelevant platforms
        if !e2e_bin {
            let ext = get_ext(package_type);

            // Skip file if wrong file extension
            if !path
                .extension()
                .map(|m_ext| m_ext.eq_ignore_ascii_case(ext))
                .unwrap_or(false)
            {
                continue;
            }
        }

        let mut u8_path = path.as_os_str().to_string_lossy().into_owned();
        u8_path.make_ascii_lowercase();

        // Skip non-UI-e2e binaries or vice versa
        if e2e_bin ^ u8_path.contains("app-e2e-tests") {
            continue;
        }

        // Filter out irrelevant platforms
        if e2e_bin && !u8_path.contains(get_os_name(package_type)) {
            continue;
        }

        // Skip file if it doesn't match the architecture
        if let Some(arch) = package_type.2 {
            // Skip for non-e2e bin on non-Linux, because there's only one package
            if (e2e_bin || package_type.0 == OsType::Linux)
                && !arch.get_identifiers().iter().any(|id| u8_path.contains(id))
            {
                continue;
            }
        }

        if u8_path.contains(&app) {
            matches.push(path);
        }
    }

    // TODO: Search for package in git repository if not found

    // Take the shortest match
    matches.sort_unstable_by_key(|path| path.as_os_str().len());
    matches.into_iter().next().context(if e2e_bin {
        format!(
            "Could not find UI/e2e test for package: {app}.\n\
         Expecting a binary named like `app-e2e-tests-{app}_ARCH` to exist in packages/\n\
         Example ARCH: `amd64-unknown-linux-gnu`, `x86_64-unknown-linux-gnu`"
        )
    } else {
        format!("Could not find package for app: {app}")
    })
}

fn get_ext(package_type: (OsType, Option<PackageType>, Option<Architecture>)) -> &'static str {
    match package_type.0 {
        OsType::Windows => "exe",
        OsType::Macos => "pkg",
        OsType::Linux => match package_type.1.expect("must specify package type") {
            PackageType::Deb => "deb",
            PackageType::Rpm => "rpm",
        },
    }
}

fn get_os_name(package_type: (OsType, Option<PackageType>, Option<Architecture>)) -> &'static str {
    match package_type.0 {
        OsType::Windows => "windows",
        OsType::Macos => "apple",
        OsType::Linux => "linux",
    }
}