Download bazel/rules/testing/wine/src/lib_tests.rs from SaylorTwift/codex: direct link, hf CLI and curl.
- Browser
- Download file 14.7 kB
-
https://huggingface.co/SaylorTwift/codex/resolve/main/bazel/rules/testing/wine/src/lib_tests.rs
- Command line
-
hf download hf://SaylorTwift/codex/bazel/rules/testing/wine/src/lib_tests.rs
-
curl -L -o lib_tests.rs https://huggingface.co/SaylorTwift/codex/resolve/main/bazel/rules/testing/wine/src/lib_tests.rs
14.7 kB
| use std::any::Any; | |
| use std::collections::HashMap; | |
| use std::fs; | |
| use std::future::Future; | |
| use std::panic::AssertUnwindSafe; | |
| use std::path::Path; | |
| use std::path::PathBuf; | |
| use std::time::Duration; | |
| use anyhow::Context; | |
| use anyhow::Result; | |
| use anyhow::anyhow; | |
| use codex_utils_pty::SpawnedProcess; | |
| use codex_utils_pty::TerminalSize; | |
| use futures::FutureExt; | |
| use pretty_assertions::assert_eq; | |
| use tempfile::TempDir; | |
| use tokio::io::AsyncBufReadExt; | |
| use tokio::io::BufReader; | |
| use tokio::process::Command as TokioCommand; | |
| use tokio::time::timeout; | |
| use super::WineRuntimePaths; | |
| use super::WineTestCommand; | |
| use super::WineTestProcess; | |
| use super::install_powershell_runtime; | |
| async fn waiting_smoke_process() -> Result<WineTestProcess> { | |
| let executable = codex_utils_cargo_bin::cargo_bin("wine-smoke")?; | |
| let mut process = WineTestCommand::new(executable).arg("--wait").spawn()?; | |
| let mut lines = BufReader::new(process.take_stdout()).lines(); | |
| let ready_line = lines | |
| .next_line() | |
| .await? | |
| .context("Windows smoke process exited before becoming ready")?; | |
| assert_eq!(ready_line, "WINE_TEST_READY"); | |
| Ok(process) | |
| } | |
| fn prefix_path(process: &WineTestProcess) -> PathBuf { | |
| process | |
| .processes | |
| .as_ref() | |
| .expect("Wine process guard") | |
| .prefix | |
| .path() | |
| .to_path_buf() | |
| } | |
| fn assert_prefix_removed(prefix: &Path) { | |
| assert!( | |
| !prefix.exists(), | |
| "Wine prefix remains: {}", | |
| prefix.display() | |
| ); | |
| } | |
| fn assert_panic_message(panic: Box<dyn Any + Send>, expected: &str) { | |
| assert_eq!(panic.downcast_ref::<&str>(), Some(&expected)); | |
| } | |
| async fn assert_future_panics<T>(future: impl Future<Output = T>, expected: &str) { | |
| let panic = match AssertUnwindSafe(future).catch_unwind().await { | |
| Ok(_) => panic!("future should panic"), | |
| Err(panic) => panic, | |
| }; | |
| assert_panic_message(panic, expected); | |
| } | |
| async fn process_with_failing_wineserver_stop() -> Result<WineTestProcess> { | |
| let mut process = waiting_smoke_process().await?; | |
| let processes = process.processes.as_mut().expect("Wine process guard"); | |
| let mut command = TokioCommand::from(processes.stop_wineserver_command()); | |
| let status = command | |
| .status() | |
| .await | |
| .context("pre-stop isolated wineserver")?; | |
| assert!(status.success(), "wineserver exited with {status}"); | |
| processes.runtime.wineserver = processes.prefix.path().join("missing-wineserver"); | |
| Ok(process) | |
| } | |
| async fn dropping_without_teardown_panics() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| assert_future_panics( | |
| async move { drop(process) }, | |
| "WineTestProcess dropped without async teardown", | |
| ) | |
| .await; | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn dropping_while_panicking_does_not_panic_again() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| assert_future_panics( | |
| async move { | |
| let _process = process; | |
| panic!("sentinel panic"); | |
| }, | |
| "sentinel panic", | |
| ) | |
| .await; | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn async_teardown_disarms_drop_bomb() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| process.shutdown().await?; | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn take_stdout_panics_when_called_twice() -> Result<()> { | |
| let mut process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| assert_future_panics( | |
| async { | |
| process.take_stdout(); | |
| }, | |
| "Wine process stdout has already been taken", | |
| ) | |
| .await; | |
| process.shutdown().await?; | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn scope_returns_value_and_tears_down() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| let value = process | |
| .scope(async { Ok::<_, anyhow::Error>("scope value") }) | |
| .await?; | |
| assert_eq!(value, "scope value"); | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn scope_returns_body_error_and_tears_down() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| let error = process | |
| .scope(async { Err::<(), _>(anyhow!("scope body failed")) }) | |
| .await | |
| .expect_err("scope body should fail"); | |
| assert_eq!(error.to_string(), "scope body failed"); | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn scope_panic_preserves_panic_and_tears_down() -> Result<()> { | |
| let process = waiting_smoke_process().await?; | |
| let prefix = prefix_path(&process); | |
| assert_future_panics( | |
| process.scope::<()>(async { panic!("scope panic") }), | |
| "scope panic", | |
| ) | |
| .await; | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn shutdown_reports_nonzero_process_exit() -> Result<()> { | |
| let executable = codex_utils_cargo_bin::cargo_bin("wine-smoke")?; | |
| let mut process = WineTestCommand::new(executable).arg("--fail").spawn()?; | |
| let prefix = prefix_path(&process); | |
| let status = process | |
| .processes | |
| .as_mut() | |
| .expect("Wine process guard") | |
| .child | |
| .wait() | |
| .await?; | |
| assert!( | |
| !status.success(), | |
| "Windows smoke process unexpectedly passed" | |
| ); | |
| let error = process.shutdown().await.expect_err("shutdown should fail"); | |
| assert!(error.to_string().starts_with("Windows process exited with")); | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn scope_preserves_body_error_when_teardown_also_fails() -> Result<()> { | |
| let process = process_with_failing_wineserver_stop().await?; | |
| let prefix = prefix_path(&process); | |
| let error = process | |
| .scope(async { Err::<(), _>(anyhow!("scope body failed")) }) | |
| .await | |
| .expect_err("scope body and teardown should fail"); | |
| assert!( | |
| error | |
| .to_string() | |
| .starts_with("Wine teardown also failed: stop isolated wineserver"), | |
| "unexpected error: {error:#}" | |
| ); | |
| assert_eq!( | |
| error.chain().last().map(ToString::to_string), | |
| Some("scope body failed".to_string()) | |
| ); | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| async fn shutdown_returns_teardown_error() -> Result<()> { | |
| let process = process_with_failing_wineserver_stop().await?; | |
| let prefix = prefix_path(&process); | |
| let error = process | |
| .shutdown() | |
| .await | |
| .expect_err("shutdown should report a wineserver failure"); | |
| assert_eq!(error.to_string(), "stop isolated wineserver"); | |
| assert_prefix_removed(&prefix); | |
| Ok(()) | |
| } | |
| fn powershell_runtime_is_materialized_at_the_windows_fallback_path() -> Result<()> { | |
| let prefix = TempDir::new()?; | |
| let runtime = TempDir::new()?; | |
| fs::create_dir(runtime.path().join("Modules"))?; | |
| fs::write(runtime.path().join("pwsh.exe"), b"pwsh")?; | |
| fs::write(runtime.path().join("Modules").join("marker.txt"), b"module")?; | |
| install_powershell_runtime(prefix.path(), runtime.path())?; | |
| let installed = prefix | |
| .path() | |
| .join("drive_c") | |
| .join("Program Files") | |
| .join("PowerShell") | |
| .join("7"); | |
| assert_eq!(fs::read(installed.join("pwsh.exe"))?, b"pwsh"); | |
| assert_eq!( | |
| fs::read(installed.join("Modules").join("marker.txt"))?, | |
| b"module" | |
| ); | |
| Ok(()) | |
| } | |
| fn powershell_runtime_follows_runfiles_symlinks() -> Result<()> { | |
| let prefix = TempDir::new()?; | |
| let runtime = TempDir::new()?; | |
| let backing = TempDir::new()?; | |
| let backing_file = backing.path().join("pwsh.exe"); | |
| fs::write(&backing_file, b"pwsh")?; | |
| std::os::unix::fs::symlink(&backing_file, runtime.path().join("pwsh.exe"))?; | |
| install_powershell_runtime(prefix.path(), runtime.path())?; | |
| let installed = prefix | |
| .path() | |
| .join("drive_c") | |
| .join("Program Files") | |
| .join("PowerShell") | |
| .join("7") | |
| .join("pwsh.exe"); | |
| assert_eq!(fs::read(installed)?, b"pwsh"); | |
| Ok(()) | |
| } | |
| async fn pinned_powershell_runs_under_wine_with_a_pty() -> Result<()> { | |
| // Keep this integration smoke test local to the Wine support crate. The | |
| // production-shaped PowerShell launch path belongs to exec-server tests. | |
| // The marker makes the assertion resilient to Wine or PTY startup chatter. | |
| const POWERSHELL_SMOKE_MARKER: &str = "WINE_PWSH_SMOKE"; | |
| // Besides proving that the pinned runtime starts, report the properties | |
| // that shell detection and command construction rely on: PowerShell 7 Core | |
| // running with Windows semantics and a backslash path separator. | |
| const POWERSHELL_SMOKE_SCRIPT: &str = concat!( | |
| "$ErrorActionPreference = 'Stop'; ", | |
| "[Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false); ", | |
| "$separatorCode = [int]([System.IO.Path]::DirectorySeparatorChar); ", | |
| "if ($PSVersionTable.PSVersion.Major -ne 7) { throw 'expected PowerShell 7' }; ", | |
| "if ($PSVersionTable.PSEdition -ne 'Core') { throw 'expected PowerShell Core' }; ", | |
| "if (-not $IsWindows) { throw 'expected Windows semantics' }; ", | |
| "if ($separatorCode -ne 92) { throw 'expected backslash path separator' }; ", | |
| "Write-Output ('WINE_PWSH_SMOKE|' + ", | |
| "$PSVersionTable.PSVersion.ToString() + '|' + ", | |
| "$PSVersionTable.PSEdition + '|' + ", | |
| "$IsWindows.ToString().ToLowerInvariant() + '|' + $separatorCode)", | |
| ); | |
| let runtime = WineRuntimePaths::from_runfiles()?; | |
| let prefix = TempDir::new()?; | |
| install_powershell_runtime(prefix.path(), &runtime.powershell_runtime)?; | |
| let mut env = std::env::vars().collect::<HashMap<_, _>>(); | |
| env.remove("DISPLAY"); | |
| env.extend([ | |
| ( | |
| "HOME".to_string(), | |
| prefix.path().to_string_lossy().into_owned(), | |
| ), | |
| ( | |
| "XDG_RUNTIME_DIR".to_string(), | |
| prefix.path().to_string_lossy().into_owned(), | |
| ), | |
| ("WINEARCH".to_string(), "win64".to_string()), | |
| ( | |
| "WINEPREFIX".to_string(), | |
| prefix.path().to_string_lossy().into_owned(), | |
| ), | |
| ( | |
| "WINEDLLPATH".to_string(), | |
| runtime.dll_path.to_string_lossy().into_owned(), | |
| ), | |
| ( | |
| "WINESERVER".to_string(), | |
| runtime.wineserver.to_string_lossy().into_owned(), | |
| ), | |
| ("WINEDEBUG".to_string(), "-all".to_string()), | |
| ( | |
| "WINEDLLOVERRIDES".to_string(), | |
| "mscoree,mshtml,winegstreamer=".to_string(), | |
| ), | |
| ("LANG".to_string(), "C.UTF-8".to_string()), | |
| ("LC_ALL".to_string(), "C.UTF-8".to_string()), | |
| ("LC_CTYPE".to_string(), "C.UTF-8".to_string()), | |
| ("TEMP".to_string(), r"C:\windows\temp".to_string()), | |
| ("TMP".to_string(), r"C:\windows\temp".to_string()), | |
| ]); | |
| let args = [ | |
| r"C:\Program Files\PowerShell\7\pwsh.exe".to_string(), | |
| "-NoLogo".to_string(), | |
| "-NoProfile".to_string(), | |
| "-NonInteractive".to_string(), | |
| "-Command".to_string(), | |
| POWERSHELL_SMOKE_SCRIPT.to_string(), | |
| ]; | |
| let wine = runtime.wine.to_string_lossy().into_owned(); | |
| let SpawnedProcess { | |
| session, | |
| mut stdout_rx, | |
| mut stderr_rx, | |
| exit_rx, | |
| } = codex_utils_pty::spawn_pty_process( | |
| &wine, | |
| &args, | |
| prefix.path(), | |
| &env, | |
| /*arg0*/ &None, | |
| TerminalSize::default(), | |
| /*inherited_fds*/ &[], | |
| ) | |
| .await?; | |
| let command_result = timeout(Duration::from_secs(30), async { | |
| let stdout = async { | |
| let mut output = Vec::new(); | |
| while let Some(chunk) = stdout_rx.recv().await { | |
| output.extend(chunk); | |
| } | |
| output | |
| }; | |
| let stderr = async { | |
| let mut output = Vec::new(); | |
| while let Some(chunk) = stderr_rx.recv().await { | |
| output.extend(chunk); | |
| } | |
| output | |
| }; | |
| let (stdout, stderr, exit_code) = tokio::join!(stdout, stderr, exit_rx); | |
| Ok::<_, anyhow::Error>((stdout, stderr, exit_code.context("wait for PowerShell")?)) | |
| }) | |
| .await | |
| .context("PowerShell smoke test timed out") | |
| .and_then(std::convert::identity); | |
| drop(session); | |
| let shutdown_result = timeout(Duration::from_secs(10), async { | |
| let mut command = TokioCommand::new(&runtime.wineserver); | |
| command | |
| .args(["-k", "-w"]) | |
| .env("HOME", prefix.path()) | |
| .env("WINEPREFIX", prefix.path()) | |
| .env("XDG_RUNTIME_DIR", prefix.path()) | |
| .kill_on_drop(true); | |
| let status = command.status().await.context("stop isolated wineserver")?; | |
| anyhow::ensure!( | |
| status.success() || status.code() == Some(1), | |
| "wineserver exited with {status}" | |
| ); | |
| Ok::<_, anyhow::Error>(()) | |
| }) | |
| .await | |
| .context("stop isolated wineserver timed out") | |
| .and_then(std::convert::identity); | |
| let (stdout, stderr, exit_code) = match (command_result, shutdown_result) { | |
| (Ok(output), Ok(())) => output, | |
| (Err(error), Ok(())) => return Err(error), | |
| (Ok(_), Err(error)) => return Err(error), | |
| (Err(error), Err(shutdown_error)) => { | |
| return Err(error.context(format!("Wine teardown also failed: {shutdown_error:#}"))); | |
| } | |
| }; | |
| anyhow::ensure!( | |
| exit_code == 0, | |
| "PowerShell exited with {}; stderr: {}", | |
| exit_code, | |
| String::from_utf8_lossy(&stderr), | |
| ); | |
| let output = String::from_utf8(stdout)?; | |
| let marker_start = output | |
| .find(POWERSHELL_SMOKE_MARKER) | |
| .with_context(|| format!("PowerShell smoke marker was missing from {output:?}"))?; | |
| let smoke = output[marker_start..] | |
| .lines() | |
| .next() | |
| .context("PowerShell smoke marker line was incomplete")? | |
| .trim_end_matches('\r'); | |
| let fields = smoke.split('|').collect::<Vec<_>>(); | |
| assert_eq!( | |
| fields.len(), | |
| 5, | |
| "unexpected PowerShell smoke output: {smoke}" | |
| ); | |
| assert_eq!(fields[0], POWERSHELL_SMOKE_MARKER); | |
| assert_eq!( | |
| fields[1].split('.').next(), | |
| Some("7"), | |
| "expected PowerShell 7.x, got {}", | |
| fields[1], | |
| ); | |
| assert_eq!(&fields[2..], &["Core", "true", "92"]); | |
| Ok(()) | |
| } | |