open_arg_whole opened and parsed the remote file through open_arg, then opened it again to download it, so every `h5rs check URL` probed the server twice. It now opens the storage once and downloads through the same block cache (whose first block the probe already filled). Test: check --data of a file within one block costs exactly one request (two before). Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
172 lines
6.1 KiB
Rust
172 lines
6.1 KiB
Rust
//! `h5rs` on URLs (feature `remote`): every subcommand prints for
|
|
//! `http://…/file.h5` what it prints for the local file (the name aside).
|
|
//! The files are served by the range-request test server of
|
|
//! clawhdf5-remote on 127.0.0.1.
|
|
|
|
#![cfg(feature = "remote")]
|
|
|
|
#[path = "../../clawhdf5-remote/tests/common/server.rs"]
|
|
mod server;
|
|
|
|
use std::path::{Path, PathBuf};
|
|
use std::process::Command;
|
|
|
|
fn h5rs(args: &[&str]) -> (String, i32) {
|
|
let out = Command::new(env!("CARGO_BIN_EXE_h5rs"))
|
|
.args(args)
|
|
.output()
|
|
.expect("run h5rs");
|
|
let text = format!(
|
|
"{}{}",
|
|
String::from_utf8_lossy(&out.stdout),
|
|
String::from_utf8_lossy(&out.stderr)
|
|
);
|
|
(text, out.status.code().unwrap_or(-1))
|
|
}
|
|
|
|
fn fixtures() -> Vec<PathBuf> {
|
|
let root = Path::new(env!("CARGO_MANIFEST_DIR"));
|
|
[
|
|
"../clawhdf5/tests/fixtures/tall.h5",
|
|
"../clawhdf5/tests/fixtures/written_by_v2_7_0.h5",
|
|
"../clawhdf5/tests/fixtures/written_by_v2_7_0_paged.h5",
|
|
"../clawhdf5/tests/fixtures/h5clear_mdc_image.h5",
|
|
"../clawhdf5-format/tests/fixtures/fractal_heap_multiblock.h5",
|
|
"../clawhdf5-format/tests/fixtures/legacy/tcompound.h5",
|
|
"../clawhdf5-format/tests/fixtures/legacy/h5ex_g_iterate.h5",
|
|
]
|
|
.iter()
|
|
.map(|p| root.join(p))
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn every_subcommand_reads_a_url_like_the_local_file() {
|
|
let files = fixtures();
|
|
let served: Vec<(String, Vec<u8>)> = files
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(i, p)| {
|
|
let name = p.file_name().unwrap().to_str().unwrap();
|
|
(format!("/{i}/{name}"), std::fs::read(p).unwrap())
|
|
})
|
|
.collect();
|
|
let server = server::Server::start(served.clone());
|
|
for (p, (url_path, _)) in files.iter().zip(&served) {
|
|
let url = server.url(url_path);
|
|
let local = p.to_str().unwrap();
|
|
for cmd in [
|
|
&["ls", "-r", "-v"][..],
|
|
&["dump"],
|
|
&["dump", "--json"],
|
|
&["stat"],
|
|
&["check", "--data"],
|
|
] {
|
|
fn args<'a>(cmd: &[&'a str], f: &'a str) -> Vec<&'a str> {
|
|
cmd.iter().copied().chain([f]).collect()
|
|
}
|
|
let (want, want_rc) = h5rs(&args(cmd, local));
|
|
let (got, got_rc) = h5rs(&args(cmd, &url));
|
|
assert_eq!(
|
|
got.replace(&url, local),
|
|
want,
|
|
"h5rs {} {url}",
|
|
cmd.join(" ")
|
|
);
|
|
assert_eq!(got_rc, want_rc, "h5rs {} {url}", cmd.join(" "));
|
|
}
|
|
let (a, rc) = h5rs(&["diff", local, &url]);
|
|
assert_eq!(rc, 0, "h5rs diff {local} {url}: {a}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn url_errors_are_clean() {
|
|
let server = server::Server::start(vec![("/x.h5".into(), vec![1u8; 100])]);
|
|
let (out, rc) = h5rs(&["ls", &server.url("/missing.h5")]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(out.contains("404"), "{out}");
|
|
let (out, rc) = h5rs(&["ls", &server.url("/x.h5")]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(out.contains("not an HDF5 file"), "{out}");
|
|
let (out, rc) = h5rs(&["check", &server.url("/missing.h5")]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
#[cfg(not(feature = "remote-https"))]
|
|
{
|
|
let (out, rc) = h5rs(&["ls", "https://example.com/a.h5"]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(out.contains("`https` feature"), "{out}");
|
|
}
|
|
}
|
|
|
|
/// `check` downloads a remote file whole, but never trusts the length the
|
|
/// server claims: a server claiming 2^62 bytes for a small file is refused
|
|
/// before anything is allocated (it used to abort the process), and
|
|
/// `--max-download` caps real files too.
|
|
#[test]
|
|
fn check_refuses_a_remote_file_beyond_the_download_limit() {
|
|
use std::sync::atomic::Ordering;
|
|
let tall = Path::new(env!("CARGO_MANIFEST_DIR")).join("../clawhdf5/tests/fixtures/tall.h5");
|
|
let server = server::Server::start(vec![("/t.h5".into(), std::fs::read(&tall).unwrap())]);
|
|
let url = server.url("/t.h5");
|
|
let (out, rc) = h5rs(&["check", &url]);
|
|
assert_eq!(rc, 0, "{out}");
|
|
let (out, rc) = h5rs(&["check", "--max-download", "1000", &url]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(out.contains("download limit of 1000 bytes"), "{out}");
|
|
server.shared.fake_total.store(1 << 62, Ordering::SeqCst);
|
|
let (out, rc) = h5rs(&["check", &url]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(out.contains("more than the download limit"), "{out}");
|
|
}
|
|
|
|
/// A URL's credentials (userinfo, a presigned URL's query string) are not
|
|
/// printed: not in errors, not in the file name of the output.
|
|
#[test]
|
|
fn credentials_in_urls_are_not_printed() {
|
|
let tall = Path::new(env!("CARGO_MANIFEST_DIR")).join("../clawhdf5/tests/fixtures/tall.h5");
|
|
let server = server::Server::start(vec![("/t.h5".into(), std::fs::read(&tall).unwrap())]);
|
|
let url = |path: &str| {
|
|
format!(
|
|
"http://user:hunter2@{}{path}?X-Amz-Signature=SECRETSIG",
|
|
server.addr
|
|
)
|
|
};
|
|
for args in [
|
|
vec!["ls", "-r"],
|
|
vec!["dump"],
|
|
vec!["stat"],
|
|
vec!["check"],
|
|
vec!["check", "--max-download", "10"],
|
|
] {
|
|
for path in ["/t.h5", "/missing.h5"] {
|
|
let u = url(path);
|
|
let mut a = args.clone();
|
|
a.push(&u);
|
|
let (out, _) = h5rs(&a);
|
|
assert!(
|
|
!out.contains("hunter2") && !out.contains("SECRETSIG"),
|
|
"h5rs {}: {out}",
|
|
a.join(" ")
|
|
);
|
|
}
|
|
}
|
|
let (out, rc) = h5rs(&["diff", tall.to_str().unwrap(), &url("/t.h5"), "/nope"]);
|
|
assert_eq!(rc, 2, "{out}");
|
|
assert!(
|
|
!out.contains("hunter2") && !out.contains("SECRETSIG"),
|
|
"{out}"
|
|
);
|
|
}
|
|
|
|
/// `check URL` opens the file once: for a file within the first block,
|
|
/// one request in all (it probed the server twice before).
|
|
#[test]
|
|
fn check_url_probes_the_server_once() {
|
|
let tall = Path::new(env!("CARGO_MANIFEST_DIR")).join("../clawhdf5/tests/fixtures/tall.h5");
|
|
let server = server::Server::start(vec![("/t.h5".into(), std::fs::read(&tall).unwrap())]);
|
|
let (out, rc) = h5rs(&["check", "--data", &server.url("/t.h5")]);
|
|
assert_eq!(rc, 0, "{out}");
|
|
assert_eq!(server.requests(), 1, "{:?}", server.log());
|
|
}
|