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 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
|
use anyhow::{bail, Result};
use fs_err as fs;
use maturin::Target;
use normpath::PathExt as _;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use std::{env, io, str};
pub mod develop;
pub mod errors;
pub mod integration;
pub mod metadata;
pub mod other;
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum TestInstallBackend {
Pip,
Uv,
}
/// Check that the package is either not installed or works correctly
pub fn check_installed(package: &Path, python: &Path) -> Result<()> {
let path = if cfg!(windows) {
// on Windows, also add Scripts to PATH
let python_dir = python.parent().unwrap();
env::join_paths([&python_dir.join("Scripts"), python_dir])?.into()
} else {
python.parent().unwrap().to_path_buf()
};
let mut check_installed = Path::new(package)
.join("check_installed")
.join("check_installed.py");
if !check_installed.is_file() {
check_installed = Path::new(package)
.parent()
.unwrap()
.join("check_installed")
.join("check_installed.py");
}
let output = Command::new(python)
.arg(check_installed)
.env("PATH", path)
.output()
.unwrap();
if !output.status.success() {
bail!(
"Check install fail: {} \n--- Stdout:\n{}\n--- Stderr:\n{}",
output.status,
str::from_utf8(&output.stdout)?,
str::from_utf8(&output.stderr)?
);
}
let message = str::from_utf8(&output.stdout).unwrap().trim();
if message != "SUCCESS" {
panic!("Not SUCCESS: {message}");
}
Ok(())
}
/// Replaces the real cargo with cargo-mock if the mock crate has been compiled
///
/// If the mock crate hasn't been compile this does nothing
pub fn maybe_mock_cargo() {
// libtest spawns multiple threads to run the tests in parallel, but all of those threads share
// the same environment variables, so this uses the also global stdout lock to
// make this region exclusive
let stdout = io::stdout();
let handle = stdout.lock();
let mock_cargo_path = PathBuf::from("test-crates/cargo-mock/target/release/");
if mock_cargo_path.join("cargo").is_file() || mock_cargo_path.join("cargo.exe").is_file() {
let old_path = env::var_os("PATH").expect("PATH must be set");
let mut path_split: Vec<PathBuf> = env::split_paths(&old_path).collect();
// Another thread might have already modified the path
if mock_cargo_path != path_split[0] {
path_split.insert(0, mock_cargo_path);
let new_path =
env::join_paths(path_split).expect("Expected to be able to re-join PATH");
env::set_var("PATH", new_path);
}
}
drop(handle);
}
/// Better error formatting
#[track_caller]
pub fn handle_result<T>(result: Result<T>) -> T {
match result {
Err(e) => {
for cause in e.chain().rev() {
eprintln!("Cause: {cause}");
}
panic!("{}", e);
}
Ok(result) => result,
}
}
/// Get Python implementation
pub fn get_python_implementation(python_interp: &Path) -> Result<String> {
let code = "import sys; print(sys.implementation.name, end='')";
let output = Command::new(python_interp).arg("-c").arg(code).output()?;
let python_impl = String::from_utf8(output.stdout).unwrap();
Ok(python_impl)
}
/// Get the current tested Python implementation
pub fn test_python_implementation() -> Result<String> {
let python = test_python_path().map(PathBuf::from).unwrap_or_else(|| {
let target = Target::from_target_triple(None).unwrap();
target.get_python()
});
get_python_implementation(&python)
}
/// Create virtualenv
pub fn create_virtualenv(name: &str, python_interp: Option<PathBuf>) -> Result<(PathBuf, PathBuf)> {
let interp = python_interp.or_else(|| test_python_path().map(PathBuf::from));
let venv_interp = interp.clone().unwrap_or_else(|| {
let target = Target::from_target_triple(None).unwrap();
target.get_python()
});
let venv_name = match get_python_implementation(&venv_interp) {
Ok(python_impl) => format!("{name}-{python_impl}"),
Err(_) => name.to_string(),
};
let venv_dir = create_named_virtualenv(&venv_name, interp)?;
let target = Target::from_target_triple(None)?;
let python = target.get_venv_python(&venv_dir);
Ok((venv_dir, python))
}
pub fn create_named_virtualenv(venv_name: &str, interp: Option<PathBuf>) -> Result<PathBuf> {
let venv_dir = PathBuf::from("test-crates")
.normalize()?
.into_path_buf()
.join("venvs")
.join(venv_name);
if venv_dir.is_dir() {
fs::remove_dir_all(&venv_dir)?;
}
let mut cmd = {
if let Ok(uv) = which::which("uv") {
let mut cmd = Command::new(uv);
cmd.args(["venv", "--seed"]);
cmd
} else {
Command::new("virtualenv")
}
};
if let Some(interp) = interp {
cmd.arg("-p").arg(interp);
}
let output = cmd
.arg(dunce::simplified(&venv_dir))
.stderr(Stdio::inherit())
.output()
.expect("Failed to create a virtualenv");
if !output.status.success() {
panic!(
"Failed to run virtualenv: {}\n---stdout:\n{}---stderr:\n{}",
output.status,
str::from_utf8(&output.stdout)?,
str::from_utf8(&output.stderr)?
);
}
Ok(venv_dir)
}
/// Creates conda environments
pub fn create_conda_env(name: &str, major: usize, minor: usize) -> Result<(PathBuf, PathBuf)> {
use serde::Deserialize;
#[derive(Deserialize)]
struct CondaCreateResult {
prefix: PathBuf,
success: bool,
}
let mut cmd = if cfg!(windows) {
let mut cmd = Command::new("cmd.exe");
cmd.arg("/c").arg("conda");
cmd
} else {
Command::new("conda")
};
let output = cmd
.arg("create")
.arg("-n")
.arg(name)
.arg(format!("python={major}.{minor}"))
.arg("-q")
.arg("-y")
.arg("--json")
.output()
.expect("Conda not available.");
if !output.status.success() {
panic!(
"Failed to create conda environment: {}\n---stdout:\n{}---stderr:\n{}",
output.status,
str::from_utf8(&output.stdout)?,
str::from_utf8(&output.stderr)?
);
}
let result: CondaCreateResult = serde_json::from_slice(&output.stdout)?;
if !result.success {
bail!("Failed to create conda environment {}.", name);
}
let target = Target::from_target_triple(None)?;
let python = target.get_venv_python(&result.prefix);
Ok((result.prefix, python))
}
/// Path to the python interpreter for testing
pub fn test_python_path() -> Option<String> {
env::var("MATURIN_TEST_PYTHON").ok()
}
|