Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/tools/update-verify/marannon/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 13 kB image not shown  

Quelle  test.rs

  Sprache: Rust
 

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */

use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::{Display, Error, Formatter};
use std::fs::{create_dir, File};
use std::io::Write;
use std::os::unix::fs::symlink;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::thread;

use anyhow::{anyhow, Result};
use log::{error, info};

use crate::runner::CommandRunner;
use crate::updater::{prepare_updater, CertOverride};

pub struct Test {
    /// A human readable identifier for the `from` build under test. Eg: a buildid
    /// or app version.
    pub id: String,
    /// The `from` installer to use as a starting point for the test
    pub from_installer: PathBuf,
    /// The locale of the `from` installer (needed to fully unpack it)
    pub locale: String,
    /// The MAR file to apply to the unpacked `from` installer
    pub mar: PathBuf,
    /// The package containing the updater to use when applying the MAR
    pub updater_package: PathBuf,
}

impl Display for Test {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "{}"self.full_id())?;
        return Ok(());
    }
}

impl Test {
    fn full_id(&self) -> String {
        let mar_filename = self
            .mar
            .file_name()
            .unwrap_or(OsStr::new("unknown_mar_name"))
            .to_str()
            .unwrap_or("unknown_mar_name");
        return format!("{}-{}-{}"self.id, self.locale, mar_filename);
    }
}

pub(cratestruct TestOutcome {
    pub(crate) label: String,
    pub(crate) result: TestResult,
    pub(crate) output: String,
}

#[derive(Debug, PartialEq)]
pub enum TestResult {
    Pass,
    /// Updater did not run succesfully.
    UpdateStatusErr,
    /// UpdateSettings.framework is missing in the updated build (mac only)
    UpdateSettingsMissingErr,
    /// ChannelPrefs.framework is missing in the updated build (mac only)
    ChannelPrefsMissingErr,
    /// Unacceptable differences found between the MAR and installer
    DifferencesFoundErr,
    /// Error running `diff`
    DiffErr,
    /// Novel error occurred - such things may benefit from distinct results
    /// or to be converted to SetupErr when encountered!
    UnknownErr,
    /// Setup problems are not results in the same way that specific failure
    /// modes are; we group them all together and allow for additional
    /// information to be included in them for this reason.
    SetupErr(String),
}

pub(cratefn run_tests(
    check_updates: &Path,
    target_platform: &str,
    to_installer: &Path,
    channel: &str,
    appname: &str,
    cert_dir: Option<&Path>,
    cert_overrides: &Vec<CertOverride>,
    tests: Vec<Test>,
    tmpdir: &Path,
    artifact_dir: &Path,
    runner: &(dyn CommandRunner + Sync),
    parallelism: usize,
) -> Result<Vec<TestResult>> {
    let mut updater_binaries: HashMap<PathBuf, PathBuf> = HashMap::new();

    // Prepare updater packages
    // Find distinct updater packages (so we don't process repeated ones
    // more than once).
    let mut distinct_updater_packages: Vec<PathBuf> =
        tests.iter().map(|t| t.updater_package.clone()).collect();
    distinct_updater_packages.sort();
    distinct_updater_packages.dedup();

    // Decide on locations for them.
    let updater_locations: HashMap<PathBuf, PathBuf> = distinct_updater_packages
        .iter()
        .map(|updater_package| -> Result<(PathBuf, PathBuf)> {
            let unpack_dir = tempfile::Builder::new()
                .prefix("updater_")
                .tempdir_in(tmpdir)?
                .keep();
            return Ok((updater_package.clone(), unpack_dir));
        })
        .collect::<Result<HashMap<_, _>>>()?;

    // Unpack into the desired locations.
    let updater_items: Vec<_> = updater_locations.iter().collect();
    let chunk_size = updater_items.len().div_ceil(parallelism).max(1);
    let updater_results: Vec<_> = thread::scope(|s| -> Result<Vec<_>> {
        let handles: Vec<_> = updater_items
            // Return `chunk_size` updater packages at a time
            .chunks(chunk_size)
            // For each chunk...
            .map(|chunk| {
                // Spawn a thread...
                s.spawn(move || {
                    chunk
                        .iter()
                        // For each updater package in the chunk...
                        .map(|(updater_package, unpack_dir)| {
                            // Unpack it...
                            let result = prepare_updater(
                                updater_package,
                                appname,
                                cert_dir,
                                cert_overrides,
                                unpack_dir,
                            );

                            // And return the package along with the
                            // Result (whose Ok value is the path to the
                            // containing updater binary on disk).
                            return (*updater_package, result);
                        })
                        .collect::<Vec<_>>()
                })
            })
            .collect();

        let mut results = Vec::with_capacity(updater_items.len());
        for h in handles {
            let chunk_result = h
                .join()
                // Handle errors that come up when joining the thread
                .map_err(|_| anyhow::anyhow!("prepare updater thread panicked"))?;
            results.extend(chunk_result);
        }
        Ok(results)
    })?;

    for (package, result) in updater_results {
        match result {
            Ok(updater) => {
                updater_binaries.insert(package.clone(), updater);
            }
            // Handle errors that came up when preparing an updater
            Err(e) => return Err(e),
        }
    }

    // Creating a named reference to `updater_binaries` allows it to be moved
    // into the thread scope multiple times (because references can be copied).
    // Without this, the thread scope tries to move the owned `updater_binaries`
    // which fails because HashMaps are not copyable.
    let updater_binaries_ref = &updater_binaries;
    let chunk_size = tests.len().div_ceil(parallelism).max(1);
    let outcomes: Vec<TestOutcome> = thread::scope(|s| -> Result<Vec<TestOutcome>> {
        let handles: Vec<_> = tests
            .chunks(chunk_size)
            .map(|chunk| {
                s.spawn(move || {
                    chunk
                        .iter()
                        .map(|test| {
                            let updater = updater_binaries_ref[&test.updater_package].clone();
                            match run_test(
                                test,
                                &updater,
                                check_updates,
                                target_platform,
                                to_installer,
                                channel,
                                appname,
                                tmpdir,
                                artifact_dir,
                                runner,
                            ) {
                                Ok(o) => o,
                                // Any errors coming from `run_test` constitute
                                // a test failure. Unlike other errors (eg: when
                                // unpacking updaters above), these do not
                                // constitute an overall error with the program
                                // that we need to exit for -- they are simply
                                // converted to a TestResult indicating the
                                // error.
                                Err(e) => TestOutcome {
                                    label: test.to_string(),
                                    result: TestResult::SetupErr(e.to_string()),
                                    output: String::new(),
                                },
                            }
                        })
                        .collect::<Vec<_>>()
                })
            })
            .collect();

        let mut outcomes = vec![];
        for h in handles {
            let chunk_result = h
                .join()
                .map_err(|_| anyhow::anyhow!("test thread panicked"))?;
            outcomes.extend(chunk_result);
        }
        Ok(outcomes)
    })?;

    let mut results = Vec::with_capacity(tests.len());
    for outcome in outcomes {
        if outcome.result == TestResult::Pass {
            info!("TEST-PASS: {}", outcome.label);
        } else {
            error!("{}", outcome.output);
            info!("TEST-UNEXPECTED-FAIL: {}", outcome.label);
        }
        results.push(outcome.result);
    }
    return Ok(results);
}

fn run_test(
    test: &Test,
    updater: &Path,
    check_updates: &Path,
    target_platform: &str,
    to_installer: &Path,
    channel: &str,
    appname: &str,
    tmpdir: &Path,
    artifact_dir: &Path,
    runner: &dyn CommandRunner,
) -> Result<TestOutcome> {
    info!("TEST-START: {}", test.full_id());

    let test_dir = setup_test_dir(&test.mar, tmpdir)?;
    let mut diff_file = artifact_dir.to_path_buf();
    diff_file.push(format!("{}.summary.log", test.full_id()));

    let mut cmd = Command::new("/bin/bash");
    cmd.arg(check_updates)
        .arg(target_platform)
        .arg(&test.from_installer)
        .arg(to_installer)
        .arg(&test.locale)
        .arg(updater)
        .arg(diff_file.to_str().unwrap())
        .arg(channel)
        // check_updates.sh requires positional args that we don't use
        .arg("")
        .arg("")
        // mac-only option that tells `check_updates.sh` where to find
        // a usable `update-settings.ini` file that the updater requires
        // this file is always located in the same dir as the `updater`
        // binary. `check_updates.sh` ignores this option on other platforms.
        .arg(
            updater
                .parent()
                .ok_or_else(|| anyhow!("Couldn't determine update-settings.ini dir!"))?,
        )
        .arg(appname)
        .current_dir(test_dir);
    let command_result = runner.run(cmd)?;
    let result = match command_result.exit_code {
        0 => TestResult::Pass,
        1 => TestResult::SetupErr("Failed to unpack from or to build".to_string()),
        2 => TestResult::SetupErr("Failed to cd into from build application directory".to_string()),
        3 => TestResult::SetupErr("from build application directory does not exist".to_string()),
        4 => TestResult::UpdateStatusErr,
        5 => TestResult::UpdateSettingsMissingErr,
        6 => TestResult::ChannelPrefsMissingErr,
        7 => TestResult::DifferencesFoundErr,
        8 => TestResult::DiffErr,
        _ => TestResult::UnknownErr,
    };

    // save the output to an artifact
    let mut output_file = artifact_dir.to_path_buf();
    output_file.push(format!("{}.output.log", test.full_id()));
    let mut f = File::create(output_file)?;
    f.write_all(command_result.output.as_bytes())?;

    return Ok(TestOutcome {
        label: test.full_id(),
        result: result,
        output: command_result.output,
    });
}

/// Setup the test directory in a way that is compatible with the expectations
/// of `check_updates.sh`: create a fresh directory with an `update` directory
/// inside of it, containing the MAR to be applied in `update.mar`.
/// When we get rid of `check_updates.sh` we can consider changing or getting
/// rid of this.
fn setup_test_dir(mar: &Path, tmpdir: &Path) -> Result<PathBuf> {
    let test_dir = tempfile::Builder::new()
        .prefix("work_")
        .tempdir_in(tmpdir)?
        .keep();
    let mut update_dir = test_dir.clone();
    update_dir.push("update");
    create_dir(update_dir.as_path())?;
    let mut mar_path = update_dir.clone();
    mar_path.push("update.mar");
    symlink(mar, mar_path.as_path())?;
    return Ok(test_dir);
}

#[cfg(test)]
mod tests {
    use crate::runner::CommandResult;

    use super::*;
    use tempfile::TempDir;

    struct FakeRunner(i32);
    impl CommandRunner for FakeRunner {
        fn run(&self, _: Command) -> Result<CommandResult> {
            Ok(CommandResult {
                exit_code: self.0,
                output: String::new(),
            })
        }
    }

    #[test]
    fn setup_test_dir_creates_expected_layout() {
        let tmpdir = TempDir::with_prefix("marannon_setup_test").unwrap();
        let tmp = tmpdir.path().to_path_buf();

        let mar = tmp.join("test.mar");
        std::fs::write(&mar, b"fake").unwrap();

        let test_dir = setup_test_dir(&mar, &tmp).unwrap();

        assert!(test_dir.exists());
        assert!(test_dir.join("update").exists());
        assert!(test_dir.join("update").join("update.mar").exists());
    }

    #[test]
    fn run_tests_setup_err_on_bad_installer() {
        let tmpdir = TempDir::with_prefix("marannon_run_tests").unwrap();
        let tmp = tmpdir.path();
        let artifact_dir = TempDir::with_prefix("marannon_artifacts").unwrap();
        let artifacts = artifact_dir.path();

        let test = Test {
            id: "from".to_string(),
            mar: tmp.join("test.mar"),
            from_installer: PathBuf::from("/nonexistent/installer.tar.xz"),
            updater_package: PathBuf::from("/nonexistent/installer.tar.xz"),
            locale: "en-US".to_string(),
        };

        let result = run_tests(
            &PathBuf::from("/fake/check_updates.sh"),
            "linux",
            &PathBuf::from("/fake/to_installer"),
            "release",
            "firefox",
            None,
            &vec![],
            vec![test],
            &tmp.to_path_buf(),
            &artifacts.to_path_buf(),
            &FakeRunner(0),
            1,
        );
        assert!(result.is_err());
        let e = result.unwrap_err();
        assert!(e.to_string().contains("No such file or directory"));
    }
}

Messung V0.5 in Prozent
C=89 H=98 G=93

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.