Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions crates/venvlauncher/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,20 @@
compile_error!("venvlauncher is only supported on Windows");

use std::env;
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::ExitCode;

fn main() -> ExitCode {
match run() {
Ok(code) => ExitCode::from(code as u8),
fn main() {
let code = match run() {
Ok(code) => code,
Err(e) => {
eprintln!("venvlauncher error: {}", e);
ExitCode::from(1)
eprintln!("venvlauncher error: {e}");
1
}
}
};
// ExitCode::from(u8) would truncate the Windows process status.
std::process::exit(code as i32);
}

fn run() -> Result<u32, Box<dyn core::error::Error>> {
Expand Down Expand Up @@ -67,7 +69,7 @@ fn run() -> Result<u32, Box<dyn core::error::Error>> {
}

// 7. Launch Python with same arguments
let args: Vec<String> = env::args().skip(1).collect();
let args: Vec<OsString> = env::args_os().skip(1).collect();
launch_process(&python_path, &args)
}

Expand Down Expand Up @@ -95,7 +97,7 @@ fn read_home(cfg_path: &Path) -> Result<String, Box<dyn core::error::Error>> {
}

/// Launch the Python process and wait for it to complete
fn launch_process(exe: &Path, args: &[String]) -> Result<u32, Box<dyn core::error::Error>> {
fn launch_process(exe: &Path, args: &[OsString]) -> Result<u32, Box<dyn core::error::Error>> {
use std::process::Command;

let status = Command::new(exe).args(args).status()?;
Expand Down
99 changes: 99 additions & 0 deletions crates/venvlauncher/tests/launcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#![cfg(windows)]

#[cfg(test)]
mod tests {
use core::sync::atomic::{AtomicUsize, Ordering};
use std::ffi::OsString;
use std::fs;
use std::os::windows::ffi::OsStringExt;
use std::path::PathBuf;
use std::process::Command;

struct TestVenv {
root: PathBuf,
}

impl TestVenv {
fn new() -> Self {
static NEXT_ID: AtomicUsize = AtomicUsize::new(0);
let root = loop {
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
let path = std::env::temp_dir().join(format!(
"rustpython-venvlauncher-{}-{id}",
std::process::id()
));
match fs::create_dir(&path) {
Ok(()) => break path,
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => continue,
Err(error) => panic!("cannot create test environment: {error}"),
}
};
let fixture = Self { root };
let home = fixture.root.join("home");
let scripts = fixture.root.join("Scripts");
fs::create_dir(&home).unwrap();
fs::create_dir(&scripts).unwrap();
let system_root = std::env::var_os("SystemRoot").unwrap();
fs::copy(
PathBuf::from(system_root).join("System32/cmd.exe"),
home.join("python.exe"),
)
.unwrap();
fs::copy(
env!("CARGO_BIN_EXE_venvlaunchert"),
scripts.join("python.exe"),
)
.unwrap();
fs::write(
fixture.root.join("pyvenv.cfg"),
format!("home = {}\n", home.display()),
)
.unwrap();
fixture
}

fn command(&self) -> Command {
Command::new(self.root.join("Scripts/python.exe"))
}
}

impl Drop for TestVenv {
fn drop(&mut self) {
fs::remove_dir_all(&self.root).unwrap();
}
}

#[test]
fn preserves_windows_exit_codes() {
let fixture = TestVenv::new();
for code in [0_u32, 255, 256, 4660, 0xC0000005, u32::MAX] {
let output = fixture
.command()
.args(["/d", "/c", "exit", &code.to_string()])
.output()
.unwrap();
assert_eq!(output.status.code().map(|code| code as u32), Some(code));
}
}

#[test]
fn forwards_windows_wide_arguments() {
let fixture = TestVenv::new();
for units in [&[0xd800][..], &[0xdc00][..], &[0xd83d, 0xde00][..]] {
let output = fixture
.command()
.args(["/d", "/u", "/c", "echo"])
.arg(OsString::from_wide(units))
.output()
.unwrap();
assert!(output.status.success(), "{:?}", output.stderr);
let expected: Vec<_> = units
.iter()
.copied()
.chain([13, 10])
.flat_map(u16::to_le_bytes)
.collect();
assert_eq!(output.stdout, expected);
}
}
}
Loading