| mod utils; |
| |
| use http::header; |
| use tame_index::{IndexLocation, IndexUrl, SparseIndex}; |
| |
| #[inline] |
| fn crates_io(path: impl AsRef<tame_index::Path>) -> SparseIndex { |
| SparseIndex::new( |
| IndexLocation::new(IndexUrl::CratesIoSparse).with_root(Some(path.as_ref().to_owned())), |
| ) |
| .unwrap() |
| } |
| |
| /// Validates the we get a valid root and krate url |
| #[test] |
| fn opens_crates_io() { |
| let index = crates_io(env!("CARGO_MANIFEST_DIR")); |
| |
| assert_eq!(index.url(), "https://index.crates.io/"); |
| assert_eq!( |
| index.crate_url("autocfg".try_into().unwrap()), |
| "https://index.crates.io/au/to/autocfg" |
| ); |
| } |
| |
| /// Validates a request can be made for a crate that doesn't have a local cache entry |
| #[test] |
| fn make_request_without_cache() { |
| let index = crates_io(env!("CARGO_MANIFEST_DIR")); |
| let lock = &utils::unlocked(); |
| |
| let req = index |
| .make_remote_request("serde".try_into().unwrap(), None, lock) |
| .unwrap(); |
| |
| let hdrs = req.headers(); |
| // Ensure neither of the possible cache headers are set |
| assert!( |
| hdrs.get(header::IF_MODIFIED_SINCE).is_none() && hdrs.get(header::IF_NONE_MATCH).is_none() |
| ); |
| |
| assert_eq!(hdrs.get("cargo-protocol").unwrap(), "version=1"); |
| assert_eq!(hdrs.get(header::ACCEPT).unwrap(), "text/plain"); |
| assert_eq!(hdrs.get(header::ACCEPT_ENCODING).unwrap(), "gzip"); |
| } |
| |
| const ETAG: &str = "W/\"fa62f662c9aae1f21cab393950d4ae23\""; |
| const DATE: &str = "Thu, 22 Oct 2023 09:40:03 GMT"; |
| |
| /// Validates appropriate headers are set when a cache entry does exist |
| #[test] |
| fn make_request_with_cache() { |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| { |
| let etag_krate = utils::fake_krate("etag-krate", 2); |
| index |
| .cache() |
| .write_to_cache(&etag_krate, &format!("{}: {ETAG}", header::ETAG), lock) |
| .unwrap(); |
| |
| let req = index |
| .make_remote_request("etag-krate".try_into().unwrap(), None, lock) |
| .unwrap(); |
| |
| assert_eq!(req.headers().get(header::IF_NONE_MATCH).unwrap(), ETAG); |
| } |
| |
| { |
| let req = index |
| .make_remote_request("etag-specified-krate".try_into().unwrap(), Some(ETAG), lock) |
| .unwrap(); |
| |
| assert_eq!(req.headers().get(header::IF_NONE_MATCH).unwrap(), ETAG); |
| } |
| |
| { |
| let modified_krate = utils::fake_krate("modified-krate", 2); |
| index |
| .cache() |
| .write_to_cache( |
| &modified_krate, |
| &format!("{}: {DATE}", header::LAST_MODIFIED), |
| lock, |
| ) |
| .unwrap(); |
| |
| let req = index |
| .make_remote_request("modified-krate".try_into().unwrap(), None, lock) |
| .unwrap(); |
| |
| assert_eq!(req.headers().get(header::IF_MODIFIED_SINCE).unwrap(), DATE); |
| } |
| } |
| |
| /// Validates we can parse a response where the local cache version is up to date |
| #[test] |
| fn parse_unmodified_response() { |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| let etag_krate = utils::fake_krate("etag-krate", 2); |
| index |
| .cache() |
| .write_to_cache(&etag_krate, &format!("{}: {ETAG}", header::ETAG), lock) |
| .unwrap(); |
| |
| let response = http::Response::builder() |
| .status(http::StatusCode::NOT_MODIFIED) |
| .header(header::ETAG, ETAG) |
| .body(Vec::new()) |
| .unwrap(); |
| |
| let cached_krate = index |
| .parse_remote_response("etag-krate".try_into().unwrap(), response, true, lock) |
| .unwrap() |
| .expect("cached krate"); |
| |
| assert_eq!(etag_krate, cached_krate); |
| } |
| |
| /// Validates we can parse a modified response |
| #[test] |
| fn parse_modified_response() { |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| { |
| let etag_krate = utils::fake_krate("etag-krate", 3); |
| let mut serialized = Vec::new(); |
| etag_krate.write_json_lines(&mut serialized).unwrap(); |
| |
| let response = http::Response::builder() |
| .status(http::StatusCode::OK) |
| .header(header::ETAG, ETAG) |
| .body(serialized) |
| .unwrap(); |
| |
| let new_krate = index |
| .parse_remote_response("etag-krate".try_into().unwrap(), response, true, lock) |
| .unwrap() |
| .expect("new response"); |
| |
| assert_eq!(etag_krate, new_krate); |
| |
| let cached_krate = index |
| .cache() |
| .cached_krate( |
| "etag-krate".try_into().unwrap(), |
| Some(&format!("{}: {ETAG}", header::ETAG)), |
| lock, |
| ) |
| .unwrap() |
| .expect("cached krate"); |
| |
| assert_eq!(etag_krate, cached_krate); |
| } |
| |
| { |
| let modified_krate = utils::fake_krate("modified-krate", 3); |
| let mut serialized = Vec::new(); |
| modified_krate.write_json_lines(&mut serialized).unwrap(); |
| |
| let response = http::Response::builder() |
| .status(http::StatusCode::OK) |
| .header(header::LAST_MODIFIED, DATE) |
| .body(serialized) |
| .unwrap(); |
| |
| let new_krate = index |
| .parse_remote_response("modified-krate".try_into().unwrap(), response, true, lock) |
| .unwrap() |
| .expect("new response"); |
| |
| assert_eq!(modified_krate, new_krate); |
| |
| let cached_krate = index |
| .cache() |
| .cached_krate( |
| "modified-krate".try_into().unwrap(), |
| Some(&format!("{}: {DATE}", header::LAST_MODIFIED)), |
| lock, |
| ) |
| .unwrap() |
| .expect("cached krate"); |
| |
| assert_eq!(modified_krate, cached_krate); |
| } |
| } |
| |
| #[cfg(feature = "sparse")] |
| mod remote { |
| use super::*; |
| |
| /// Ensure we can actually send a request to crates.io and parse the response |
| #[test] |
| fn end_to_end() { |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| let client = reqwest::blocking::Client::builder().build().unwrap(); |
| |
| let rsi = tame_index::index::RemoteSparseIndex::new(index, client); |
| |
| let spdx_krate = rsi |
| .krate("spdx".try_into().unwrap(), true, lock) |
| .expect("failed to retrieve spdx") |
| .expect("failed to find spdx"); |
| |
| spdx_krate |
| .versions |
| .iter() |
| .find(|iv| iv.version == "0.10.1") |
| .expect("failed to find expected version"); |
| } |
| |
| // cargo metadata --format-version=1 | jq -r '.packages[].name | "\"\(.)\","' |
| const KRATES: &[&str] = &[ |
| "addr2line", |
| "adler", |
| "async-compression", |
| "autocfg", |
| "backtrace", |
| "base64", |
| "bitflags", |
| "bitflags", |
| "bumpalo", |
| "bytes", |
| "camino", |
| "cargo-platform", |
| "cargo_metadata", |
| "cc", |
| "cfg-if", |
| "core-foundation", |
| "core-foundation-sys", |
| "crc32fast", |
| "crossbeam-deque", |
| "crossbeam-epoch", |
| "crossbeam-utils", |
| "either", |
| "encoding_rs", |
| "equivalent", |
| "errno", |
| "fastrand", |
| "flate2", |
| "fnv", |
| "form_urlencoded", |
| "futures-channel", |
| "futures-core", |
| "futures-io", |
| "futures-sink", |
| "futures-task", |
| "futures-util", |
| "getrandom", |
| "gimli", |
| "h2", |
| "hashbrown", |
| "hermit-abi", |
| "home", |
| "http", |
| "http-body", |
| "httparse", |
| "httpdate", |
| "hyper", |
| "hyper-rustls", |
| "idna", |
| "indexmap", |
| "ipnet", |
| "itoa", |
| "js-sys", |
| "libc", |
| "linux-raw-sys", |
| "log", |
| "memchr", |
| "mime", |
| "miniz_oxide", |
| "mio", |
| "num_cpus", |
| "object", |
| "once_cell", |
| "percent-encoding", |
| "pin-project-lite", |
| "pin-utils", |
| "proc-macro2", |
| "quote", |
| "rayon", |
| "rayon-core", |
| "reqwest", |
| "ring", |
| "rustc-demangle", |
| "rustix", |
| "rustls", |
| "rustls-pemfile", |
| "rustls-webpki", |
| "ryu", |
| "sct", |
| "semver", |
| "serde", |
| "serde_derive", |
| "serde_json", |
| "serde_spanned", |
| "serde_urlencoded", |
| "slab", |
| "smol_str", |
| "socket2", |
| "spin", |
| "static_assertions", |
| "syn", |
| "sync_wrapper", |
| "system-configuration", |
| "system-configuration-sys", |
| "tame-index", |
| "tempfile", |
| "thiserror", |
| "thiserror-impl", |
| "tiny-bench", |
| "tinyvec", |
| "tinyvec_macros", |
| "tokio", |
| "tokio-rustls", |
| "tokio-util", |
| "toml", |
| "toml_datetime", |
| "toml_edit", |
| "tower-service", |
| "tracing", |
| "tracing-core", |
| "try-lock", |
| "twox-hash", |
| "unicode-bidi", |
| "unicode-ident", |
| "unicode-normalization", |
| "untrusted", |
| "url", |
| "want", |
| "wasi", |
| "wasm-bindgen", |
| "wasm-bindgen-backend", |
| "wasm-bindgen-futures", |
| "wasm-bindgen-macro", |
| "wasm-bindgen-macro-support", |
| "wasm-bindgen-shared", |
| "web-sys", |
| "webpki-roots", |
| "windows-sys", |
| "windows-sys", |
| "windows-targets", |
| "windows-targets", |
| "windows_aarch64_gnullvm", |
| "windows_aarch64_gnullvm", |
| "windows_aarch64_msvc", |
| "windows_aarch64_msvc", |
| "windows_i686_gnu", |
| "windows_i686_gnu", |
| "windows_i686_msvc", |
| "windows_i686_msvc", |
| "windows_x86_64_gnu", |
| "windows_x86_64_gnu", |
| "windows_x86_64_gnullvm", |
| "windows_x86_64_gnullvm", |
| "windows_x86_64_msvc", |
| "windows_x86_64_msvc", |
| "winnow", |
| "winreg", |
| ]; |
| |
| fn ensure_no_errors( |
| results: std::collections::BTreeMap< |
| String, |
| Result<Option<tame_index::IndexKrate>, tame_index::Error>, |
| >, |
| ) { |
| use std::fmt::Write; |
| let mut errors = String::new(); |
| |
| for (name, res) in results { |
| match res { |
| Ok(Some(_)) => continue, |
| Ok(None) => writeln!(&mut errors, "{name}:\tfailed to locate").unwrap(), |
| Err(err) => writeln!(&mut errors, "{name}:\t{err}").unwrap(), |
| } |
| } |
| |
| assert!(errors.is_empty(), "{errors}"); |
| } |
| |
| /// Reuses connections. This test is intended to be run under strace to |
| /// validate that <n> connections are not being created |
| /// <https://github.com/EmbarkStudios/tame-index/issues/46> |
| #[test] |
| fn reuses_connection() { |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| let client = reqwest::blocking::Client::builder().build().unwrap(); |
| let rsi = tame_index::index::RemoteSparseIndex::new(index, client); |
| |
| let results = rsi.krates( |
| KRATES.iter().map(|s| (*s).to_string()).collect(), |
| false, |
| lock, |
| ); |
| |
| ensure_no_errors(results); |
| } |
| |
| // Ditto, but for async |
| #[test] |
| fn async_reuses_connection() { |
| let rt = tokio::runtime::Runtime::new().unwrap(); |
| let _guard = rt.enter(); |
| |
| let td = utils::tempdir(); |
| let index = crates_io(&td); |
| let lock = &utils::unlocked(); |
| |
| let client = reqwest::Client::builder().build().unwrap(); |
| let rsi = tame_index::index::AsyncRemoteSparseIndex::new(index, client); |
| |
| let results = rsi |
| .krates_blocking( |
| KRATES.iter().map(|s| (*s).to_string()).collect(), |
| false, |
| None, |
| lock, |
| ) |
| .unwrap(); |
| |
| ensure_no_errors(results); |
| } |
| } |