blob: dde779a41bbcaac83d1dc4f179cf7c9caafca959 [file] [log] [blame] [edit]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use std::fs::{create_dir_all, File};
use std::io::{copy, Error};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::process::CommandExt;
use std::path::Path;
use std::process::Command;
use std::time::Instant;
use std::{env, fs};
use tempfile::TempDir;
use zip::ZipArchive;
const PYTHON_RUNTIME_NAME: &str = "python3";
const LACEWING_TEST_NAME: &str = "test.pyz";
fn unpack_artifacts(current_exe: &Path, dir: &TempDir) -> Result<(), Error> {
println!("[HermeticWrapper] Extracting Lacewing artifacts");
let t_before_unpack = Instant::now();
// The current executable file is assumed to have a ZIP file appended to it.
let current_exe_file = File::open(current_exe)?;
// Extract archive contents.
let mut zip = ZipArchive::new(&current_exe_file).expect("Unable to read archive.");
for i in 0..zip.len() {
let mut file = zip.by_index(i).expect("Unable to get file by index from archive.");
let outpath = file.sanitized_name();
let dest = dir.path().join(outpath);
// Ensure the paths we extract do not escape the temp dir.
// sanitized_name() already removes `..`, but we want to explicitly ensure that this behavior does not regress.
assert_eq!(dest.starts_with(dir), true);
if file.is_dir() {
create_dir_all(&dest)?;
} else {
let mut outfile = File::create(&dest).expect("Unable to extract file.");
copy(&mut file, &mut outfile)?;
}
}
fs::set_permissions(dir.path().join(PYTHON_RUNTIME_NAME), fs::Permissions::from_mode(0o555))?;
// TODO(https://fxbug.dev/346856480): Report metrics to performance backend.
let unpack_duration = t_before_unpack.elapsed();
println!("[HermeticWrapper] Time spent preparing environment: {:?}", unpack_duration);
Ok(())
}
fn main() -> Result<(), Error> {
// Create temp dir to unpack data resources
let tempdir = TempDir::new().expect("failed to create tmp dir");
let current_exe = env::current_exe().expect("failed to obtain current executable path");
unpack_artifacts(&current_exe, &tempdir)?;
// Execute the Lacewing test in an external process.
let mut command = Command::new(tempdir.path().join(PYTHON_RUNTIME_NAME));
command
.current_dir(tempdir.path()) // Fuchsia Controller import hooks require its shared libs to be present in CWD.
.arg(tempdir.path().join(LACEWING_TEST_NAME))
.env("PYTHONPATH", tempdir.path()) // Ensure ambient Python libraries are not used.
.env("PYTHONUNBUFFERED", "1"); // Set line-buffering for Mobly tests to flush output immediately.
for test_arg in env::args().skip(1) {
// Plumb Mobly test args from the caller through (e.g. ["-c", "config.yaml", "-v"])
command.arg(test_arg);
}
// [`CommandExt::exec`] doesn't return if successful, the current process is
// replaced, so this just always returns an error if anything at all.
println!("[HermeticWrapper] Executing Python: {:?}", command);
Err(command.exec())
}