Use a stable directory for (local) script virtual environments (#11347)

## Summary

Today, scripts use `CachedEnvironment`, which results in a different
virtual environment path every time the interpreter changes _or_ the
project requirements change. This makes it impossible to provide users
with a stable path to the script that they can use for (e.g.) directing
their editor.

This PR modifies `uv run` to use a stable path for local scripts (we
continue to use `CachedEnvironment` for remote scripts and scripts from
`stdin`). The logic now looks a lot more like it does for projects: we
`get_or_init` an environment, etc.

For now, the path to the script is like:
`environments-v1/4485801245a4732f`, where `4485801245a4732f` is a SHA of
the absolute path to the script. But I'm not picky on that :)
This commit is contained in:
Charlie Marsh 2025-02-11 19:45:26 -05:00 committed by GitHub
parent ba5efa8aa4
commit 79ad7a1ab9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 430 additions and 196 deletions

View file

@ -109,6 +109,11 @@ impl CacheShard {
fs_err::create_dir_all(self.as_ref())?;
LockedFile::acquire(self.join(".lock"), self.display()).await
}
/// Return the [`CacheShard`] as a [`PathBuf`].
pub fn into_path_buf(self) -> PathBuf {
self.0
}
}
impl AsRef<Path> for CacheShard {

View file

@ -16,7 +16,7 @@ pub(crate) fn url_to_precise(url: VerbatimParsedUrl, git: &GitResolver) -> Verba
let Some(new_git_url) = git.precise(git_url.clone()) else {
if cfg!(debug_assertions) {
panic!("Unresolved Git URL: {}, {git_url:?}", url.verbatim,);
panic!("Unresolved Git URL: {}, {git_url:?}", url.verbatim);
} else {
return url;
}

View file

@ -65,7 +65,7 @@ impl Pep723Item {
}
/// A reference to a PEP 723 item.
#[derive(Debug)]
#[derive(Debug, Copy, Clone)]
pub enum Pep723ItemRef<'item> {
/// A PEP 723 script read from disk.
Script(&'item Pep723Script),

View file

@ -3,16 +3,12 @@ use tracing::debug;
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::{cache_digest, hash_digest};
use uv_client::Connectivity;
use uv_configuration::{
Concurrency, DevGroupsManifest, ExtrasSpecification, InstallOptions, PreviewMode, TrustedHost,
};
use uv_configuration::{Concurrency, PreviewMode, TrustedHost};
use uv_distribution_types::{Name, Resolution};
use uv_python::{Interpreter, PythonEnvironment};
use uv_resolver::Installable;
use crate::commands::pip::loggers::{InstallLogger, ResolveLogger};
use crate::commands::pip::operations::Modifications;
use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::{
resolve_environment, sync_environment, EnvironmentSpecification, PlatformState, ProjectError,
};
@ -68,93 +64,6 @@ impl CachedEnvironment {
.await?,
);
Self::from_resolution(
resolution,
interpreter,
settings,
state,
install,
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await
}
/// Get or create an [`CachedEnvironment`] based on a given [`InstallTarget`].
pub(crate) async fn from_lock(
target: InstallTarget<'_>,
extras: &ExtrasSpecification,
dev: &DevGroupsManifest,
install_options: InstallOptions,
settings: &ResolverInstallerSettings,
interpreter: &Interpreter,
state: &PlatformState,
install: Box<dyn InstallLogger>,
installer_metadata: bool,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
allow_insecure_host: &[TrustedHost],
cache: &Cache,
printer: Printer,
preview: PreviewMode,
) -> Result<Self, ProjectError> {
let interpreter = Self::base_interpreter(interpreter, cache)?;
// Determine the tags, markers, and interpreter to use for resolution.
let tags = interpreter.tags()?;
let marker_env = interpreter.resolver_marker_environment();
// Read the lockfile.
let resolution = target.to_resolution(
&marker_env,
tags,
extras,
dev,
&settings.build_options,
&install_options,
)?;
Self::from_resolution(
resolution,
interpreter,
settings,
state,
install,
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await
}
/// Get or create an [`CachedEnvironment`] based on a given [`Resolution`].
pub(crate) async fn from_resolution(
resolution: Resolution,
interpreter: Interpreter,
settings: &ResolverInstallerSettings,
state: &PlatformState,
install: Box<dyn InstallLogger>,
installer_metadata: bool,
connectivity: Connectivity,
concurrency: Concurrency,
native_tls: bool,
allow_insecure_host: &[TrustedHost],
cache: &Cache,
printer: Printer,
preview: PreviewMode,
) -> Result<Self, ProjectError> {
// Hash the resolution by hashing the generated lockfile.
// TODO(charlie): If the resolution contains any mutable metadata (like a path or URL
// dependency), skip this step.

View file

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::collections::{BTreeMap, BTreeSet};
use std::fmt::Write;
use std::ops::Deref;
@ -6,9 +7,9 @@ use std::sync::Arc;
use itertools::Itertools;
use owo_colors::OwoColorize;
use tracing::debug;
use tracing::{debug, warn};
use uv_cache::Cache;
use uv_cache::{Cache, CacheBucket};
use uv_cache_key::cache_digest;
use uv_client::{BaseClientBuilder, Connectivity, FlatIndexClient, RegistryClientBuilder};
use uv_configuration::{
@ -38,7 +39,7 @@ use uv_resolver::{
FlatIndex, Lock, OptionsBuilder, PythonRequirement, RequiresPython, ResolverEnvironment,
ResolverOutput,
};
use uv_scripts::Pep723ItemRef;
use uv_scripts::{Pep723ItemRef, Pep723Script};
use uv_settings::PythonInstallMirrors;
use uv_types::{BuildIsolation, EmptyInstalledPackages, HashStrategy};
use uv_warnings::{warn_user, warn_user_once};
@ -519,9 +520,33 @@ fn validate_script_requires_python(
/// An interpreter suitable for a PEP 723 script.
#[derive(Debug, Clone)]
pub(crate) struct ScriptInterpreter(Interpreter);
#[allow(clippy::large_enum_variant)]
pub(crate) enum ScriptInterpreter {
/// An interpreter to use to create a new script environment.
Interpreter(Interpreter),
/// An interpreter from an existing script environment.
Environment(PythonEnvironment),
}
impl ScriptInterpreter {
/// Return the expected virtual environment path for the [`Pep723Script`].
pub(crate) fn root(script: &Pep723Script, cache: &Cache) -> PathBuf {
let digest = cache_digest(&script.path);
let entry = if let Some(name) = script
.path
.file_stem()
.and_then(|name| name.to_str())
.and_then(cache_name)
{
format!("{name}-{digest}")
} else {
digest
};
cache
.shard(CacheBucket::Environments, entry)
.into_path_buf()
}
/// Discover the interpreter to use for the current [`Pep723Item`].
pub(crate) async fn discover(
script: Pep723ItemRef<'_>,
@ -545,6 +570,43 @@ impl ScriptInterpreter {
requires_python,
} = ScriptPython::from_request(python_request, workspace, script, no_config).await?;
// If this is a local script, use a stable virtual environment.
if let Pep723ItemRef::Script(script) = script {
let root = Self::root(script, cache);
match PythonEnvironment::from_root(&root, cache) {
Ok(venv) => {
if python_request.as_ref().map_or(true, |request| {
if request.satisfied(venv.interpreter(), cache) {
debug!(
"The script environment's Python version satisfies `{}`",
request.to_canonical_string()
);
true
} else {
debug!(
"The script environment's Python version does not satisfy `{}`",
request.to_canonical_string()
);
false
}
}) {
if let Some((requires_python, ..)) = requires_python.as_ref() {
if requires_python.contains(venv.interpreter().python_version()) {
return Ok(Self::Environment(venv));
}
debug!(
"The script environment's Python version does not meet the script's Python requirement: `{requires_python}`"
);
} else {
return Ok(Self::Environment(venv));
}
}
}
Err(uv_python::Error::MissingEnvironment(_)) => {}
Err(err) => warn!("Ignoring existing script environment: {err}"),
};
}
let client_builder = BaseClientBuilder::new()
.connectivity(connectivity)
.native_tls(native_tls)
@ -578,12 +640,24 @@ impl ScriptInterpreter {
warn_user!("{err}");
}
Ok(Self(interpreter))
Ok(Self::Interpreter(interpreter))
}
/// Consume the [`PythonInstallation`] and return the [`Interpreter`].
pub(crate) fn into_interpreter(self) -> Interpreter {
self.0
match self {
ScriptInterpreter::Interpreter(interpreter) => interpreter,
ScriptInterpreter::Environment(venv) => venv.into_interpreter(),
}
}
/// Grab a file lock for the script to prevent concurrent writes across processes.
pub(crate) async fn lock(script: &Pep723Script) -> Result<LockedFile, std::io::Error> {
LockedFile::acquire(
std::env::temp_dir().join(format!("uv-{}.lock", cache_digest(&script.path))),
script.path.simplified_display(),
)
.await
}
}
@ -1140,6 +1214,99 @@ impl Deref for ProjectEnvironment {
}
}
/// The Python environment for a script.
#[derive(Debug)]
struct ScriptEnvironment(PythonEnvironment);
impl ScriptEnvironment {
/// Initialize a virtual environment for a PEP 723 script.
pub(crate) async fn get_or_init(
script: &Pep723Script,
python_request: Option<PythonRequest>,
python_preference: PythonPreference,
python_downloads: PythonDownloads,
connectivity: Connectivity,
native_tls: bool,
allow_insecure_host: &[TrustedHost],
install_mirrors: &PythonInstallMirrors,
no_config: bool,
cache: &Cache,
printer: Printer,
) -> Result<Self, ProjectError> {
// Lock the script environment to avoid synchronization issues.
let _lock = ScriptInterpreter::lock(script).await?;
match ScriptInterpreter::discover(
Pep723ItemRef::Script(script),
python_request,
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
install_mirrors,
no_config,
cache,
printer,
)
.await?
{
// If we found an existing, compatible environment, use it.
ScriptInterpreter::Environment(environment) => Ok(Self(environment)),
// Otherwise, create a virtual environment with the discovered interpreter.
ScriptInterpreter::Interpreter(interpreter) => {
let root = ScriptInterpreter::root(script, cache);
// Remove the existing virtual environment.
match fs_err::remove_dir_all(&root) {
Ok(()) => {
debug!(
"Removed virtual environment at: {}",
root.user_display().cyan()
);
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {}
Err(err) => return Err(err.into()),
};
debug!(
"Creating script environment at: {}",
root.user_display().cyan()
);
// Determine a prompt for the environment, in order of preference:
//
// 1) The name of the script
// 2) No prompt
let prompt = script
.path
.file_name()
.map(|f| f.to_string_lossy().to_string())
.map(uv_virtualenv::Prompt::Static)
.unwrap_or(uv_virtualenv::Prompt::None);
let environment = uv_virtualenv::create_venv(
&root,
interpreter,
prompt,
false,
false,
false,
false,
)?;
Ok(Self(environment))
}
}
}
/// Convert the [`ScriptEnvironment`] into a [`PythonEnvironment`].
pub(crate) fn into_environment(self) -> PythonEnvironment {
self.0
}
}
/// Resolve any [`UnresolvedRequirementSpecification`] into a fully-qualified [`Requirement`].
pub(crate) async fn resolve_names(
requirements: Vec<UnresolvedRequirementSpecification>,
@ -2094,3 +2261,56 @@ fn warn_on_requirements_txt_setting(
warn_user_once!("Ignoring `--no-binary` setting from requirements file. Instead, use the `--no-build` command-line argument, or set `no-build` in a `uv.toml` or `pyproject.toml` file.");
}
}
/// Normalize a filename for use in a cache entry.
///
/// Replaces non-alphanumeric characters with dashes, and lowercases the filename.
fn cache_name(name: &str) -> Option<Cow<'_, str>> {
if name.bytes().all(|c| matches!(c, b'0'..=b'9' | b'a'..=b'f')) {
return if name.is_empty() {
None
} else {
Some(Cow::Borrowed(name))
};
}
let mut normalized = String::with_capacity(name.len());
let mut dash = false;
for char in name.bytes() {
match char {
b'0'..=b'9' | b'a'..=b'z' | b'A'..=b'Z' => {
dash = false;
normalized.push(char.to_ascii_lowercase() as char);
}
_ => {
if !dash {
normalized.push('-');
dash = true;
}
}
}
}
if normalized.ends_with('-') {
normalized.pop();
}
if normalized.is_empty() {
None
} else {
Some(Cow::Owned(normalized))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_name() {
assert_eq!(cache_name("foo"), Some("foo".into()));
assert_eq!(cache_name("foo-bar"), Some("foo-bar".into()));
assert_eq!(cache_name("foo_bar"), Some("foo-bar".into()));
assert_eq!(cache_name("foo-bar_baz"), Some("foo-bar-baz".into()));
assert_eq!(cache_name("foo-bar_baz_"), Some("foo-bar-baz".into()));
assert_eq!(cache_name("foo-_bar_baz"), Some("foo-bar-baz".into()));
assert_eq!(cache_name("_+-_"), None);
}
}

View file

@ -46,9 +46,9 @@ use crate::commands::project::install_target::InstallTarget;
use crate::commands::project::lock::LockMode;
use crate::commands::project::lock_target::LockTarget;
use crate::commands::project::{
default_dependency_groups, validate_project_requires_python, DependencyGroupsTarget,
EnvironmentSpecification, ProjectEnvironment, ProjectError, ScriptInterpreter, UniversalState,
WorkspacePython,
default_dependency_groups, update_environment, validate_project_requires_python,
DependencyGroupsTarget, EnvironmentSpecification, ProjectEnvironment, ProjectError,
ScriptEnvironment, ScriptInterpreter, UniversalState, WorkspacePython,
};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::run::run_to_completion;
@ -191,23 +191,6 @@ pub(crate) async fn run(
}
}
// Discover the interpreter for the script.
let interpreter = ScriptInterpreter::discover(
(&script).into(),
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_interpreter();
// If a lockfile already exists, lock the script.
if let Some(target) = script
.as_script()
@ -216,17 +199,34 @@ pub(crate) async fn run(
{
debug!("Found existing lockfile for script");
// Discover the interpreter for the script.
let environment = ScriptEnvironment::get_or_init(
script.as_script().unwrap(),
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_environment();
// Determine the lock mode.
let mode = if frozen {
LockMode::Frozen
} else if locked {
LockMode::Locked(&interpreter)
LockMode::Locked(environment.interpreter())
} else {
LockMode::Write(&interpreter)
LockMode::Write(environment.interpreter())
};
// Generate a lockfile.
let lock = project::lock::do_safe_lock(
let lock = match project::lock::do_safe_lock(
mode,
target,
settings.as_ref().into(),
@ -244,19 +244,35 @@ pub(crate) async fn run(
printer,
preview,
)
.await?
.into_lock();
.await
{
Ok(result) => result.into_lock(),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Err(err) => return Err(err.into()),
};
let result = CachedEnvironment::from_lock(
InstallTarget::Script {
script: script.as_script().unwrap(),
lock: &lock,
},
&ExtrasSpecification::default(),
&DevGroupsSpecification::default().with_defaults(Vec::new()),
InstallOptions::default(),
&settings,
&interpreter,
// Sync the environment.
let target = InstallTarget::Script {
script: script.as_script().unwrap(),
lock: &lock,
};
let install_options = InstallOptions::default();
match project::sync::do_sync(
target,
&environment,
&extras,
&dev.with_defaults(Vec::new()),
editable,
install_options,
modifications,
settings.as_ref().into(),
&sync_state,
if show_resolution {
Box::new(DefaultInstallLogger)
@ -269,13 +285,13 @@ pub(crate) async fn run(
native_tls,
allow_insecure_host,
cache,
DryRun::Disabled,
printer,
preview,
)
.await;
let environment = match result {
Ok(resolution) => resolution,
.await
{
Ok(()) => {}
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
@ -283,10 +299,7 @@ pub(crate) async fn run(
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Err(err) => return Err(err.into()),
};
// Clear any existing overlay.
environment.clear_overlay()?;
}
Some(environment.into_interpreter())
} else {
@ -312,14 +325,14 @@ pub(crate) async fn run(
.to_owned(),
Pep723Item::Stdin(..) | Pep723Item::Remote(..) => std::env::current_dir()?,
};
let script = script.into_metadata();
let metadata = script.metadata();
// Install the script requirements, if necessary. Otherwise, use an isolated environment.
if let Some(dependencies) = script.dependencies {
if let Some(dependencies) = metadata.dependencies.as_ref() {
// Collect any `tool.uv.index` from the script.
let empty = Vec::default();
let script_indexes = match settings.sources {
SourceStrategy::Enabled => script
SourceStrategy::Enabled => metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
@ -331,7 +344,7 @@ pub(crate) async fn run(
// Collect any `tool.uv.sources` from the script.
let empty = BTreeMap::default();
let script_sources = match settings.sources {
SourceStrategy::Enabled => script
SourceStrategy::Enabled => metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
@ -341,7 +354,8 @@ pub(crate) async fn run(
};
let requirements = dependencies
.into_iter()
.iter()
.cloned()
.flat_map(|requirement| {
LoweredRequirement::from_non_workspace_requirement(
requirement,
@ -353,7 +367,7 @@ pub(crate) async fn run(
.map_ok(LoweredRequirement::into_inner)
})
.collect::<Result<_, _>>()?;
let constraints = script
let constraints = metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
@ -372,7 +386,7 @@ pub(crate) async fn run(
.map_ok(LoweredRequirement::into_inner)
})
.collect::<Result<Vec<_>, _>>()?;
let overrides = script
let overrides = metadata
.tool
.as_ref()
.and_then(|tool| tool.uv.as_ref())
@ -394,49 +408,139 @@ pub(crate) async fn run(
let spec =
RequirementsSpecification::from_overrides(requirements, constraints, overrides);
let result = CachedEnvironment::from_spec(
EnvironmentSpecification::from(spec),
&interpreter,
&settings,
&sync_state,
if show_resolution {
Box::new(DefaultResolveLogger)
} else {
Box::new(SummaryResolveLogger)
},
if show_resolution {
Box::new(DefaultInstallLogger)
} else {
Box::new(SummaryInstallLogger)
},
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await;
let environment = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
if let Some(script) = script.as_script() {
// If the script is a local file, use a persistent environment.
let environment = ScriptEnvironment::get_or_init(
script,
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_environment();
match update_environment(
environment,
spec,
modifications,
&settings,
&sync_state,
if show_resolution {
Box::new(DefaultResolveLogger)
} else {
Box::new(SummaryResolveLogger)
},
if show_resolution {
Box::new(DefaultInstallLogger)
} else {
Box::new(SummaryInstallLogger)
},
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await
{
Ok(update) => Some(update.into_environment().into_interpreter()),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Err(err) => return Err(err.into()),
}
Err(err) => return Err(err.into()),
};
} else {
// Otherwise, use an ephemeral environment.
let interpreter = ScriptInterpreter::discover(
(&script).into(),
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_interpreter();
// Clear any existing overlay.
environment.clear_overlay()?;
let result = CachedEnvironment::from_spec(
EnvironmentSpecification::from(spec),
&interpreter,
&settings,
&sync_state,
if show_resolution {
Box::new(DefaultResolveLogger)
} else {
Box::new(SummaryResolveLogger)
},
if show_resolution {
Box::new(DefaultInstallLogger)
} else {
Box::new(SummaryInstallLogger)
},
installer_metadata,
connectivity,
concurrency,
native_tls,
allow_insecure_host,
cache,
printer,
preview,
)
.await;
Some(environment.into_interpreter())
let environment = match result {
Ok(resolution) => resolution,
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::native_tls(native_tls)
.with_context("script")
.report(err)
.map_or(Ok(ExitStatus::Failure), |err| Err(err.into()))
}
Err(err) => return Err(err.into()),
};
// Clear any existing overlay.
environment.clear_overlay()?;
Some(environment.into_interpreter())
}
} else {
// Create a virtual environment.
let interpreter = ScriptInterpreter::discover(
(&script).into(),
python.as_deref().map(PythonRequest::parse),
python_preference,
python_downloads,
connectivity,
native_tls,
allow_insecure_host,
&install_mirrors,
no_config,
cache,
printer,
)
.await?
.into_interpreter();
temp_dir = cache.venv_dir()?;
let environment = uv_virtualenv::create_venv(
temp_dir.path(),
@ -471,7 +575,7 @@ pub(crate) async fn run(
warn_user!("Extras are not supported for Python scripts with inline metadata");
}
for flag in dev.history().as_flags_pretty() {
warn_user!("`{flag}` is not supported for Python scripts with inline metadata",);
warn_user!("`{flag}` is not supported for Python scripts with inline metadata");
}
if all_packages {
warn_user!(

View file

@ -319,7 +319,6 @@ fn run_pep723_script() -> Result<()> {
----- stdout -----
----- stderr -----
Resolved 1 package in [TIME]
"###);
// But neither invocation should create a lockfile.
@ -847,8 +846,7 @@ fn run_pep723_script_lock() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
Installed 1 package in [TIME]
+ iniconfig==2.0.0
Audited 1 package in [TIME]
"###);
// With a lockfile, running with `--locked` should not warn.
@ -860,6 +858,7 @@ fn run_pep723_script_lock() -> Result<()> {
----- stderr -----
Resolved 1 package in [TIME]
Audited 1 package in [TIME]
"###);
// Modify the metadata.
@ -895,6 +894,7 @@ fn run_pep723_script_lock() -> Result<()> {
----- stdout -----
----- stderr -----
Audited 1 package in [TIME]
Traceback (most recent call last):
File "[TEMP_DIR]/main.py", line 8, in <module>
import anyio
@ -3276,8 +3276,6 @@ fn run_gui_script_explicit_windows() -> Result<()> {
----- stdout -----
----- stderr -----
Resolved in [TIME]
Audited in [TIME]
Using executable: pythonw.exe
"###);
@ -3341,9 +3339,7 @@ fn run_gui_script_explicit_unix() -> Result<()> {
----- stdout -----
----- stderr -----
Resolved in [TIME]
Audited in [TIME]
Using executable: python3
Using executable: python
"###);
Ok(())