From 42479bd91ef135298cc6e63d8eca0b138980f18e Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Thu, 27 Jun 2024 08:40:56 -0500 Subject: [PATCH] Improve toolchain and environment missing error messages --- crates/uv-toolchain/src/discovery.rs | 227 ++++++++++--------------- crates/uv-toolchain/src/environment.rs | 63 ++++++- crates/uv-toolchain/src/lib.rs | 58 ++----- crates/uv-toolchain/src/toolchain.rs | 2 +- crates/uv/src/commands/project/mod.rs | 4 +- crates/uv/tests/lock.rs | 4 +- crates/uv/tests/pip_sync.rs | 2 +- crates/uv/tests/toolchain_find.rs | 56 ++++-- crates/uv/tests/venv.rs | 8 +- 9 files changed, 214 insertions(+), 210 deletions(-) diff --git a/crates/uv-toolchain/src/discovery.rs b/crates/uv-toolchain/src/discovery.rs index a58313345e04..cfd3171df341 100644 --- a/crates/uv-toolchain/src/discovery.rs +++ b/crates/uv-toolchain/src/discovery.rs @@ -51,6 +51,7 @@ pub enum ToolchainRequest { /// Generally these refer to uv-managed toolchain downloads. Key(PythonDownloadRequest), } + #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, serde::Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] #[cfg_attr(feature = "clap", derive(clap::ValueEnum))] @@ -115,29 +116,12 @@ type ToolchainResult = Result; /// The result of failed toolchain discovery. /// -/// See [`InterpreterResult`]. +/// See [`ToolchainResult`]. #[derive(Clone, Debug, Error)] -pub enum ToolchainNotFound { - /// No Python installations were found. - NoPythonInstallation(ToolchainPreference, Option), - /// No Python installations with the requested version were found. - NoMatchingVersion(ToolchainPreference, VersionRequest), - /// No Python installations with the requested key were found. - NoMatchingKey(ToolchainPreference, PythonDownloadRequest), - /// No Python installations with the requested implementation name were found. - NoMatchingImplementation(ToolchainPreference, ImplementationName), - /// No Python installations with the requested implementation name and version were found. - NoMatchingImplementationVersion(ToolchainPreference, ImplementationName, VersionRequest), - /// The requested file path does not exist. - FileNotFound(PathBuf), - /// The requested directory path does not exist. - DirectoryNotFound(PathBuf), - /// No Python executables could be found in the requested directory. - ExecutableNotFoundInDirectory(PathBuf, PathBuf), - /// The Python executable name could not be found in the search path (i.e. PATH). - ExecutableNotFoundInSearchPath(String), - /// A Python executable was found but is not executable. - FileNotExecutable(PathBuf), +pub struct ToolchainNotFound { + pub request: ToolchainRequest, + pub toolchain_preference: ToolchainPreference, + pub environment_preference: EnvironmentPreference, } /// A location for discovery of a Python toolchain. @@ -559,34 +543,25 @@ impl Error { } } -fn find_toolchain_at_file(path: &PathBuf, cache: &Cache) -> Result { - if !path.try_exists()? { - return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound( - path.clone(), - ))); - } - Ok(ToolchainResult::Ok(Toolchain { +fn find_toolchain_at_file( + path: &PathBuf, + cache: &Cache, +) -> Result { + Ok(Toolchain { source: ToolchainSource::ProvidedPath, interpreter: Interpreter::query(path, cache)?, - })) + }) } -fn find_toolchain_at_directory(path: &PathBuf, cache: &Cache) -> Result { - if !path.try_exists()? { - return Ok(ToolchainResult::Err(ToolchainNotFound::FileNotFound( - path.clone(), - ))); - } +fn find_toolchain_at_directory( + path: &PathBuf, + cache: &Cache, +) -> Result { let executable = virtualenv_python_executable(path); - if !executable.try_exists()? { - return Ok(ToolchainResult::Err( - ToolchainNotFound::ExecutableNotFoundInDirectory(path.clone(), executable), - )); - } - Ok(ToolchainResult::Ok(Toolchain { + Ok(Toolchain { source: ToolchainSource::ProvidedPath, interpreter: Interpreter::query(executable, cache)?, - })) + }) } /// Lazily iterate over all Python interpreters on the path with the given executable name. @@ -613,7 +588,17 @@ pub fn find_toolchains<'a>( ToolchainRequest::File(path) => Box::new(std::iter::once({ if preference.allows(ToolchainSource::ProvidedPath) { debug!("Checking for Python interpreter at {request}"); - find_toolchain_at_file(path, cache) + match find_toolchain_at_file(path, cache) { + Ok(toolchain) => Ok(ToolchainResult::Ok(toolchain)), + Err(InterpreterError::NotFound(_)) => { + Ok(ToolchainResult::Err(ToolchainNotFound { + request: request.clone(), + toolchain_preference: preference, + environment_preference: environments, + })) + } + Err(err) => Err(err.into()), + } } else { Err(Error::SourceNotAllowed( request.clone(), @@ -626,7 +611,17 @@ pub fn find_toolchains<'a>( debug!("Checking for Python interpreter in {request}"); if preference.allows(ToolchainSource::ProvidedPath) { debug!("Checking for Python interpreter at {request}"); - find_toolchain_at_directory(path, cache) + match find_toolchain_at_directory(path, cache) { + Ok(toolchain) => Ok(ToolchainResult::Ok(toolchain)), + Err(InterpreterError::NotFound(_)) => { + Ok(ToolchainResult::Err(ToolchainNotFound { + request: request.clone(), + toolchain_preference: preference, + environment_preference: environments, + })) + } + Err(err) => Err(err.into()), + } } else { Err(Error::SourceNotAllowed( request.clone(), @@ -731,31 +726,11 @@ pub(crate) fn find_toolchain( }) { result } else { - let err = match request { - ToolchainRequest::Implementation(implementation) => { - ToolchainNotFound::NoMatchingImplementation(preference, *implementation) - } - ToolchainRequest::ImplementationVersion(implementation, version) => { - ToolchainNotFound::NoMatchingImplementationVersion( - preference, - *implementation, - version.clone(), - ) - } - ToolchainRequest::Version(version) => { - ToolchainNotFound::NoMatchingVersion(preference, version.clone()) - } - ToolchainRequest::ExecutableName(name) => { - ToolchainNotFound::ExecutableNotFoundInSearchPath(name.clone()) - } - ToolchainRequest::Key(key) => ToolchainNotFound::NoMatchingKey(preference, key.clone()), - // TODO(zanieb): As currently implemented, these are unreachable as they are handled in `find_toolchains` - // We should avoid this duplication - ToolchainRequest::Directory(path) => ToolchainNotFound::DirectoryNotFound(path.clone()), - ToolchainRequest::File(path) => ToolchainNotFound::FileNotFound(path.clone()), - ToolchainRequest::Any => ToolchainNotFound::NoPythonInstallation(preference, None), - }; - Ok(ToolchainResult::Err(err)) + Ok(ToolchainResult::Err(ToolchainNotFound { + request: request.clone(), + environment_preference: environments, + toolchain_preference: preference, + })) } } @@ -818,10 +793,10 @@ pub fn find_best_toolchain( Ok( find_toolchain(&request, environments, preference, cache)?.map_err(|err| { // Use a more general error in this case since we looked for multiple versions - if matches!(err, ToolchainNotFound::NoMatchingVersion(..)) { - ToolchainNotFound::NoPythonInstallation(preference, None) - } else { - err + ToolchainNotFound { + request, + toolchain_preference: err.toolchain_preference, + environment_preference: err.environment_preference, } }), ) @@ -1149,6 +1124,10 @@ impl ToolchainRequest { ToolchainRequest::Key(request) => request.satisfied_by_interpreter(interpreter), } } + + pub(crate) fn is_explicit_system(&self) -> bool { + matches!(self, Self::File(_) | Self::Directory(_)) + } } impl ToolchainPreference { @@ -1468,10 +1447,10 @@ impl fmt::Display for ToolchainRequest { Self::File(path) => write!(f, "path `{}`", path.user_display()), Self::ExecutableName(name) => write!(f, "executable name `{name}`"), Self::Implementation(implementation) => { - write!(f, "{implementation}") + write!(f, "{}", implementation.pretty()) } Self::ImplementationVersion(implementation, version) => { - write!(f, "{implementation} {version}") + write!(f, "{} {version}", implementation.pretty()) } Self::Key(request) => write!(f, "{request}"), } @@ -1495,75 +1474,49 @@ impl fmt::Display for ToolchainSource { impl fmt::Display for ToolchainPreference { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::OnlyManaged => f.write_str("managed toolchains"), - Self::OnlySystem => f.write_str("system toolchains"), - Self::PreferInstalledManaged | Self::PreferManaged | Self::PreferSystem => { - f.write_str("managed or system toolchains") + let s = match self { + Self::OnlyManaged => "managed toolchains", + Self::PreferManaged | Self::PreferInstalledManaged | Self::PreferSystem => { + if cfg!(windows) { + "managed toolchains, system path, or `py` launcher" + } else { + "managed toolchains or system path" + } } - } + Self::OnlySystem => { + if cfg!(windows) { + "system path or `py` launcher" + } else { + "system path" + } + } + }; + f.write_str(s) } } impl fmt::Display for ToolchainNotFound { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Self::NoPythonInstallation(sources, None | Some(VersionRequest::Any)) => { - write!(f, "No Python interpreters found in {sources}") - } - Self::NoPythonInstallation(sources, Some(version)) => { - write!(f, "No Python {version} interpreters found in {sources}") - } - Self::NoMatchingVersion(sources, VersionRequest::Any) => { - write!(f, "No Python interpreter found in {sources}") - } - Self::NoMatchingVersion(sources, version) => { - write!(f, "No interpreter found for Python {version} in {sources}") + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let sources = match self.environment_preference { + EnvironmentPreference::Any => { + format!("virtual environments or {}", self.toolchain_preference) } - Self::NoMatchingImplementation(sources, implementation) => { - write!( - f, - "No interpreter found for {} in {sources}", - implementation.pretty() - ) - } - Self::NoMatchingImplementationVersion(sources, implementation, version) => { - write!( - f, - "No interpreter found for {} {version} in {sources}", - implementation.pretty() - ) - } - Self::NoMatchingKey(sources, key) => { - write!(f, "No interpreter found key {key} in {sources}") - } - Self::FileNotFound(path) => write!( - f, - "Requested interpreter path `{}` does not exist", - path.user_display() - ), - Self::DirectoryNotFound(path) => write!( - f, - "Requested interpreter directory `{}` does not exist", - path.user_display() - ), - Self::ExecutableNotFoundInDirectory(directory, executable) => { - write!( - f, - "Interpreter directory `{}` does not contain Python executable at `{}`", - directory.user_display(), - executable.user_display_from(directory) - ) + EnvironmentPreference::ExplicitSystem => { + if self.request.is_explicit_system() { + "virtual or system environment".to_string() + } else { + "virtual environment".to_string() + } } - Self::ExecutableNotFoundInSearchPath(name) => { - write!(f, "Requested Python executable `{name}` not found in PATH") + EnvironmentPreference::OnlySystem => self.toolchain_preference.to_string(), + EnvironmentPreference::OnlyVirtual => "virtual environments".to_string(), + }; + match self.request { + ToolchainRequest::Any => { + write!(f, "No interpreter found in {sources}") } - Self::FileNotExecutable(path) => { - write!( - f, - "Python interpreter at `{}` is not executable", - path.user_display() - ) + _ => { + write!(f, "No interpreter found for {} in {sources}", self.request) } } } diff --git a/crates/uv-toolchain/src/environment.rs b/crates/uv-toolchain/src/environment.rs index 8830f5b25978..1c23ee4e6d61 100644 --- a/crates/uv-toolchain/src/environment.rs +++ b/crates/uv-toolchain/src/environment.rs @@ -1,5 +1,6 @@ use std::borrow::Cow; use std::env; +use std::fmt; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -10,8 +11,8 @@ use crate::discovery::find_toolchain; use crate::toolchain::Toolchain; use crate::virtualenv::{virtualenv_python_executable, PyVenvConfiguration}; use crate::{ - EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainPreference, - ToolchainRequest, + EnvironmentPreference, Error, Interpreter, Prefix, Target, ToolchainNotFound, + ToolchainPreference, ToolchainRequest, }; /// A Python environment, consisting of a Python [`Interpreter`] and its associated paths. @@ -24,6 +25,50 @@ struct PythonEnvironmentShared { interpreter: Interpreter, } +/// The result of failed environment discovery. +/// +/// Generally this is cast from [`ToolchainNotFound`] by [`PythonEnvironment::find`]. +#[derive(Clone, Debug, Error)] +pub struct EnvironmentNotFound { + request: ToolchainRequest, + preference: EnvironmentPreference, +} + +impl From for EnvironmentNotFound { + fn from(value: ToolchainNotFound) -> Self { + Self { + request: value.request, + preference: value.environment_preference, + } + } +} + +impl fmt::Display for EnvironmentNotFound { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let environment = match self.preference { + EnvironmentPreference::Any => "virtual or system environment", + EnvironmentPreference::ExplicitSystem => { + if self.request.is_explicit_system() { + "virtual or system environment" + } else { + // TODO(zanieb): We could add a hint to use the `--system` flag here + "virtual environment" + } + } + EnvironmentPreference::OnlySystem => "system environment", + EnvironmentPreference::OnlyVirtual => "virtual environment", + }; + match self.request { + ToolchainRequest::Any => { + write!(f, "No {environment} found") + } + _ => { + write!(f, "No {environment} found for {}", self.request) + } + } + } +} + impl PythonEnvironment { /// Find a [`PythonEnvironment`] matching the given request and preference. /// @@ -34,13 +79,16 @@ impl PythonEnvironment { preference: EnvironmentPreference, cache: &Cache, ) -> Result { - let toolchain = find_toolchain( + let toolchain = match find_toolchain( request, preference, // Ignore managed toolchains when looking for environments ToolchainPreference::OnlySystem, cache, - )??; + )? { + Ok(toolchain) => toolchain, + Err(err) => return Err(EnvironmentNotFound::from(err).into()), + }; Ok(Self::from_toolchain(toolchain)) } @@ -49,9 +97,10 @@ impl PythonEnvironment { let venv = match fs_err::canonicalize(root.as_ref()) { Ok(venv) => venv, Err(err) if err.kind() == std::io::ErrorKind::NotFound => { - return Err(Error::NotFound( - crate::ToolchainNotFound::DirectoryNotFound(root.as_ref().to_path_buf()), - )); + return Err(Error::MissingEnvironment(EnvironmentNotFound { + preference: EnvironmentPreference::Any, + request: ToolchainRequest::Directory(root.as_ref().to_owned()), + })); } Err(err) => return Err(Error::Discovery(err.into())), }; diff --git a/crates/uv-toolchain/src/lib.rs b/crates/uv-toolchain/src/lib.rs index eb48d5965c7e..e36b47cda7f3 100644 --- a/crates/uv-toolchain/src/lib.rs +++ b/crates/uv-toolchain/src/lib.rs @@ -70,7 +70,10 @@ pub enum Error { KeyError(#[from] toolchain::ToolchainKeyError), #[error(transparent)] - NotFound(#[from] ToolchainNotFound), + MissingToolchain(#[from] ToolchainNotFound), + + #[error(transparent)] + MissingEnvironment(#[from] environment::EnvironmentNotFound), } // The mock interpreters are not valid on Windows so we don't have unit test coverage there @@ -99,7 +102,7 @@ mod tests { use crate::{ implementation::ImplementationName, managed::InstalledToolchains, toolchain::Toolchain, virtualenv::virtualenv_python_executable, PythonVersion, ToolchainNotFound, - ToolchainRequest, ToolchainSource, VersionRequest, + ToolchainRequest, ToolchainSource, }; struct TestContext { @@ -410,7 +413,7 @@ mod tests { ) }); assert!( - matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))), + matches!(result, Ok(Err(ToolchainNotFound { .. }))), "With an empty path, no Python installation should be detected got {result:?}" ); @@ -424,7 +427,7 @@ mod tests { ) }); assert!( - matches!(result, Ok(Err(ToolchainNotFound::NoPythonInstallation(..)))), + matches!(result, Ok(Err(ToolchainNotFound { .. }))), "With an unset path, no Python installation should be detected got {result:?}" ); @@ -450,7 +453,7 @@ mod tests { assert!( matches!( result, - Ok(Err(ToolchainNotFound::NoPythonInstallation(..))) + Ok(Err(ToolchainNotFound { .. })) ), "With an non-executable Python, no Python installation should be detected; got {result:?}" ); @@ -563,7 +566,7 @@ mod tests { ) })?; assert!( - matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))), + matches!(result, Err(ToolchainNotFound { .. })), // TODO(zanieb): We could improve the error handling to hint this to the user "If only Python 2 is available, we should not find a toolchain; got {result:?}" ); @@ -789,13 +792,7 @@ mod tests { ) })?; assert!( - matches!( - result, - Err(ToolchainNotFound::NoMatchingVersion( - _, - VersionRequest::MajorMinor(3, 9) - )) - ), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find a toolchain; got {result:?}" ); @@ -816,13 +813,7 @@ mod tests { ) })?; assert!( - matches!( - result, - Err(ToolchainNotFound::NoMatchingVersion( - _, - VersionRequest::MajorMinorPatch(3, 11, 9) - )) - ), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find a toolchain; got {result:?}" ); @@ -1298,14 +1289,7 @@ mod tests { ) })?; assert!( - matches!( - result, - Err(ToolchainNotFound::NoPythonInstallation( - // TODO(zanieb): We need the environment preference in the error - ToolchainPreference::OnlySystem, - None - )) - ), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find an toolchain; got {result:?}" ); @@ -1322,13 +1306,7 @@ mod tests { }, )?; assert!( - matches!( - result, - Err(ToolchainNotFound::NoMatchingVersion( - ToolchainPreference::OnlySystem, - VersionRequest::MajorMinorPatch(3, 12, 3) - )) - ), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find an toolchain; got {result:?}" ); Ok(()) @@ -1362,7 +1340,7 @@ mod tests { ) })?; assert!( - matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find it without a specific request" ); @@ -1375,7 +1353,7 @@ mod tests { ) })?; assert!( - matches!(result, Err(ToolchainNotFound::NoMatchingVersion(..))), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find it via a matching version request" ); @@ -1569,7 +1547,7 @@ mod tests { assert_eq!( toolchain.interpreter().python_full_version().to_string(), "3.10.0", - "We should prefer the requested directory over the system and active virtul toolchains" + "We should prefer the requested directory over the system and active virtual environments" ); Ok(()) @@ -1589,7 +1567,7 @@ mod tests { ) })?; assert!( - matches!(result, Err(ToolchainNotFound::FileNotFound(_))), + matches!(result, Err(ToolchainNotFound { .. })), "We should not find the file; got {result:?}" ); @@ -1639,7 +1617,7 @@ mod tests { ) })?; assert!( - matches!(result, Err(ToolchainNotFound::NoPythonInstallation(..))), + matches!(result, Err(ToolchainNotFound { .. })), "We should not the pypy interpreter if not named `python` or requested; got {result:?}" ); diff --git a/crates/uv-toolchain/src/toolchain.rs b/crates/uv-toolchain/src/toolchain.rs index 5edbb95c0730..dda22dd25763 100644 --- a/crates/uv-toolchain/src/toolchain.rs +++ b/crates/uv-toolchain/src/toolchain.rs @@ -86,7 +86,7 @@ impl Toolchain { // Perform a find first match Self::find(&request, environments, preference, cache) { Ok(venv) => Ok(venv), - Err(Error::NotFound(_)) + Err(Error::MissingToolchain(_)) if preference.allows_managed() && client_builder.connectivity.is_online() => { debug!("Requested Python not found, checking for available download..."); diff --git a/crates/uv/src/commands/project/mod.rs b/crates/uv/src/commands/project/mod.rs index 29b2bc4b437f..53ed68fc3664 100644 --- a/crates/uv/src/commands/project/mod.rs +++ b/crates/uv/src/commands/project/mod.rs @@ -173,7 +173,7 @@ pub(crate) async fn find_interpreter( return Ok(venv.into_interpreter()); } } - Err(uv_toolchain::Error::NotFound(_)) => {} + Err(uv_toolchain::Error::MissingEnvironment(_)) => {} Err(e) => return Err(e.into()), }; @@ -245,7 +245,7 @@ pub(crate) async fn init_environment( fs_err::remove_dir_all(venv.root()) .context("Failed to remove existing virtual environment")?; } - Err(uv_toolchain::Error::NotFound(_)) => {} + Err(uv_toolchain::Error::MissingEnvironment(_)) => {} Err(e) => return Err(e.into()), }; diff --git a/crates/uv/tests/lock.rs b/crates/uv/tests/lock.rs index a398ace4bdce..9dc4b59521e6 100644 --- a/crates/uv/tests/lock.rs +++ b/crates/uv/tests/lock.rs @@ -1993,6 +1993,8 @@ fn lock_requires_python() -> Result<()> { .filters() .into_iter() .chain(context.filters()) + // Platform independent message for the missing toolchain + .chain([(" or `py` launcher", "")]) .collect(); // Install from the lockfile. @@ -2005,7 +2007,7 @@ fn lock_requires_python() -> Result<()> { ----- stderr ----- warning: `uv sync` is experimental and may change without warning. Removing virtual environment at: .venv - error: No interpreter found for Python >=3.12 in system toolchains + error: No interpreter found for Python >=3.12 in system path "###); Ok(()) diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 77e9bf7eb1da..eea15e09b723 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -66,7 +66,7 @@ fn missing_venv() -> Result<()> { ----- stdout ----- ----- stderr ----- - error: No Python interpreters found in system toolchains + error: No virtual environment found "###); assert!(predicates::path::missing().eval(&context.venv)); diff --git a/crates/uv/tests/toolchain_find.rs b/crates/uv/tests/toolchain_find.rs index 07d51ca602c8..4d54d6566a17 100644 --- a/crates/uv/tests/toolchain_find.rs +++ b/crates/uv/tests/toolchain_find.rs @@ -10,14 +10,25 @@ fn toolchain_find() { let mut context: TestContext = TestContext::new_with_versions(&["3.11", "3.12"]); // No interpreters on the path - uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: No Python interpreters found in system toolchains - "###); + if cfg!(windows) { + uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found in system path or `py` launcher + "###); + } else { + uv_snapshot!(context.filters(), context.toolchain_find().env("UV_TEST_PYTHON_PATH", ""), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found in system path + "###); + } // We find the first interpreter on the path uv_snapshot!(context.filters(), context.toolchain_find(), @r###" @@ -94,15 +105,26 @@ fn toolchain_find() { ----- stderr ----- "###); - // Request PyPy - uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###" - success: false - exit_code: 2 - ----- stdout ----- - - ----- stderr ----- - error: No interpreter found for PyPy in system toolchains - "###); + // Request PyPy (which should be missing) + if cfg!(windows) { + uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for PyPy in system path or `py` launcher + "###); + } else { + uv_snapshot!(context.filters(), context.toolchain_find().arg("pypy"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: No interpreter found for PyPy in system path + "###); + } // Swap the order of the Python versions context.python_versions.reverse(); diff --git a/crates/uv/tests/venv.rs b/crates/uv/tests/venv.rs index 4bfa6119c866..1d302a0bf17e 100644 --- a/crates/uv/tests/venv.rs +++ b/crates/uv/tests/venv.rs @@ -264,7 +264,7 @@ fn create_venv_unknown_python_minor() { ----- stdout ----- ----- stderr ----- - × No interpreter found for Python 3.100 in system toolchains + × No interpreter found for Python 3.100 in system path or `py` launcher "### ); } else { @@ -274,7 +274,7 @@ fn create_venv_unknown_python_minor() { ----- stdout ----- ----- stderr ----- - × No interpreter found for Python 3.100 in system toolchains + × No interpreter found for Python 3.100 in system path "### ); } @@ -302,7 +302,7 @@ fn create_venv_unknown_python_patch() { ----- stdout ----- ----- stderr ----- - × No interpreter found for Python 3.12.100 in system toolchains + × No interpreter found for Python 3.12.100 in system path or `py` launcher "### ); } else { @@ -312,7 +312,7 @@ fn create_venv_unknown_python_patch() { ----- stdout ----- ----- stderr ----- - × No interpreter found for Python 3.12.100 in system toolchains + × No interpreter found for Python 3.12.100 in system path "### ); }