diff --git a/crates/install-wheel-rs/src/lib.rs b/crates/install-wheel-rs/src/lib.rs index 3ad2053ad..ae07fdc43 100644 --- a/crates/install-wheel-rs/src/lib.rs +++ b/crates/install-wheel-rs/src/lib.rs @@ -80,6 +80,8 @@ pub enum Error { DirectUrlJson(#[from] serde_json::Error), #[error("No .dist-info directory found")] MissingDistInfo, + #[error("Cannot uninstall package; RECORD file not found at: {0}")] + MissingRecord(PathBuf), #[error("Multiple .dist-info directories found: {0}")] MultipleDistInfo(String), #[error("Invalid wheel size")] diff --git a/crates/install-wheel-rs/src/uninstall.rs b/crates/install-wheel-rs/src/uninstall.rs index 61d5e428a..3d7585092 100644 --- a/crates/install-wheel-rs/src/uninstall.rs +++ b/crates/install-wheel-rs/src/uninstall.rs @@ -2,7 +2,6 @@ use std::collections::BTreeSet; use std::path::{Component, Path, PathBuf}; use fs_err as fs; -use fs_err::File; use tracing::debug; use crate::{read_record_file, Error}; @@ -16,7 +15,14 @@ pub fn uninstall_wheel(dist_info: &Path) -> Result { }; // Read the RECORD file. - let mut record_file = File::open(dist_info.join("RECORD"))?; + let record_path = dist_info.join("RECORD"); + let mut record_file = match fs::File::open(&record_path) { + Ok(record_file) => record_file, + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + return Err(Error::MissingRecord(record_path)); + } + Err(err) => return Err(err.into()), + }; let record = read_record_file(&mut record_file)?; let mut file_count = 0usize; diff --git a/crates/puffin-cli/tests/pip_uninstall.rs b/crates/puffin-cli/tests/pip_uninstall.rs index ab9054862..8e7dc4baa 100644 --- a/crates/puffin-cli/tests/pip_uninstall.rs +++ b/crates/puffin-cli/tests/pip_uninstall.rs @@ -5,6 +5,7 @@ use assert_cmd::prelude::*; use assert_fs::prelude::*; use insta_cmd::{assert_cmd_snapshot, get_cargo_bin}; +use crate::common::create_venv_py312; use common::{BIN_NAME, INSTA_FILTERS}; mod common; @@ -283,6 +284,69 @@ fn uninstall() -> Result<()> { Ok(()) } +#[test] +fn missing_record() -> Result<()> { + let temp_dir = assert_fs::TempDir::new()?; + let cache_dir = assert_fs::TempDir::new()?; + let venv = create_venv_py312(&temp_dir, &cache_dir); + + let requirements_txt = temp_dir.child("requirements.txt"); + requirements_txt.touch()?; + requirements_txt.write_str("MarkupSafe==2.1.3")?; + + Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-sync") + .arg("requirements.txt") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir) + .assert() + .success(); + + Command::new(venv.join("bin").join("python")) + .arg("-c") + .arg("import markupsafe") + .current_dir(&temp_dir) + .assert() + .success(); + + // Delete the RECORD file. + let dist_info = venv + .join("lib") + .join("python3.12") + .join("site-packages") + .join("MarkupSafe-2.1.3.dist-info"); + std::fs::remove_file(dist_info.join("RECORD"))?; + + let mut filters = INSTA_FILTERS.to_vec(); + filters.push(( + "RECORD file not found at: .*/.venv", + "RECORD file not found at: [VENV_PATH]", + )); + + insta::with_settings!({ + filters => filters, + }, { + assert_cmd_snapshot!(Command::new(get_cargo_bin(BIN_NAME)) + .arg("pip-uninstall") + .arg("MarkupSafe") + .arg("--cache-dir") + .arg(cache_dir.path()) + .env("VIRTUAL_ENV", venv.as_os_str()) + .current_dir(&temp_dir), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Cannot uninstall package; RECORD file not found at: [VENV_PATH]/lib/python3.12/site-packages/MarkupSafe-2.1.3.dist-info/RECORD + "###); + }); + + Ok(()) +} + #[test] fn uninstall_editable_by_name() -> Result<()> { let temp_dir = assert_fs::TempDir::new()?;