Store tool locks as standalone files

This commit is contained in:
Charlie Marsh
2026-05-27 18:49:18 +02:00
parent 934a5d881f
commit 78cfee666d
10 changed files with 382 additions and 226 deletions
+5
View File
@@ -663,6 +663,11 @@ impl Lock {
self
}
/// Return whether this lock was generated with the given [`ResolverManifest`].
pub fn matches_manifest(&self, manifest: &ResolverManifest) -> bool {
self.manifest == *manifest
}
/// Record the conflicting groups that were used to generate this lock.
#[must_use]
pub fn with_conflicts(mut self, conflicts: Conflicts) -> Self {
+61 -5
View File
@@ -16,6 +16,7 @@ use uv_installer::SitePackages;
use uv_normalize::{InvalidNameError, PackageName};
use uv_pep440::Version;
use uv_python::{BrokenLink, Interpreter, PythonEnvironment};
use uv_resolver::Lock;
use uv_state::{StateBucket, StateStore};
use uv_static::EnvVars;
@@ -70,6 +71,8 @@ pub enum Error {
ReceiptWrite(PathBuf, #[source] Box<toml_edit::ser::Error>),
#[error("Failed to read `uv-receipt.toml` at {0}")]
ReceiptRead(PathBuf, #[source] Box<toml::de::Error>),
#[error("Failed to update `uv.lock` at {0}")]
LockWrite(PathBuf, #[source] Box<toml_edit::ser::Error>),
#[error(transparent)]
VirtualEnvError(#[from] uv_virtualenv::Error),
#[error("Failed to read package entry points {0}")]
@@ -98,6 +101,7 @@ impl Error {
Self::VirtualEnvError(uv_virtualenv::Error::Io(err)) => Some(err),
Self::ReceiptWrite(_, _)
| Self::ReceiptRead(_, _)
| Self::LockWrite(_, _)
| Self::VirtualEnvError(_)
| Self::EntrypointRead(_)
| Self::NoExecutableDirectory
@@ -146,6 +150,32 @@ impl InstalledTools {
self.root.join(name.to_string())
}
/// Read the lock for a tool, if it has been generated and still matches its receipt.
fn read_lock(directory: &Path, tool: &Tool) -> Result<Option<Lock>, Error> {
let path = directory.join("uv.lock");
match fs_err::read_to_string(&path) {
Ok(contents) => match toml::from_str::<Lock>(&contents) {
Ok(lock) if tool.matches_lock(&lock, directory) => Ok(Some(lock)),
Ok(_) => {
debug!(
"Ignoring tool lock at `{}` because it does not match its receipt",
path.user_display()
);
Ok(None)
}
Err(err) => {
debug!(
"Ignoring invalid tool lock at `{}`: {err}",
path.user_display()
);
Ok(None)
}
},
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
}
}
/// Return the metadata for all installed tools.
///
/// If a tool is present, but is missing a receipt or the receipt is invalid, the tool will be
@@ -174,7 +204,13 @@ impl InstalledTools {
Err(err) => return Err(err.into()),
};
match ToolReceipt::from_string(contents) {
Ok(tool_receipt) => tools.push((name, Ok(tool_receipt.tool))),
Ok(tool_receipt) => {
let tool = tool_receipt.tool;
tools.push((
name,
Self::read_lock(&directory, &tool).map(|lock| tool.with_lock(lock)),
));
}
Err(err) => {
let err = Error::ReceiptRead(path, Box::new(err));
tools.push((name, Err(err)));
@@ -191,9 +227,14 @@ impl InstalledTools {
///
/// Note it is generally incorrect to use this without [`Self::acquire_lock`].
pub fn get_tool_receipt(&self, name: &PackageName) -> Result<Option<Tool>, Error> {
let path = self.tool_dir(name).join("uv-receipt.toml");
let directory = self.tool_dir(name);
let path = directory.join("uv-receipt.toml");
match ToolReceipt::from_path(&path) {
Ok(tool_receipt) => Ok(Some(tool_receipt.tool)),
Ok(tool_receipt) => {
let tool = tool_receipt.tool;
let lock = Self::read_lock(&directory, &tool)?;
Ok(Some(tool.with_lock(lock)))
}
Err(Error::Io(err)) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err),
}
@@ -215,6 +256,12 @@ impl InstalledTools {
///
/// Note it is generally incorrect to use this without [`Self::acquire_lock`].
pub fn add_tool_receipt(&self, name: &PackageName, tool: Tool) -> Result<(), Error> {
let lock_path = self.tool_dir(name).join("uv.lock");
let lock = tool
.lock()
.map(Lock::to_toml)
.transpose()
.map_err(|err| Error::LockWrite(lock_path.clone(), Box::new(err)))?;
let tool_receipt = ToolReceipt::from(tool);
let path = self.tool_dir(name).join("uv-receipt.toml");
@@ -227,8 +274,17 @@ impl InstalledTools {
.to_toml()
.map_err(|err| Error::ReceiptWrite(path.clone(), Box::new(err)))?;
// Save the modified `uv-receipt.toml`.
fs_err::write(&path, doc)?;
// Store the derived lock before making its authoritative receipt visible.
match lock {
Some(lock) => uv_fs::write_atomic_sync(&lock_path, lock)?,
None => match fs_err::remove_file(&lock_path) {
Ok(()) => (),
Err(err) if err.kind() == io::ErrorKind::NotFound => (),
Err(err) => return Err(err.into()),
},
}
uv_fs::write_atomic_sync(&path, doc)?;
Ok(())
}
+3 -20
View File
@@ -1,7 +1,6 @@
use std::path::Path;
use serde::Deserialize;
use uv_resolver::Lock;
use crate::Tool;
@@ -19,14 +18,8 @@ pub struct ToolReceipt {
impl ToolReceipt {
/// Parse a [`ToolReceipt`] from a raw TOML string.
pub(crate) fn from_string(raw: String) -> Result<Self, toml::de::Error> {
let mut receipt: Self = toml::from_str(&raw)?;
let document: toml::Table = toml::from_str(&raw)?;
if document.contains_key("version") {
let lock: Lock = toml::from_str(&raw)?;
receipt.tool = receipt.tool.with_lock(Some(lock));
}
receipt.raw = raw;
Ok(receipt)
let tool = toml::from_str(&raw)?;
Ok(Self { raw, ..tool })
}
/// Read a [`ToolReceipt`] from the given path.
@@ -42,17 +35,7 @@ impl ToolReceipt {
pub(crate) fn to_toml(&self) -> Result<String, toml_edit::ser::Error> {
// We construct a TOML document manually instead of going through Serde to enable
// the use of inline tables.
let mut doc = if let Some(lock) = self.tool.lock() {
lock.to_toml()?
.parse::<toml_edit::DocumentMut>()
.map_err(|err| {
toml_edit::ser::Error::Custom(format!(
"Failed to parse embedded tool lock: {err}"
))
})?
} else {
toml_edit::DocumentMut::new()
};
let mut doc = toml_edit::DocumentMut::new();
doc.insert("tool", toml_edit::Item::Table(self.tool.to_toml()?));
Ok(doc.to_string())
+20 -4
View File
@@ -1,15 +1,15 @@
use std::fmt::{self, Display, Formatter};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use serde::Deserialize;
use toml_edit::{Array, Item, Table, Value, value};
use uv_distribution_types::Requirement;
use uv_distribution_types::{Requirement, StaticMetadata};
use uv_fs::{PortablePath, Simplified};
use uv_normalize::PackageName;
use uv_normalize::{GroupName, PackageName};
use uv_pypi_types::VerbatimParsedUrl;
use uv_python::PythonRequest;
use uv_resolver::Lock;
use uv_resolver::{Lock, ResolverManifest};
use uv_settings::{ToolOptions, ToolOptionsWire};
/// A tool entry.
@@ -213,6 +213,22 @@ impl Tool {
Self { lock, ..self }
}
/// Return whether a standalone [`Lock`] was generated for this receipt.
pub(crate) fn matches_lock(&self, lock: &Lock, directory: &Path) -> bool {
ResolverManifest::new(
std::iter::empty::<PackageName>(),
self.requirements.iter().cloned(),
self.constraints.iter().cloned(),
self.overrides.iter().cloned(),
self.excludes.iter().cloned(),
self.build_constraints.iter().cloned(),
std::iter::empty::<(GroupName, Vec<Requirement>)>(),
std::iter::empty::<StaticMetadata>(),
)
.relative_to(directory)
.is_ok_and(|manifest| lock.matches_manifest(&manifest))
}
/// Returns the TOML table for this tool.
pub(crate) fn to_toml(&self) -> Result<Table, toml_edit::ser::Error> {
let mut table = Table::new();
+8 -8
View File
@@ -290,8 +290,8 @@ pub(crate) fn normalize_tool_local_requirements(
.collect()
}
/// Build the lock manifest for a tool receipt.
pub(crate) fn tool_receipt_manifest(
/// Build the lock manifest for a tool environment.
pub(crate) fn tool_lock_manifest(
requirements: &[Requirement],
constraints: &[Requirement],
overrides: &[Requirement],
@@ -310,8 +310,8 @@ pub(crate) fn tool_receipt_manifest(
)
}
/// Build the receipt lock for a tool environment.
pub(crate) fn tool_receipt_lock(
/// Build the lock for a tool environment.
pub(crate) fn tool_lock(
root: &Path,
resolution: &ResolverOutput,
manifest: &ResolverManifest,
@@ -320,19 +320,19 @@ pub(crate) fn tool_receipt_lock(
Ok(lock) => match manifest.clone().relative_to(root) {
Ok(manifest) => Some(lock.with_manifest(manifest)),
Err(err) => {
debug!("Failed to relativize tool receipt lock manifest: {err}");
debug!("Failed to relativize tool lock manifest: {err}");
None
}
},
Err(err) => {
debug!("Failed to build tool receipt lock: {err}");
debug!("Failed to build tool lock: {err}");
None
}
}
}
/// Build an environment specification for a tool, preferring versions from the existing receipt
/// lock when available, then falling back to the installed environment.
/// Build an environment specification for a tool, preferring versions from its existing lock when
/// available, then falling back to the installed environment.
pub(crate) fn tool_environment_spec<'lock>(
requirements: RequirementsSpecification,
tool: Option<&'lock Tool>,
+19 -22
View File
@@ -17,7 +17,6 @@ use uv_distribution_types::{
ExtraBuildRequires, IndexCapabilities, NameRequirementSpecification, Requirement,
RequirementSource, Resolution, UnresolvedRequirementSpecification,
};
use uv_fs::CWD;
use uv_installer::{InstallationStrategy, Planner, SitePackages};
use uv_normalize::PackageName;
use uv_pep440::{VersionSpecifier, VersionSpecifiers};
@@ -48,19 +47,18 @@ use crate::commands::project::{
};
use crate::commands::tool::common::{
ToolPython, finalize_tool_install, normalize_tool_local_requirements, refine_interpreter,
remove_entrypoints, tool_environment_spec, tool_receipt_lock, tool_receipt_manifest,
remove_entrypoints, tool_environment_spec, tool_lock, tool_lock_manifest,
};
use crate::commands::tool::{Target, ToolRequest};
use crate::commands::{diagnostics, reporters::PythonDownloadReporter};
use crate::printer::Printer;
use crate::settings::{ResolverInstallerSettings, ResolverSettings};
/// An [`Installable`] adapter for a tool receipt [`Lock`].
/// An [`Installable`] adapter for a tool [`Lock`].
///
/// Tools embed a lock in `uv-receipt.toml`, but they are not modeled as workspace or project
/// install targets. This adapter lets tool installation reuse [`Installable::to_resolution`] to
/// derive a [`Resolution`] from the embedded lock when checking whether an existing environment is
/// already up-to-date.
/// Tools store a `uv.lock`, but they are not modeled as workspace or project install targets. This
/// adapter lets tool installation reuse [`Installable::to_resolution`] to derive a [`Resolution`]
/// from the lock when checking whether an existing environment is already up-to-date.
struct ToolLockInstallTarget<'lock> {
install_path: &'lock Path,
lock: &'lock Lock,
@@ -68,7 +66,7 @@ struct ToolLockInstallTarget<'lock> {
}
impl<'lock> ToolLockInstallTarget<'lock> {
/// Create a [`ToolLockInstallTarget`] for a tool environment and its embedded [`Lock`].
/// Create a [`ToolLockInstallTarget`] for a tool environment and its [`Lock`].
fn new(
install_path: &'lock Path,
lock: &'lock Lock,
@@ -507,7 +505,7 @@ pub(crate) async fn install(
// Convert to tool options.
let options = ToolOptions::from(options);
let receipt_manifest = tool_receipt_manifest(
let lock_manifest = tool_lock_manifest(
&requirements,
&constraints,
&overrides,
@@ -643,8 +641,7 @@ pub(crate) async fn install(
)?
.is_empty()
} else {
// Force legacy receipts through the update path so they get rewritten with an
// embedded lock.
// Force tools without locks through the update path so a lock gets generated.
false
};
if already_installed {
@@ -693,7 +690,7 @@ pub(crate) async fn install(
// This lets us confirm the environment is valid before removing an existing install. However,
// entrypoints always contain an absolute path to the relevant Python interpreter, which would
// be invalidated by moving the environment.
let (environment, receipt_lock) = if let Some(environment) = existing_environment {
let (environment, tool_lock) = if let Some(environment) = existing_environment {
let update = match update_environment(
environment.into_environment(),
spec.clone(),
@@ -734,13 +731,13 @@ pub(crate) async fn install(
remove_entrypoints(existing_receipt);
}
let receipt_lock = if let Some(tool_receipt) = existing_tool_receipt.as_ref() {
let tool_lock = if let Some(tool_receipt) = existing_tool_receipt.as_ref() {
let site_packages = if tool_receipt.lock().is_none() {
match SitePackages::from_environment(&update.environment) {
Ok(site_packages) => Some(site_packages),
Err(err) => {
debug!(
"Failed to read tool environment site-packages while rebuilding receipt lock after update: {err}"
"Failed to read tool environment site-packages while rebuilding lock after update: {err}"
);
None
}
@@ -772,13 +769,13 @@ pub(crate) async fn install(
)
.await
{
Ok(resolution) => tool_receipt_lock(
Ok(resolution) => tool_lock(
&installed_tools.tool_dir(package_name),
&resolution,
&receipt_manifest,
&lock_manifest,
),
Err(err) => {
debug!("Failed to rebuild tool receipt lock after update: {err}");
debug!("Failed to rebuild tool lock after update: {err}");
None
}
}
@@ -786,7 +783,7 @@ pub(crate) async fn install(
None
};
(update.environment, receipt_lock)
(update.environment, tool_lock)
} else {
let spec = EnvironmentSpecification::from(spec);
@@ -882,10 +879,10 @@ pub(crate) async fn install(
};
let environment = installed_tools.create_environment(package_name, interpreter)?;
let receipt_lock = tool_receipt_lock(
let tool_lock = tool_lock(
&installed_tools.tool_dir(package_name),
&resolution,
&receipt_manifest,
&lock_manifest,
);
let resolution: Resolution = resolution.into();
@@ -917,7 +914,7 @@ pub(crate) async fn install(
debug!("Failed to sync environment; removing `{}`", package_name);
let _ = installed_tools.remove_environment(package_name);
}) {
Ok(environment) => (environment, receipt_lock),
Ok(environment) => (environment, tool_lock),
Err(ProjectError::Operation(err)) => {
return diagnostics::OperationDiagnostic::with_system_certs(
client_builder.system_certs(),
@@ -947,7 +944,7 @@ pub(crate) async fn install(
overrides,
excludes,
build_constraints,
receipt_lock,
tool_lock,
printer,
)?;
+10 -22
View File
@@ -34,8 +34,8 @@ use crate::commands::pip::{operations::Modifications, resolution_tags};
use crate::commands::project::{PlatformState, resolve_environment, sync_environment};
use crate::commands::reporters::PythonDownloadReporter;
use crate::commands::tool::common::{
normalize_tool_local_requirements, remove_entrypoints, tool_environment_spec,
tool_receipt_lock, tool_receipt_manifest,
normalize_tool_local_requirements, remove_entrypoints, tool_environment_spec, tool_lock,
tool_lock_manifest,
};
use crate::commands::{ExitStatus, conjunction, tool::common::finalize_tool_install};
use crate::printer::Printer;
@@ -329,7 +329,7 @@ async fn upgrade_tool(
)
.into_iter(),
);
let receipt_manifest = tool_receipt_manifest(
let lock_manifest = tool_lock_manifest(
existing_tool_receipt.requirements(),
existing_tool_receipt.constraints(),
existing_tool_receipt.overrides(),
@@ -358,7 +358,7 @@ async fn upgrade_tool(
// Check if we need to create a new environment — if so, resolve it first, then
// install the requested tool
let (environment, outcome, receipt_lock) = if let Some(interpreter) =
let (environment, outcome, tool_lock) = if let Some(interpreter) =
interpreter.filter(|interpreter| !environment.environment().uses(interpreter))
{
// If we're using a new interpreter, re-create the environment for each tool.
@@ -386,11 +386,7 @@ async fn upgrade_tool(
.await?;
let environment = installed_tools.create_environment(name, interpreter.clone())?;
let receipt_lock = tool_receipt_lock(
&installed_tools.tool_dir(name),
&resolution,
&receipt_manifest,
);
let tool_lock = tool_lock(&installed_tools.tool_dir(name), &resolution, &lock_manifest);
let environment = sync_environment(
environment,
@@ -409,11 +405,7 @@ async fn upgrade_tool(
)
.await?;
(
environment,
UpgradeOutcome::UpgradeEnvironment,
receipt_lock,
)
(environment, UpgradeOutcome::UpgradeEnvironment, tool_lock)
} else {
let resolution = resolve_environment(
tool_environment_spec(
@@ -438,11 +430,7 @@ async fn upgrade_tool(
)
.await?;
let receipt_lock = tool_receipt_lock(
&installed_tools.tool_dir(name),
&resolution,
&receipt_manifest,
);
let tool_lock = tool_lock(&installed_tools.tool_dir(name), &resolution, &lock_manifest);
let resolution = Resolution::from(resolution);
let ResolverInstallerSettings {
@@ -514,7 +502,7 @@ async fn upgrade_tool(
.await?
};
(environment, outcome, receipt_lock)
(environment, outcome, tool_lock)
};
if matches!(
@@ -545,7 +533,7 @@ async fn upgrade_tool(
existing_tool_receipt.overrides().to_vec(),
existing_tool_receipt.excludes().to_vec(),
existing_tool_receipt.build_constraints().to_vec(),
receipt_lock,
tool_lock,
printer,
)?;
} else {
@@ -554,7 +542,7 @@ async fn upgrade_tool(
existing_tool_receipt
.clone()
.with_options(ToolOptions::from(options))
.with_lock(receipt_lock),
.with_lock(tool_lock),
)?;
}
+242 -130
View File
@@ -4,7 +4,7 @@ use std::collections::BTreeSet;
use std::ffi::OsString;
use std::process::Command;
use anyhow::{Context, Result};
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
#[cfg(feature = "test-git")]
use assert_fs::fixture::ChildPath;
@@ -85,6 +85,10 @@ fn tool_install() {
.child("black")
.child("uv-receipt.toml")
.assert(predicate::path::exists());
tool_dir
.child("black")
.child("uv.lock")
.assert(predicate::path::exists());
let executable = bin_dir.child(format!("black{}", std::env::consts::EXE_SUFFIX));
assert!(executable.exists());
@@ -113,8 +117,8 @@ fn tool_install() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -189,7 +193,8 @@ fn tool_install() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -277,7 +282,7 @@ fn tool_install() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -372,7 +377,8 @@ fn tool_install() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10", size = 226669, upload-time = "2023-10-24T20:57:47.326Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "flask" }]
entrypoints = [
@@ -409,7 +415,7 @@ fn tool_install_relative_exclude_newer_receipt_preserves_span() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -485,7 +491,8 @@ fn tool_install_relative_exclude_newer_receipt_preserves_span() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.2.0" }]
entrypoints = [
@@ -2018,8 +2025,8 @@ fn tool_install_with_compatible_build_constraints() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.9.[X]"
@@ -2136,7 +2143,8 @@ fn tool_install_with_compatible_build_constraints() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/f3/936e209267d6ef7510322191003885de524fc48d1b43269810cd589ceaf5/typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a", size = 34698, upload-time = "2024-04-05T12:35:44.388Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -2327,8 +2335,8 @@ fn tool_install_version() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -2403,7 +2411,8 @@ fn tool_install_version() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.2.0" }]
entrypoints = [
@@ -2488,8 +2497,8 @@ fn tool_install_editable() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -2517,7 +2526,8 @@ fn tool_install_editable() {
{ name = "uvloop", marker = "extra == 'uvloop'", specifier = ">=0.15.2" },
]
provides-extras = ["colorama", "uvloop", "d", "jupyter", "dev"]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", editable = "[WORKSPACE]/test/packages/black_editable" }]
entrypoints = [
@@ -2559,8 +2569,8 @@ fn tool_install_editable() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -2635,7 +2645,8 @@ fn tool_install_editable() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -2679,8 +2690,8 @@ fn tool_install_editable() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -2755,7 +2766,8 @@ fn tool_install_editable() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.2.0" }]
entrypoints = [
@@ -3162,8 +3174,8 @@ fn tool_install_remove_on_empty() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3182,7 +3194,8 @@ fn tool_install_remove_on_empty() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "pyflakes" }]
entrypoints = [
@@ -3261,8 +3274,8 @@ fn tool_install_remove_on_empty() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3281,7 +3294,8 @@ fn tool_install_remove_on_empty() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "pyflakes" }]
entrypoints = [
@@ -3357,8 +3371,8 @@ fn tool_install_editable_from() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3386,7 +3400,8 @@ fn tool_install_editable_from() {
{ name = "uvloop", marker = "extra == 'uvloop'", specifier = ">=0.15.2" },
]
provides-extras = ["colorama", "uvloop", "d", "jupyter", "dev"]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", editable = "[WORKSPACE]/test/packages/black_editable" }]
entrypoints = [
@@ -3539,8 +3554,8 @@ fn tool_install_already_installed() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3615,7 +3630,8 @@ fn tool_install_already_installed() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -3652,8 +3668,8 @@ fn tool_install_already_installed() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should not have an additional tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should not have an additional tool lock or receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3728,7 +3744,8 @@ fn tool_install_already_installed() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -3742,9 +3759,9 @@ fn tool_install_already_installed() {
});
}
/// Test installing a tool with a valid legacy receipt that lacks a lock.
/// Test installing over a valid tool installation with an invalid lock.
#[test]
fn tool_install_migrates_lockless_receipt() -> Result<()> {
fn tool_install_migrates_invalid_lock() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_counts()
.with_filtered_exe_suffix();
@@ -3763,17 +3780,13 @@ fn tool_install_migrates_lockless_receipt() -> Result<()> {
.success();
let receipt_path = tool_dir.child("black").child("uv-receipt.toml");
fs_err::write(tool_dir.child("black").child("uv.lock"), "[invalid")?;
let receipt = fs_err::read_to_string(&receipt_path)?;
let (_, tool_receipt) = receipt
.split_once("\n[tool]\n")
.context("expected the tool receipt to contain a tool section")?;
receipt_path.write_str(&format!("[tool]\n{tool_receipt}"))?;
let lockless_receipt = fs_err::read_to_string(&receipt_path)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(lockless_receipt, @r#"
assert_snapshot!(receipt, @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -3786,6 +3799,19 @@ fn tool_install_migrates_lockless_receipt() -> Result<()> {
"#);
});
uv_snapshot!(context.filters(), context.tool_list()
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
black v24.3.0
- black
- blackd
----- stderr -----
");
uv_snapshot!(context.filters(), context.tool_install()
.arg("--python-platform")
.arg("linux")
@@ -3803,11 +3829,10 @@ fn tool_install_migrates_lockless_receipt() -> Result<()> {
Installed 2 executables: black, blackd
");
let receipt = fs_err::read_to_string(&receipt_path)?;
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(receipt, @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -3882,7 +3907,8 @@ fn tool_install_migrates_lockless_receipt() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -3898,9 +3924,74 @@ fn tool_install_migrates_lockless_receipt() -> Result<()> {
Ok(())
}
/// Test migrating a legacy receipt without upgrading dependencies beyond the installed environment.
/// Test installing over a tool whose lock belongs to a different receipt.
#[test]
fn tool_install_migrates_lockless_receipt_with_installed_preferences() -> Result<()> {
fn tool_install_regenerates_mismatched_lock() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_counts()
.with_filtered_exe_suffix();
let tool_dir = context.temp_dir.child("tools");
let bin_dir = context.temp_dir.child("bin");
context
.tool_install()
.arg("--python-platform")
.arg("linux")
.arg("black")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str())
.assert()
.success();
let lock_path = tool_dir.child("black").child("uv.lock");
let lock = fs_err::read_to_string(&lock_path)?;
let mismatched_lock = lock.replacen(
r#"requirements = [{ name = "black" }]"#,
r#"requirements = [{ name = "flask" }]"#,
1,
);
assert_ne!(lock, mismatched_lock);
fs_err::write(&lock_path, mismatched_lock)?;
uv_snapshot!(context.filters(), context.tool_install()
.arg("--python-platform")
.arg("linux")
.arg("black")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Resolved [N] packages in [TIME]
Checked [N] packages in [TIME]
Installed 2 executables: black, blackd
");
uv_snapshot!(context.filters(), context.tool_install()
.arg("--python-platform")
.arg("linux")
.arg("black")
.env(EnvVars::UV_TOOL_DIR, tool_dir.as_os_str())
.env(EnvVars::XDG_BIN_HOME, bin_dir.as_os_str())
.env(EnvVars::PATH, bin_dir.as_os_str()), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
`black` is already installed
");
Ok(())
}
/// Test migrating a legacy tool installation without upgrading installed dependencies.
#[test]
fn tool_install_migrates_missing_lock_with_installed_preferences() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_counts()
.with_filtered_exe_suffix();
@@ -3920,12 +4011,7 @@ fn tool_install_migrates_lockless_receipt_with_installed_preferences() -> Result
.assert()
.success();
let receipt_path = tool_dir.child("black").child("uv-receipt.toml");
let receipt = fs_err::read_to_string(&receipt_path)?;
let (_, tool_receipt) = receipt
.split_once("\n[tool]\n")
.context("expected the tool receipt to contain a tool section")?;
receipt_path.write_str(&format!("[tool]\n{tool_receipt}"))?;
fs_err::remove_file(tool_dir.child("black").child("uv.lock"))?;
context
.tool_install()
@@ -4247,8 +4333,8 @@ fn tool_install_force() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -4267,7 +4353,8 @@ fn tool_install_force() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/d4/d7/f1b7db88d8e4417c5d47adad627a93547f44bdc9028372dbd2313f34a855/pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a", size = 62725, upload-time = "2024-01-05T00:28:45.903Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("pyflakes").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "pyflakes" }]
entrypoints = [
@@ -4881,8 +4968,8 @@ fn tool_install_git_lfs() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("test-lfs-repo").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("test-lfs-repo").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.13.[X]"
@@ -4897,7 +4984,8 @@ fn tool_install_git_lfs() {
name = "test-lfs-repo"
version = "0.1.0"
source = { git = "https://github.com/astral-sh/test-lfs-repo?lfs=true&rev=e282f5be233e3f1d44934164895a043fc534b8aa#e282f5be233e3f1d44934164895a043fc534b8aa" }
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("test-lfs-repo").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "test-lfs-repo", git = "https://github.com/astral-sh/test-lfs-repo?lfs=true&rev=e282f5be233e3f1d44934164895a043fc534b8aa" }]
entrypoints = [
@@ -5281,8 +5369,8 @@ fn tool_install_with_dependencies_from_script() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -5391,7 +5479,8 @@ fn tool_install_with_dependencies_from_script() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -5445,8 +5534,8 @@ fn tool_install_with_dependencies_from_script() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -5565,7 +5654,8 @@ fn tool_install_with_dependencies_from_script() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload-time = "2024-02-25T23:20:01.196Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -5628,8 +5718,8 @@ fn tool_install_requirements_txt() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -5716,7 +5806,8 @@ fn tool_install_requirements_txt() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -5762,8 +5853,8 @@ fn tool_install_requirements_txt() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -5850,7 +5941,8 @@ fn tool_install_requirements_txt() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -5915,8 +6007,8 @@ fn tool_install_requirements_txt_arguments() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -6003,7 +6095,8 @@ fn tool_install_requirements_txt_arguments() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "black" },
@@ -6125,8 +6218,8 @@ fn tool_install_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -6201,7 +6294,8 @@ fn tool_install_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.1.1" }]
entrypoints = [
@@ -6236,8 +6330,8 @@ fn tool_install_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -6312,7 +6406,8 @@ fn tool_install_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -6395,8 +6490,8 @@ fn tool_install_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -6471,7 +6566,8 @@ fn tool_install_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -6939,8 +7035,8 @@ fn tool_install_malformed_dist_info() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -6959,7 +7055,8 @@ fn tool_install_malformed_dist_info() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/97/8ab6fa1bbcb0a888f460c0a19c301f4cc4180573564ad7dd98b5ceca2ab6/executable_application-0.3.0-py3-none-any.whl", hash = "sha256:ca272aee7332e9d266663bc70037cd3ef1d74ffae40030eaf9ca46462dc8dcc6", size = 1719, upload-time = "2025-01-17T23:21:22.716Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
@@ -7040,8 +7137,8 @@ fn tool_install_settings() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7137,7 +7234,8 @@ fn tool_install_settings() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10", size = 226669, upload-time = "2023-10-24T20:57:47.326Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "flask", specifier = ">=3" }]
entrypoints = [
@@ -7171,8 +7269,8 @@ fn tool_install_settings() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7268,7 +7366,8 @@ fn tool_install_settings() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10", size = 226669, upload-time = "2023-10-24T20:57:47.326Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "flask", specifier = ">=3" }]
entrypoints = [
@@ -7309,8 +7408,8 @@ fn tool_install_settings() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7405,7 +7504,8 @@ fn tool_install_settings() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/c3/fc/254c3e9b5feb89ff5b9076a23218dafbc99c96ac5941e900b71206e6313b/werkzeug-3.0.1-py3-none-any.whl", hash = "sha256:90a285dc0e42ad56b34e696398b8122ee4c681833fb35b8334a095d82c56da10", size = 226669, upload-time = "2023-10-24T20:57:47.326Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("flask").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "flask", specifier = ">=3" }]
entrypoints = [
@@ -7456,7 +7556,7 @@ fn tool_install_at_version() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7531,7 +7631,8 @@ fn tool_install_at_version() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.1.0" }]
entrypoints = [
@@ -7601,7 +7702,7 @@ fn tool_install_at_latest() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7676,7 +7777,8 @@ fn tool_install_at_latest() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -7722,7 +7824,7 @@ fn tool_install_from_at_latest() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7741,7 +7843,8 @@ fn tool_install_from_at_latest() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/32/97/8ab6fa1bbcb0a888f460c0a19c301f4cc4180573564ad7dd98b5ceca2ab6/executable_application-0.3.0-py3-none-any.whl", hash = "sha256:ca272aee7332e9d266663bc70037cd3ef1d74ffae40030eaf9ca46462dc8dcc6", size = 1719, upload-time = "2025-01-17T23:21:22.716Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
@@ -7786,7 +7889,7 @@ fn tool_install_from_at_version() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7805,7 +7908,8 @@ fn tool_install_from_at_version() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/d6/d9e5e20e5fd52a2ff02c4dcd351a2a2fb1e1a159c5de355aded16bccadef/executable_application-0.2.0-py3-none-any.whl", hash = "sha256:2b26f00eb59ebe606697535aee0bfcf1f7ae0dfa7223703cd40cf1c31959d149", size = 1718, upload-time = "2025-01-17T23:21:08.354Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application", specifier = "==0.2.0" }]
entrypoints = [
@@ -7855,8 +7959,8 @@ fn tool_install_at_latest_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -7931,7 +8035,8 @@ fn tool_install_at_latest_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.1.1" }]
entrypoints = [
@@ -7966,8 +8071,8 @@ fn tool_install_at_latest_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8042,7 +8147,8 @@ fn tool_install_at_latest_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -8080,8 +8186,8 @@ fn tool_install_at_latest_upgrade() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8156,7 +8262,8 @@ fn tool_install_at_latest_upgrade() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -8215,8 +8322,8 @@ fn tool_install_constraints() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8292,7 +8399,8 @@ fn tool_install_constraints() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
constraints = [
@@ -8402,8 +8510,8 @@ fn tool_install_overrides() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8482,7 +8590,8 @@ fn tool_install_overrides() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
overrides = [
@@ -8660,8 +8769,8 @@ async fn tool_install_credentials() {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8680,7 +8789,8 @@ async fn tool_install_credentials() {
wheels = [
{ url = "http://[LOCALHOST]/basic-auth/files/packages/32/97/8ab6fa1bbcb0a888f460c0a19c301f4cc4180573564ad7dd98b5ceca2ab6/executable_application-0.3.0-py3-none-any.whl", hash = "sha256:ca272aee7332e9d266663bc70037cd3ef1d74ffae40030eaf9ca46462dc8dcc6", size = 1719, upload-time = "2025-01-17T23:21:22.716Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
@@ -8771,8 +8881,8 @@ async fn tool_install_default_credentials() -> Result<()> {
insta::with_settings!({
filters => context.filters(),
}, {
// We should have a tool receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
// We should have a tool lock and receipt
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -8791,7 +8901,8 @@ async fn tool_install_default_credentials() -> Result<()> {
wheels = [
{ url = "http://[LOCALHOST]/basic-auth/files/packages/32/97/8ab6fa1bbcb0a888f460c0a19c301f4cc4180573564ad7dd98b5ceca2ab6/executable_application-0.3.0-py3-none-any.whl", hash = "sha256:ca272aee7332e9d266663bc70037cd3ef1d74ffae40030eaf9ca46462dc8dcc6", size = 1719, upload-time = "2025-01-17T23:21:22.716Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [
@@ -8888,7 +8999,7 @@ fn tool_install_with_executables_from() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("ansible").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("ansible").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -9113,7 +9224,8 @@ fn tool_install_with_executables_from() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/d2/fc/e9ccf0521607bcd244aa0b3fbd574f71b65e9ce6a112c83af988bbbe2e23/resolvelib-1.0.1-py2.py3-none-any.whl", hash = "sha256:d2da45d1a8dfee81bdd591647783e340ef3bcb104b54c383f70d422ef5cc7dbf", size = 17194, upload-time = "2023-03-09T05:10:36.214Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("ansible").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [
{ name = "ansible", specifier = "==9.3.0" },
+5 -3
View File
@@ -362,11 +362,11 @@ fn tool_list_deprecated() -> Result<()> {
.assert()
.success();
// Ensure that we have a modern tool receipt.
// Ensure that we have modern tool metadata.
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -441,7 +441,8 @@ fn tool_list_deprecated() -> Result<()> {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black", specifier = "==24.2.0" }]
entrypoints = [
@@ -466,6 +467,7 @@ fn tool_list_deprecated() -> Result<()> {
]
"#,
)?;
fs::remove_file(tool_dir.join("black").join("uv.lock"))?;
// Ensure that we can still list the tool.
uv_snapshot!(context.filters(), context.tool_list()
+9 -12
View File
@@ -1,6 +1,6 @@
use std::process::Command;
use anyhow::{Context, Result};
use anyhow::Result;
use assert_cmd::assert::OutputAssertExt;
use assert_fs::prelude::*;
use indoc::indoc;
@@ -610,7 +610,7 @@ fn tool_upgrade_recomputes_relative_exclude_newer() {
insta::with_settings!({
filters => context.filters(),
}, {
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -686,7 +686,8 @@ fn tool_upgrade_recomputes_relative_exclude_newer() {
wheels = [
{ url = "https://files.pythonhosted.org/packages/55/72/4898c44ee9ea6f43396fbc23d9bfaf3d06e01b83698bdf2e4c919deceb7c/platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", size = 17717, upload-time = "2024-01-31T01:00:34.019Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("black").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "black" }]
entrypoints = [
@@ -702,7 +703,7 @@ fn tool_upgrade_recomputes_relative_exclude_newer() {
}
#[test]
fn tool_upgrade_migrates_lockless_receipt_with_installed_preferences() -> Result<()> {
fn tool_upgrade_migrates_missing_lock_with_installed_preferences() -> Result<()> {
let context = uv_test::test_context!("3.12")
.with_filtered_counts()
.with_filtered_exe_suffix();
@@ -722,12 +723,7 @@ fn tool_upgrade_migrates_lockless_receipt_with_installed_preferences() -> Result
.assert()
.success();
let receipt_path = tool_dir.child("black").child("uv-receipt.toml");
let receipt = fs_err::read_to_string(&receipt_path)?;
let (_, tool_receipt) = receipt
.split_once("\n[tool]\n")
.context("expected the tool receipt to contain a tool section")?;
receipt_path.write_str(&format!("[tool]\n{tool_receipt}"))?;
fs_err::remove_file(tool_dir.child("black").child("uv.lock"))?;
uv_snapshot!(context.filters(), context.tool_upgrade()
.arg("black")
@@ -1776,7 +1772,7 @@ async fn tool_upgrade_invalid_auth() -> Result<()> {
}, {
// Verify the receipt has `authenticate = "always"` (promoted from "auto" because the
// original URL had embedded credentials).
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv.lock")).unwrap(), @r#"
version = 1
revision = 3
requires-python = ">=3.12.[X]"
@@ -1795,7 +1791,8 @@ async fn tool_upgrade_invalid_auth() -> Result<()> {
wheels = [
{ url = "http://[LOCALHOST]/basic-auth/files/packages/32/97/8ab6fa1bbcb0a888f460c0a19c301f4cc4180573564ad7dd98b5ceca2ab6/executable_application-0.3.0-py3-none-any.whl", hash = "sha256:ca272aee7332e9d266663bc70037cd3ef1d74ffae40030eaf9ca46462dc8dcc6", size = 1719, upload-time = "2025-01-17T23:21:22.716Z" },
]
"#);
assert_snapshot!(fs_err::read_to_string(tool_dir.join("executable-application").join("uv-receipt.toml")).unwrap(), @r#"
[tool]
requirements = [{ name = "executable-application" }]
entrypoints = [