From c11e9e509794be64b69f35b4ae454c47ae63d411 Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Fri, 5 Apr 2024 14:16:18 -0400 Subject: [PATCH] Add backtracking tests for distribution incompatibilities (#2839) ## Summary Demonstrates some suboptimal behavior in how we handle invalid metadata, which are fixed in https://github.com/astral-sh/uv/pull/2834. The included wheels were modified by-hand to include invalid structures. --- crates/uv/tests/pip_compile.rs | 128 ++++++++++++++++++ .../links/validation-1.0.0-py3-none-any.whl | Bin 0 -> 1410 bytes .../links/validation-2.0.0-py3-none-any.whl | Bin 0 -> 1405 bytes .../links/validation-3.0.0-py3-none-any.whl | Bin 0 -> 2446 bytes 4 files changed, 128 insertions(+) create mode 100644 scripts/links/validation-1.0.0-py3-none-any.whl create mode 100644 scripts/links/validation-2.0.0-py3-none-any.whl create mode 100644 scripts/links/validation-3.0.0-py3-none-any.whl diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 91252ac7c..5f50d655c 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -4397,6 +4397,50 @@ fn offline_registry() -> Result<()> { Ok(()) } +/// Resolve a registry package without network access via the `--offline` flag. We should backtrack +/// to the latest version of the package that's available in the cache. +#[test] +fn offline_registry_backtrack() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig==1.1.1")?; + + // Populate the cache. + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: true + exit_code: 0 + ----- stdout ----- + # This file was autogenerated by uv via the following command: + # uv pip compile --cache-dir [CACHE_DIR] --exclude-newer 2024-03-25T00:00:00Z requirements.in + iniconfig==1.1.1 + + ----- stderr ----- + Resolved 1 package in [TIME] + "### + ); + + // Resolve with `--offline`, with a looser requirement. We should backtrack to `1.1.1`, but + // we don't right now. + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("iniconfig")?; + + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--offline"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: iniconfig==2.0.0 + Caused by: Network connectivity is disabled, but the requested data wasn't found in the cache for: `https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl.metadata` + "### + ); + + Ok(()) +} + /// Resolve a package without network access via the `--offline` flag, using `--find-links` for an /// HTML registry. #[test] @@ -4504,6 +4548,90 @@ fn offline_direct_url() -> Result<()> { Ok(()) } +/// Resolve a package with invalid metadata, by way of an invalid `Requires-Python` field in the +/// `METADATA` file. +#[test] +fn invalid_metadata_requires_python() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation==2.0.0")?; + + // `2.0.0` has invalid metadata. + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: validation==2.0.0 + Caused by: Couldn't parse metadata of validation-2.0.0-py3-none-any.whl from validation==2.0.0 + Caused by: Failed to parse version: Unexpected end of version specifier, expected operator: + 12 + ^^ + + "### + ); + + Ok(()) +} + +/// Resolve a package with multiple `.dist-info` directories. +#[test] +fn invalid_metadata_multiple_dist_info() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation==3.0.0")?; + + // `3.0.0` has an invalid structure (multiple `.dist-info` directories). + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: validation==3.0.0 + Caused by: Multiple .dist-info directories found: validation-2.0.0, validation-3.0.0 + "### + ); + + Ok(()) +} + +/// Resolve a package, but backtrack past versions with invalid metadata. +#[test] +fn invalid_metadata_backtrack() -> Result<()> { + let context = TestContext::new("3.12"); + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("validation")?; + + // `2.0.0` and `3.0.0` have invalid metadata. We should backtrack to `1.0.0` (the preceding + // version, which has valid metadata), but we don't right now. + uv_snapshot!(context.compile() + .arg("requirements.in") + .arg("--no-index") + .arg("--find-links") + .arg(context.workspace_root.join("scripts").join("links")), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to download: validation==3.0.0 + Caused by: Multiple .dist-info directories found: validation-2.0.0, validation-3.0.0 + "### + ); + + Ok(()) +} + /// Resolve nested `-r` requirements files with relative paths. #[test] fn compile_relative_subfile() -> Result<()> { diff --git a/scripts/links/validation-1.0.0-py3-none-any.whl b/scripts/links/validation-1.0.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..8808be42fc19d35c1659183a7e41f3a1f45e17a1 GIT binary patch literal 1410 zcmWIWW@h1H0DV3IeF&%<^KNjXHM&M_FvataYgf-hl!Et z=C7B|UeYx^p`%$)%58XB$Ll2T`Lk;{bb7w{`g}ZX*t5ZeXC=qkv**8j{P6XPq0u9) zX6a?wE7BQSG-n=F-8Hi^^XIR9>lFL+`V{-DtbT;FR`1yL>p{5ClUHx#pD=|mgQEoE zFHn>`*q>G#2J}-n5G&yKSGb3(s}H`&;XP@{*Py_|aA5EH2h6IAq~#6rj!DeDXyV`$ z6qU|)%bfRwVdAs z_E!FC(eaVkahM$xxe)it0^^~-qWQBk(5=2etc>5izOEsTE{-9NU?)dnkBQH|x}Lz8 z@Vvt7t*dqJ%=yhh2A7PVeDXfyyERAy7)vat7+rTh*wj|VR>cTxLI5Q&`dP*;^WIw zi;97^#>XqzDkvH18R!`(aUpC$cd9gsQ+16%>h)4Gi%WDf^V0GWPPfADbdVP~7_I^R z&%Sg`b}f*%3y2l*8ye*5>>uO;_QhkM51wH9LfiYyNgq$0lV{Ff?(aW;=Cn>{|8?yZ zS2WLgm>8LE{(9-`C0)}KI+_Kg+=i!hyiW3-Kf8uQr{{~W&&Sh-JsV7TR&ty@d;ZJE z4_~hs8a>i#mR^>PJXx^^RS?9)t@$dG$vA z2~!9&I7%S?0!7J#{b{vfKtF{8u>yX7g?qTV`rwNk-jjxW4GKIA2llRiz^uARTHYY< zn8e(RCJs(f!37V#SG}Iwx#EFi)aDK5=lBeSC%37+^5uH#r&|0cx#ZeGwMzvnckJ_0 zjlXA7%lS=UZ{@ER9UqAuhuJ}q3vq8oerv?zB|^P+K(~4Uu`+)5`nrZVx;Tb7f}Q*Y z7{T8#Bl)wht|u@iJg@M2>uQ}lbAEG>!6oA-pS;ibZVl1^#uCdZM%SGaQh2}c>}U$& z_BQSg>Mmv0@-}if)aDig3`#~OIc8j$L;~nU5D;K^>jTxLI7IErxmV@nxw+ z#Xwu*;}vWblnnI@^bC}^5VoK@RT{;qx<(-NdMTO3CAyh;Y554JTVZ!P$O{|{*MR_nQs1i>Fgz4(-S(H1*P1Er**te@}57thC`?4i?7ee(}q18On6puoIQK~%f}C2 zuNWFV(rT7omc1mMp+$4%QPo{DD>Hxo+P6-zPp?n0&&ujYNNe?uUB4cL3q5)DM*ay? z2s1cJApQbH$%FlAwP8R%g#)nyet(5~xVrk_iyYpQhI|bQJPZf+u7AL+x=32yAn%yO z+>0g-PEo-H558Bup4++Nfn(I>4d&$dh4fJ{3p5O+CjBT1uJ*#^HPn! zXHv`gO<-^3uNEC2i5-X8L6Hk_Z$^G=#N#DGy>>vidI7OAe)syihB&%7hB$(q`~?`n zSYqO{udXLBCOoh3dh2SPJ9B<>kijM6C!f5}_-+l-0LBu_DMr_w6H<7;@a$*`;`TP~ z4(cvt*77!TIMn7AgOs`nq&s6=>8=Aai6i`brl2+AqMl(Mj&z4^C{ntc4>WcmrqR?- zcWY>n?$G^(n(n$Vy+!MEcLEp>B~1KT4%pKjx_eR5-E^RHXJR^;sC4&W)3GYHDz+HM zm90x>x~@FBi*c%|q(Y*zvK}zyF*3<9<0_sdfDQ!#0fx7ZAR48xW`z{i7zH)LK+K{V zVj!?kXIRqcifSNA!3{JURB+=l8#ALIJ9agS*;tBee2&LeSR*^00q6%rXkb_X%R)eF zKt(rJf8Z)Rk*&Fb-x_F&#ur++N<3s+I+*Z!29XYd7J;vw*V literal 0 HcmV?d00001