server/index-rows/src/tests.rs
use super::*;
fn rpm() -> IndexRow {
IndexRow::Rpm {
epoch: Some("1".into()),
summary: Some("a demo".into()),
license: Some("MIT".into()),
url: None,
vendor: None,
sourcerpm: None,
provides: vec!["hello = 1.0".into()],
requires: vec![],
}
}
fn honest() -> Vec<IndexRow> {
vec![
IndexRow::Deb {
control: "Package: hello\nVersion: 1.0\nArchitecture: amd64\nDescription: hi\n there\n"
.into(),
},
rpm(),
IndexRow::Helm {
name: "demo".into(),
version: "0.1.0".into(),
app_version: Some("1.16.0".into()),
description: None,
},
IndexRow::Wheel {
metadata: Some(b"Metadata-Version: 2.1\nName: demo\n\xff".to_vec()),
sha256: Some("ab".repeat(32)),
},
IndexRow::Wheel {
metadata: None,
sha256: None,
},
IndexRow::Npm {
manifest: serde_json::json!({"name": "left-pad", "version": "1.3.0"}),
integrity: format!("sha512-{}", "A".repeat(86) + "=="),
},
IndexRow::Maven {
group: "org.demo".into(),
artifact: "demo".into(),
version: "1.0".into(),
file: "demo-1.0.jar".into(),
},
IndexRow::Go {
module: "example.com/demo".into(),
version: "v1.0.0".into(),
},
]
}
#[test]
fn every_honest_row_passes_and_reads_back_the_same() {
for row in honest() {
row.check()
.unwrap_or_else(|e| panic!("{}: {e}", row.kind()));
let s = StoredRow::new(&"cd".repeat(32), 42, row.clone());
let back = StoredRow::parse(&serde_json::to_vec(&s).unwrap(), &"cd".repeat(32)).unwrap();
assert_eq!(back, s, "{}", row.kind());
}
}
#[test]
fn a_row_out_of_its_bounds_is_refused_by_the_field_it_breaks() {
let big = "x".repeat(MAX_ROW + 1);
let cases: Vec<(&str, IndexRow)> = vec![
(
"control",
IndexRow::Deb {
control: big.clone(),
},
),
(
"control",
IndexRow::Deb {
control: "Package: a\u{1b}[2J\n".into(),
},
),
(
"summary",
rpm_with(|s, _| *s = Some("x".repeat(MAX_TEXT + 1))),
),
(
"requires",
rpm_with(|_, r| *r = vec!["x".into(); MAX_DEPS + 1]),
),
(
"epoch",
match rpm() {
IndexRow::Rpm {
summary,
license,
url,
vendor,
sourcerpm,
provides,
requires,
..
} => IndexRow::Rpm {
epoch: Some("1\n".into()),
summary,
license,
url,
vendor,
sourcerpm,
provides,
requires,
},
_ => unreachable!(),
},
),
(
"name",
IndexRow::Helm {
name: String::new(),
version: "1".into(),
app_version: None,
description: None,
},
),
(
"version",
IndexRow::Helm {
name: "a".into(),
version: "1\r".into(),
app_version: None,
description: None,
},
),
(
"metadata",
IndexRow::Wheel {
metadata: Some(vec![b'a'; MAX_WHEEL_METADATA + 1]),
sha256: Some("ab".repeat(32)),
},
),
(
"sha256",
IndexRow::Wheel {
metadata: Some(b"Name: a\n".to_vec()),
sha256: Some("not hex".into()),
},
),
(
"sha256",
IndexRow::Wheel {
metadata: Some(b"Name: a\n".to_vec()),
sha256: None,
},
),
(
"manifest",
IndexRow::Npm {
manifest: serde_json::json!("a string"),
integrity: "sha512-AA==".into(),
},
),
(
"manifest",
IndexRow::Npm {
manifest: serde_json::json!({"x": "y".repeat(MAX_NPM_MANIFEST)}),
integrity: "sha512-AA==".into(),
},
),
(
"integrity",
IndexRow::Npm {
manifest: serde_json::json!({}),
integrity: "sha1-AA==".into(),
},
),
(
"group",
IndexRow::Maven {
group: "a/b".into(),
artifact: "c".into(),
version: "1".into(),
file: "c-1.jar".into(),
},
),
(
"file",
IndexRow::Maven {
group: "a".into(),
artifact: "c".into(),
version: "1".into(),
file: "../c-1.jar".into(),
},
),
(
"module",
IndexRow::Go {
module: "x".repeat(MAX_ID + 1),
version: "v1".into(),
},
),
];
for (field, row) in cases {
let e = row
.check()
.expect_err(&format!("{} {field} must be refused", row.kind()));
assert!(
e.contains(field),
"{} {field}: the refusal names the field: {e}",
row.kind()
);
}
}
#[test]
fn a_stored_row_about_other_bytes_or_of_another_schema_is_refused() {
let s = StoredRow::new(&"cd".repeat(32), 1, rpm());
let bytes = serde_json::to_vec(&s).unwrap();
assert!(StoredRow::parse(&bytes, &"ef".repeat(32))
.unwrap_err()
.contains("not"));
let mut v: serde_json::Value = serde_json::from_slice(&bytes).unwrap();
v["schema"] = "holger.index-row/v0".into();
assert!(
StoredRow::parse(&serde_json::to_vec(&v).unwrap(), &"cd".repeat(32))
.unwrap_err()
.contains("schema")
);
v["schema"] = SCHEMA.into();
v["row"]["path_to_open"] = "/etc/shadow".into();
assert!(
StoredRow::parse(&serde_json::to_vec(&v).unwrap(), &"cd".repeat(32)).is_err(),
"an unknown field"
);
}
/// The honest rpm row with its summary and requires changed by `f`.
fn rpm_with(f: impl FnOnce(&mut Option<String>, &mut Vec<String>)) -> IndexRow {
let mut r = rpm();
if let IndexRow::Rpm {
summary, requires, ..
} = &mut r
{
f(summary, requires);
}
r
}
/// A gzipped tar of `files`, as `npm pack` and `helm package` lay one.
fn tgz(files: &[(&str, &[u8])]) -> Vec<u8> {
let mut b = tar::Builder::new(flate2::write::GzEncoder::new(
Vec::new(),
flate2::Compression::default(),
));
for (path, body) in files {
let mut h = tar::Header::new_gnu();
h.set_size(body.len() as u64);
h.set_mode(0o644);
h.set_cksum();
b.append_data(&mut h, path, *body).unwrap();
}
b.into_inner().unwrap().finish().unwrap()
}
/// **Each format's row is read out of its own bytes** (lane HOL-PERF-2), by the very parse
/// the format backends ran in the server before: the deb stanza, the rpm header, the
/// Chart.yaml, the npm package.json with its integrity, the wheel METADATA with its sha256.
#[test]
fn every_format_reads_its_row_out_of_its_bytes() {
let deb = deb::tests_support::sample_deb();
let want = deb::control_from_deb(&deb).unwrap();
assert!(want.contains("Depends: libc6 (>= 2.14)"));
assert_eq!(
row_of("deb", "hello_2.10-2_amd64.deb", &deb),
Some(IndexRow::Deb { control: want })
);
use rpm::tests_support::{make_rpm, Tag};
let r = make_rpm(&[
Tag::Int32(1003, 2),
Tag::Str(1004, "A greeter"),
Tag::Str(1014, "MIT"),
Tag::StrArray(1049, &["libc.so.6"]),
]);
let h = rpm::header(&r).unwrap();
assert_eq!(h.epoch.as_deref(), Some("2"));
assert_eq!(
row_of("raw", "hello-2.10-1.x86_64.rpm", &r),
Some(IndexRow::Rpm {
epoch: h.epoch,
summary: h.summary,
license: h.license,
url: None,
vendor: None,
sourcerpm: None,
provides: vec![],
requires: vec!["libc.so.6".into()],
})
);
let chart = tgz(&[("demo/Chart.yaml", b"apiVersion: v2\nname: demo\nversion: 0.1.0\nappVersion: \"1.16.0\"\ndescription: A chart\n")]);
assert_eq!(
row_of("raw", "demo-0.1.0.tgz", &chart),
Some(IndexRow::Helm {
name: "demo".into(),
version: "0.1.0".into(),
app_version: Some("1.16.0".into()),
description: Some("A chart".into())
})
);
let pkg = tgz(&[(
"package/package.json",
br#"{"name":"left-pad","version":"1.3.0","main":"index.js"}"#,
)]);
assert_eq!(
row_of("npm", "left-pad-1.3.0.tgz", &pkg),
Some(IndexRow::Npm {
manifest: serde_json::json!({"name": "left-pad", "version": "1.3.0", "main": "index.js"}),
integrity: npm::integrity_of(&pkg)
})
);
assert_eq!(
row_of("raw", "left-pad-1.3.0.tgz", &pkg),
row_of("npm", "left-pad-1.3.0.tgz", &pkg),
"an npm tarball a pull-through names `raw` is still npm's, never a chart"
);
let meta = b"Metadata-Version: 2.1\nName: demo\nVersion: 1.0\n";
let whl = zip_entry::fixture::zip(&[
("demo/__init__.py", b""),
("demo-1.0.dist-info/METADATA", meta),
]);
let sha = {
use sha2::Digest as _;
sha2::Sha256::digest(meta)
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>()
};
assert_eq!(
row_of("pip", "demo-1.0-py3-none-any.whl", &whl),
Some(IndexRow::Wheel {
metadata: Some(meta.to_vec()),
sha256: Some(sha)
})
);
let bare = zip_entry::fixture::zip(&[("demo/__init__.py", b"")]);
assert_eq!(
row_of("pip", "demo-1.0-py3-none-any.whl", &bare),
Some(IndexRow::Wheel {
metadata: None,
sha256: None
})
);
assert_eq!(
row_of("raw", "hello_1.0_amd64.deb", b"not a deb"),
None,
"bytes that are not the package give no row"
);
assert_eq!(
row_of("raw", "notes.txt", b"hello"),
None,
"a format without an index has no row"
);
}
/// A package whose fields are out of a row's bounds gives no row, never a row the server
/// would refuse: the worker checks what it answers.
#[test]
fn a_package_whose_fields_break_the_bounds_gives_no_row() {
let chart = tgz(&[(
"demo/Chart.yaml",
b"name: demo\nversion: \"0.1.0\x1b[2J\"\n",
)]);
assert!(
helm::chart_meta_from_tgz(&chart).is_ok(),
"the chart itself reads"
);
assert_eq!(row_of("raw", "demo-0.1.0.tgz", &chart), None);
}
/// **A crate's row** (lane HOL-CARGO-PUBLISH-DOOR): its index line from its own normalised
/// `Cargo.toml`, and the dependency versions it pins: exact requirements of normal and build
/// dependencies (never a dev-dependency), and a binary crate's `Cargo.lock` registry packages.
#[test]
fn a_crate_row_carries_its_index_line_and_its_pinned_dependencies() {
let manifest = br#"
[package]
name = "demo-bin"
version = "0.3.0"
[[bin]]
name = "demo-bin"
path = "src/main.rs"
[dependencies]
smallvec = { version = "=1.6.0", registry-index = "https://github.com/rust-lang/crates.io-index" }
log = "0.4"
[dependencies.myrand]
version = "=0.8.5"
package = "rand"
[dev-dependencies]
tempfile = "=3.0.0"
"#;
let lock = br#"
version = 4
[[package]]
name = "demo-bin"
version = "0.3.0"
[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
"#;
let bytes = tgz(&[
("demo-bin-0.3.0/Cargo.toml", manifest),
("demo-bin-0.3.0/Cargo.lock", lock),
("demo-bin-0.3.0/src/main.rs", b"fn main() {}\n"),
]);
let row = row_of("cargo", "demo-bin-0.3.0.crate", &bytes).expect("a crate row");
let IndexRow::Crate {
name,
version,
line,
pinned,
} = &row
else {
panic!("{row:?}");
};
assert_eq!((name.as_str(), version.as_str()), ("demo-bin", "0.3.0"));
assert_eq!(line["vers"], "0.3.0");
assert!(
line.get("cksum").is_none(),
"the sealed bytes give the cksum"
);
let deps = line["deps"].as_array().unwrap();
assert!(deps
.iter()
.any(|d| d["name"] == "smallvec" && d["registry"].as_str().is_some()));
let got: Vec<(&str, &str, &str)> = pinned
.iter()
.map(|p| (p.name.as_str(), p.version.as_str(), p.from.as_str()))
.collect();
assert_eq!(
got,
vec![
("rand", "0.8.5", "Cargo.toml"),
("smallvec", "1.6.0", "Cargo.toml"),
("time", "0.1.43", "Cargo.lock"),
],
"a renamed dependency is pinned by its crate name; a dev-dependency never"
);
row.check().unwrap();
// A library's lock file is never what its users build with.
let lib_manifest = String::from_utf8_lossy(manifest)
.replace("[[bin]]\nname = \"demo-bin\"\npath = \"src/main.rs\"\n", "");
let lib = tgz(&[
("demo-bin-0.3.0/Cargo.toml", lib_manifest.as_bytes()),
("demo-bin-0.3.0/Cargo.lock", lock),
]);
let Some(IndexRow::Crate { pinned, .. }) = row_of("cargo", "demo-bin-0.3.0.crate", &lib) else {
panic!("a library crate row");
};
assert!(pinned.iter().all(|p| p.from == "Cargo.toml"), "{pinned:?}");
// A crate whose directory is not what its manifest names gives no row.
let liar = tgz(&[("other-0.3.0/Cargo.toml", manifest)]);
assert_eq!(row_of("cargo", "demo-bin-0.3.0.crate", &liar), None);
assert_eq!(crates::exact_version("=1.6.0"), Some("1.6.0"));
assert_eq!(crates::exact_version("= 1.6.0-rc.1"), Some("1.6.0-rc.1"));
for loose in ["1.6.0", "^1.6", "=1.6", ">=1, <2", "=1.*.0"] {
assert_eq!(crates::exact_version(loose), None, "{loose}");
}
}