From c54f131500c9bd33daf96146af46cd400d3b006c Mon Sep 17 00:00:00 2001 From: konsti Date: Tue, 10 Jun 2025 12:00:04 +0200 Subject: [PATCH] Add basic network error tests (#13585) Add basic tests for error messages on retryable network errors. This test mod is intended to grow to ensure that we handle retryable errors correctly and that we show the appropriate error message if we failed after retrying. The starter tests show some common cases we've seen download errors in: simple and find links indexes, file downloads and Python installs. For `io::Error` fault injection to test the reqwest `Err` path besides the HTTP status code `Ok` path, see https://github.com/LukeMathWalker/wiremock-rs/issues/149. --- crates/uv/tests/it/main.rs | 4 +- crates/uv/tests/it/network.rs | 154 ++++++++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 crates/uv/tests/it/network.rs diff --git a/crates/uv/tests/it/main.rs b/crates/uv/tests/it/main.rs index 5daa56a3a..7835fa461 100644 --- a/crates/uv/tests/it/main.rs +++ b/crates/uv/tests/it/main.rs @@ -39,7 +39,7 @@ mod lock_conflict; mod lock_scenarios; -mod version; +mod network; #[cfg(all(feature = "python", feature = "pypi"))] mod pip_check; @@ -120,6 +120,8 @@ mod tree; #[cfg(feature = "python")] mod venv; +mod version; + #[cfg(all(feature = "python", feature = "pypi"))] mod workflow; diff --git a/crates/uv/tests/it/network.rs b/crates/uv/tests/it/network.rs new file mode 100644 index 000000000..fba24afe1 --- /dev/null +++ b/crates/uv/tests/it/network.rs @@ -0,0 +1,154 @@ +use std::env; + +use assert_fs::fixture::{FileWriteStr, PathChild}; +use http::StatusCode; +use serde_json::json; +use wiremock::matchers::method; +use wiremock::{Mock, MockServer, ResponseTemplate}; + +use crate::common::{TestContext, uv_snapshot}; + +/// Check the simple index error message when the server returns HTTP status 500, a retryable error. +#[tokio::test] +async fn simple_http_500() { + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR)) + .mount(&server) + .await; + let mock_server_uri = server.uri(); + + let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; + uv_snapshot!(filters, context + .pip_install() + .arg("tqdm") + .arg("--index-url") + .arg(server.uri()), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to fetch: `[SERVER]/tqdm/` + Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/tqdm/) + "); +} + +/// Check the find links error message when the server returns HTTP status 500, a retryable error. +#[tokio::test] +async fn find_links_http_500() { + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR)) + .mount(&server) + .await; + let mock_server_uri = server.uri(); + + let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; + uv_snapshot!(filters, context + .pip_install() + .arg("tqdm") + .arg("--no-index") + .arg("--find-links") + .arg(server.uri()), @r" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Failed to read `--find-links` URL: [SERVER]/ + Caused by: Failed to fetch: `[SERVER]/` + Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/) + "); +} + +/// Check the direct package URL error message when the server returns HTTP status 500, a retryable +/// error. +#[tokio::test] +async fn direct_url_http_500() { + let context = TestContext::new("3.12"); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR)) + .mount(&server) + .await; + let mock_server_uri = server.uri(); + + let tqdm_url = format!( + "{mock_server_uri}/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl" + ); + let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; + uv_snapshot!(filters, context + .pip_install() + .arg(format!("tqdm @ {tqdm_url}")), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + × Failed to download `tqdm @ [SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl` + ├─▶ Failed to fetch: `[SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl` + ╰─▶ HTTP status server error (500 Internal Server Error) for url ([SERVER]/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl) + "); +} + +/// Check the Python install error message when the server returns HTTP status 500, a retryable +/// error. +#[tokio::test] +async fn python_install_http_500() { + let context = TestContext::new("3.12") + .with_filtered_python_keys() + .with_filtered_exe_suffix() + .with_managed_python_dirs(); + + let server = MockServer::start().await; + Mock::given(method("GET")) + .respond_with(ResponseTemplate::new(StatusCode::INTERNAL_SERVER_ERROR)) + .mount(&server) + .await; + let mock_server_uri = server.uri(); + + let python_downloads_json = context.temp_dir.child("python_downloads.json"); + let interpreter = json!({ + "cpython-3.10.0-darwin-aarch64-none": { + "arch": { + "family": "aarch64", + "variant": null + }, + "libc": "none", + "major": 3, + "minor": 10, + "name": "cpython", + "os": "darwin", + "patch": 0, + "prerelease": "", + "sha256": null, + "url": format!("{mock_server_uri}/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst"), + "variant": null + } + }); + python_downloads_json + .write_str(&serde_json::to_string(&interpreter).unwrap()) + .unwrap(); + + let filters = vec![(mock_server_uri.as_str(), "[SERVER]")]; + uv_snapshot!(filters, context + .python_install() + .arg("cpython-3.10.0-darwin-aarch64-none") + .arg("--python-downloads-json-url") + .arg(python_downloads_json.path()), @r" + success: false + exit_code: 1 + ----- stdout ----- + + ----- stderr ----- + error: Failed to install cpython-3.10.0-macos-aarch64-none + Caused by: Failed to download [SERVER]/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst + Caused by: HTTP status server error (500 Internal Server Error) for url ([SERVER]/astral-sh/python-build-standalone/releases/download/20211017/cpython-3.10.0-aarch64-apple-darwin-pgo%2Blto-20211017T1616.tar.zst) + "); +}