use anyhow::Result;
use assert_cmd::prelude::*;
use assert_fs::prelude::*;
use uv_cache::Cache;
use uv_static::EnvVars;
use uv_test::uv_snapshot;
#[test]
fn clean_all() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("typing-extensions\niniconfig")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
uv_snapshot!(context.with_filtered_counts().filters(), context.clean().arg("--verbose"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
Clearing cache at: [CACHE_DIR]/
Removed [N] files ([SIZE])
");
Ok(())
}
#[test]
fn clear_all_alias() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("typing-extensions\niniconfig")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
let mut command = context.command();
command.arg("cache").arg("clear").arg("--verbose");
uv_snapshot!(context.with_filtered_counts().filters(), command, @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
Clearing cache at: [CACHE_DIR]/
Removed [N] files ([SIZE])
");
Ok(())
}
#[tokio::test]
async fn clean_force() -> Result<()> {
let context = uv_test::test_context!("3.12").with_filtered_counts();
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("typing-extensions\niniconfig")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
uv_snapshot!(context.filters(), context.clean().arg("--verbose").arg("--force"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
Clearing cache at: [CACHE_DIR]/
Removed [N] files ([SIZE])
");
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
let _cache = uv_cache::Cache::from_path(context.cache_dir.path())
.with_exclusive_lock()
.await;
uv_snapshot!(context.filters(), context.clean().arg("--verbose").arg("--force"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Lock is busy for `[CACHE_DIR]/`
DEBUG Cache is currently in use, proceeding due to `--force`
Clearing cache at: [CACHE_DIR]/
Removed [N] files ([SIZE])
");
Ok(())
}
#[test]
fn clean_package_pypi() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio\niniconfig")?;
context
.pip_sync()
.arg("requirements.txt")
.assert()
.success();
let rkyv = context
.cache_dir
.child("simple-v21")
.child("pypi")
.child("iniconfig.rkyv");
assert!(
rkyv.exists(),
"Expected the `.rkyv` file to exist for `iniconfig`"
);
let filters: Vec<_> = context
.filters()
.into_iter()
.chain([
(
r"\[CACHE_DIR\](\\|\/)(.+)(\\|\/).*",
"[CACHE_DIR]/$2/[ENTRY]",
),
("Removed \\d+ files?", "Removed [N] files"),
])
.collect();
uv_snapshot!(&filters, context.clean().arg("--verbose").arg("iniconfig"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY]
Removed [N] files ([SIZE])
");
assert!(
!rkyv.exists(),
"Expected the `.rkyv` file to be removed for `iniconfig`"
);
uv_snapshot!(&filters, context.prune().arg("--verbose"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
Pruning cache at: [CACHE_DIR]/
No unused entries found
");
Ok(())
}
#[test]
fn clean_package_index() -> Result<()> {
let context = uv_test::test_context!("3.12");
let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str("anyio\niniconfig")?;
context
.pip_sync()
.arg("requirements.txt")
.arg("--index-url")
.arg("https://test.pypi.org/simple")
.assert()
.success();
let rkyv = context
.cache_dir
.child("simple-v21")
.child("index")
.child("e8208120cae3ba69")
.child("iniconfig.rkyv");
assert!(
rkyv.exists(),
"Expected the `.rkyv` file to exist for `iniconfig`"
);
let filters: Vec<_> = context
.filters()
.into_iter()
.chain([
(
r"\[CACHE_DIR\](\\|\/)(.+)(\\|\/).*",
"[CACHE_DIR]/$2/[ENTRY]",
),
("Removed \\d+ files?", "Removed [N] files"),
])
.collect();
uv_snapshot!(&filters, context.clean().arg("--verbose").arg("iniconfig"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
DEBUG Removing dangling cache entry: [CACHE_DIR]/archive-v0/[ENTRY]
Removed [N] files ([SIZE])
");
assert!(
!rkyv.exists(),
"Expected the `.rkyv` file to be removed for `iniconfig`"
);
Ok(())
}
#[cfg(unix)]
#[test]
fn clean_package_does_not_follow_symlinks() -> Result<()> {
let context = uv_test::test_context!("3.12");
let victim_dir = context.temp_dir.child("victim");
let archive_entry = context.cache_dir.child("archive-v0").child("archive");
let package_entry = context
.cache_dir
.child("wheels-v6")
.child("pypi")
.child("demo");
victim_dir.create_dir_all()?;
victim_dir.child("payload.txt").write_str("payload")?;
archive_entry.create_dir_all()?;
archive_entry.child("payload.txt").write_str("payload")?;
package_entry.create_dir_all()?;
fs_err::os::unix::fs::symlink(&victim_dir, package_entry.join("escape"))?;
fs_err::os::unix::fs::symlink(&archive_entry, package_entry.join("archive"))?;
uv_snapshot!(context.filters(), context.clean().arg("demo"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
Removed 3 files ([SIZE])
");
assert!(victim_dir.is_dir());
assert!(victim_dir.child("payload.txt").is_file());
assert!(fs_err::symlink_metadata(package_entry).is_err());
assert!(fs_err::symlink_metadata(archive_entry).is_err());
Ok(())
}
#[tokio::test]
async fn cache_timeout() {
let context = uv_test::test_context!("3.12");
let _cache = Cache::from_path(context.cache_dir.path())
.with_exclusive_lock()
.await;
uv_snapshot!(context.filters(), context.clean().env(EnvVars::UV_LOCK_TIMEOUT, "1"), @"
success: false
exit_code: 2
----- stdout -----
----- stderr -----
Cache is currently in-use, waiting for other uv processes to finish (use `--force` to override)
error: Timeout ([TIME]) when waiting for lock on `[CACHE_DIR]/` at `[CACHE_DIR]/.lock`, is another uv process running? You can set `UV_LOCK_TIMEOUT` to increase the timeout.
");
}
#[cfg(windows)]
#[test]
fn clean_handles_verbatim_paths() -> Result<()> {
let context = uv_test::test_context!("3.12");
fs_err::remove_dir_all(&context.cache_dir)?;
let uwsgi_shard = context
.cache_dir
.child("sdists-v9")
.child("pypi")
.child("uwsgi")
.child("2.0.31")
.child("QxDIp0qpjbsWjWURKmegK")
.child("src")
.child("core");
uwsgi_shard.create_dir_all()?;
let invalid_path = uwsgi_shard.child("logging.").to_path_buf();
let invalid_file = uv_fs::verbatim_path(invalid_path.as_path());
fs_err::write(&invalid_file, b"")?;
let remove_err = fs_err::remove_file(&invalid_path).expect_err("expected to fail");
assert_eq!(remove_err.kind(), std::io::ErrorKind::NotFound);
uv_snapshot!(context.filters(), context.clean().arg("--verbose"), @"
success: true
exit_code: 0
----- stdout -----
----- stderr -----
DEBUG Searching for user configuration in: `[UV_USER_CONFIG_DIR]/uv.toml`
DEBUG uv [VERSION] ([COMMIT] DATE)
Clearing cache at: [CACHE_DIR]/
Removed 2 files
");
Ok(())
}