Skip to content

Commit d7a8822

Browse files
committed
test: run tests using reference data from purl-spec
Signed-off-by: Keshav Priyadarshi <[email protected]>
1 parent 9272098 commit d7a8822

File tree

5 files changed

+185
-425
lines changed

5 files changed

+185
-425
lines changed

tests/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
#[macro_use]
2-
extern crate serde;
31
extern crate packageurl;
2+
extern crate serde;
43
extern crate serde_json;
54

65
mod spec;

tests/spec/macros.rs

Lines changed: 120 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,142 @@
1-
macro_rules! spec_tests {
2-
($name:ident, $desc:expr) => {
3-
mod $name {
1+
use crate::spec::testcase::SpecTestCase;
2+
use crate::spec::testcase::TestSuite;
3+
use crate::spec::testcase::PurlOrString;
4+
use std::path::Path;
5+
use std::fs;
6+
use std::borrow::Cow;
7+
use serde_json;
8+
use packageurl::PackageUrl;
9+
use std::str::FromStr;
410

5-
use super::testcase::SpecTestCase;
6-
use packageurl::PackageUrl;
7-
use std::borrow::Cow;
8-
use std::str::FromStr;
9-
use std::sync::LazyLock;
1011

11-
static TEST_CASE: LazyLock<SpecTestCase<'static>> =
12-
LazyLock::new(|| SpecTestCase::new($desc));
12+
pub fn run_parse_test(case: &SpecTestCase) {
13+
if let PurlOrString::String(input) = &case.input {
14+
if let Ok(purl) = PackageUrl::from_str(input) {
15+
assert!(!case.expected_failure, "Expected failure: but parsing succeeded for PURL: {}", input);
1316

14-
#[test]
15-
fn purl_to_components() {
16-
if let Ok(purl) = PackageUrl::from_str(&TEST_CASE.purl) {
17-
assert!(!TEST_CASE.is_invalid);
18-
assert_eq!(TEST_CASE.ty.as_ref().unwrap().as_ref(), purl.ty());
19-
assert_eq!(TEST_CASE.name.as_ref().unwrap().as_ref(), purl.name());
20-
assert_eq!(
21-
TEST_CASE.namespace.as_ref().map(Cow::as_ref),
22-
purl.namespace()
23-
);
24-
assert_eq!(TEST_CASE.version.as_ref().map(Cow::as_ref), purl.version());
25-
assert_eq!(TEST_CASE.subpath.as_ref().map(Cow::as_ref), purl.subpath());
26-
if let Some(ref quals) = TEST_CASE.qualifiers {
27-
assert_eq!(quals, purl.qualifiers());
28-
} else {
29-
assert!(purl.qualifiers().is_empty());
30-
}
17+
if let Some(PurlOrString::PurlComponent(expected)) = &case.expected_output {
18+
assert_eq!(Some(purl.ty()), expected.ty.as_ref().map(Cow::as_ref));
19+
assert_eq!(Some(purl.name()), expected.name.as_ref().map(Cow::as_ref));
20+
assert_eq!(purl.namespace(), expected.namespace.as_ref().map(Cow::as_ref));
21+
assert_eq!(purl.version(), expected.version.as_ref().map(Cow::as_ref));
22+
assert_eq!(purl.subpath(), expected.subpath.as_ref().map(Cow::as_ref));
23+
24+
if let Some(ref expected_quals) = expected.qualifiers {
25+
assert_eq!(purl.qualifiers(), expected_quals);
3126
} else {
32-
assert!(TEST_CASE.is_invalid);
27+
assert!(purl.qualifiers().is_empty());
3328
}
29+
} else {
30+
panic!("Expected PurlComponent as expected_output for: {}", case.description);
3431
}
32+
} else {
33+
assert!(case.expected_failure, "Unexpected parse failure: {}", case.description);
34+
}
35+
}
36+
}
3537

36-
#[test]
37-
fn components_to_canonical() {
38-
if TEST_CASE.is_invalid {
39-
return;
40-
}
4138

42-
let mut purl = PackageUrl::new(
43-
TEST_CASE.ty.as_ref().unwrap().clone(),
44-
TEST_CASE.name.as_ref().unwrap().clone(),
45-
)
46-
.unwrap();
39+
pub fn run_build_test(case: &SpecTestCase) {
40+
let PurlOrString::PurlComponent(ref input) = case.input else {
41+
panic!("Expected PurlComponent as input for build test: {}", case.description);
42+
};
4743

48-
if let Some(ref ns) = TEST_CASE.namespace {
49-
purl.with_namespace(ns.as_ref());
50-
}
44+
if input.ty.is_none() || input.name.is_none() {
45+
assert!(case.expected_failure, "Missing type or name, but test not marked as failure: {}", case.description);
46+
return;
47+
}
5148

52-
if let Some(ref v) = TEST_CASE.version {
53-
purl.with_version(v.as_ref());
54-
}
49+
let ty = input.ty.as_ref().unwrap().as_ref();
50+
let name = input.name.as_ref().unwrap().as_ref();
5551

56-
if let Some(ref sp) = TEST_CASE.subpath {
57-
purl.with_subpath(sp.as_ref()).unwrap();
58-
}
52+
let purl_result = PackageUrl::new(ty, name);
5953

60-
if let Some(ref quals) = TEST_CASE.qualifiers {
61-
for (k, v) in quals.iter() {
62-
purl.add_qualifier(k.as_ref(), v.as_ref()).unwrap();
63-
}
64-
}
54+
if purl_result.is_err() {
55+
assert!(case.expected_failure, "Purl build failed: {}", case.description);
56+
return;
57+
}
58+
59+
let mut purl = purl_result.unwrap();
60+
if let Some(ref ns) = input.namespace {
61+
purl.with_namespace(ns.as_ref());
62+
}
63+
64+
if let Some(ref v) = input.version {
65+
purl.with_version(v.as_ref());
66+
}
67+
68+
if let Some(ref sp) = input.subpath {
69+
purl.with_subpath(sp.as_ref()).unwrap();
70+
}
6571

66-
assert_eq!(
67-
TEST_CASE.canonical_purl.as_ref().unwrap(),
68-
&purl.to_string()
69-
);
72+
if let Some(ref quals) = input.qualifiers {
73+
for (k, v) in quals.iter() {
74+
if let Err(_) = purl.add_qualifier(k.as_ref(), v.as_ref()) {
75+
assert!(case.expected_failure, "add_qualifier failed unexpectedly");
76+
return;
7077
}
78+
}
79+
}
7180

72-
#[test]
73-
fn canonical_to_canonical() {
74-
if TEST_CASE.is_invalid {
75-
return;
76-
}
81+
assert!(!case.expected_failure, "Test was expected to fail but succeeded: {}", case.description);
82+
if let Some(PurlOrString::String(expected)) = &case.expected_output {
83+
assert_eq!(&purl.to_string(), expected);
84+
} else {
85+
panic!("Expected String as expected_output for build test: {}", case.description);
86+
}
87+
}
7788

78-
let purl =
79-
PackageUrl::from_str(&TEST_CASE.canonical_purl.as_ref().unwrap()).unwrap();
80-
assert_eq!(
81-
TEST_CASE.canonical_purl.as_ref().unwrap(),
82-
&purl.to_string()
83-
);
89+
pub fn run_roundtrip_test(case: &SpecTestCase) {
90+
let input = match &case.input {
91+
PurlOrString::String(s) => s,
92+
_ => panic!("Input must be a string: {}", case.description),
93+
};
94+
95+
match PackageUrl::from_str(input) {
96+
Ok(purl) => {
97+
assert!(!case.expected_failure, "Test was expected to fail but succeeded: {}", case.description);
98+
if let Some(PurlOrString::String(expected)) = &case.expected_output {
99+
assert_eq!(&purl.to_string(), expected);
84100
}
101+
}
102+
Err(_) => {
103+
assert!(case.expected_failure, "Failed to create PURL for: {}", input);
104+
}
105+
}
106+
}
85107

86-
#[test]
87-
fn purl_to_canonical() {
88-
if TEST_CASE.is_invalid {
89-
return;
90-
}
91-
let purl = PackageUrl::from_str(&TEST_CASE.purl).unwrap();
92-
assert_eq!(
93-
TEST_CASE.canonical_purl.as_ref().unwrap(),
94-
&purl.to_string()
95-
)
108+
109+
pub fn run_tests_from_spec(path: &Path) {
110+
let data = fs::read(path).expect("Failed to read test file");
111+
let suite: TestSuite = serde_json::from_slice(&data).expect("Invalid test file");
112+
113+
for case in suite.tests {
114+
115+
match case.test_type.as_ref() {
116+
"parse" => {
117+
run_parse_test(&case);
118+
}
119+
"build" => {
120+
run_build_test(&case);
121+
}
122+
"roundtrip" => {
123+
run_roundtrip_test(&case);
124+
}
125+
other => {
126+
println!("Unknown test type '{}', skipping: {}", other, case.description);
96127
}
97128
}
129+
}
130+
}
131+
132+
#[macro_export]
133+
macro_rules! generate_json_tests {
134+
($($test_name:ident => $file_path:expr),* $(,)?) => {
135+
$(
136+
#[test]
137+
fn $test_name() {
138+
crate::spec::macros::run_tests_from_spec(std::path::Path::new($file_path));
139+
}
140+
)*
98141
};
99142
}

tests/spec/mod.rs

Lines changed: 37 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,40 @@
44
mod macros;
55
mod testcase;
66

7-
spec_tests!(type_required, "a type is always required");
8-
spec_tests!(scheme_required, "a scheme is always required");
9-
spec_tests!(name_required, "a name is required");
10-
spec_tests!(invalid_qualifier_key, "checks for invalid qualifier keys");
11-
spec_tests!(gem, "Java gem can use a qualifier");
12-
spec_tests!(npm, "npm can be scoped");
13-
spec_tests!(rpm, "rpm often use qualifiers");
14-
spec_tests!(nuget, "nuget names are case sensitive");
15-
spec_tests!(pypi, "pypi names have special rules and not case sensitive");
16-
spec_tests!(debian, "debian can use qualifiers");
17-
spec_tests!(bitbucket, "bitbucket namespace and name should be lowercased");
18-
spec_tests!(github, "github namespace and name should be lowercased");
19-
spec_tests!(docker, "docker uses qualifiers and hash image id as versions");
20-
spec_tests!(maven, "valid maven purl");
21-
spec_tests!(maven_basic, "basic valid maven purl without version");
22-
spec_tests!(maven_case_sensitive, "valid maven purl with case sensitive namespace and name");
23-
spec_tests!(maven_space, "valid maven purl containing a space in the version and qualifier");
24-
spec_tests!(go_subpath, "valid go purl without version and with subpath");
25-
spec_tests!(go_version, "valid go purl with version and subpath");
26-
spec_tests!(maven_qualifiers, "maven often uses qualifiers");
27-
spec_tests!(maven_pom, "maven pom reference");
28-
spec_tests!(maven_type, "maven can come with a type qualifier");
29-
spec_tests!(simple_slash, "slash / after scheme is not significant");
30-
spec_tests!(double_slash, "double slash // after scheme is not significant");
31-
spec_tests!(triple_slash, "slash /// after type is not significant");
7+
8+
generate_json_tests! {
9+
alpm_test => "tests/spec/purl-spec/tests/types/alpm-test.json",
10+
apk_test => "tests/spec/purl-spec/tests/types/apk-test.json",
11+
bintray_test => "tests/spec/purl-spec/tests/types/bintray-test.json",
12+
bitbucket_test => "tests/spec/purl-spec/tests/types/bitbucket-test.json",
13+
bitnami_test => "tests/spec/purl-spec/tests/types/bitnami-test.json",
14+
cargo_test => "tests/spec/purl-spec/tests/types/cargo-test.json",
15+
cocoapods_test => "tests/spec/purl-spec/tests/types/cocoapods-test.json",
16+
composer_test => "tests/spec/purl-spec/tests/types/composer-test.json",
17+
conan_test => "tests/spec/purl-spec/tests/types/conan-test.json",
18+
conda_test => "tests/spec/purl-spec/tests/types/conda-test.json",
19+
cpan_test => "tests/spec/purl-spec/tests/types/cpan-test.json",
20+
cran_test => "tests/spec/purl-spec/tests/types/cran-test.json",
21+
deb_test => "tests/spec/purl-spec/tests/types/deb-test.json",
22+
docker_test => "tests/spec/purl-spec/tests/types/docker-test.json",
23+
gem_test => "tests/spec/purl-spec/tests/types/gem-test.json",
24+
generic_test => "tests/spec/purl-spec/tests/types/generic-test.json",
25+
github_test => "tests/spec/purl-spec/tests/types/github-test.json",
26+
golang_test => "tests/spec/purl-spec/tests/types/golang-test.json",
27+
hackage_test => "tests/spec/purl-spec/tests/types/hackage-test.json",
28+
hex_test => "tests/spec/purl-spec/tests/types/hex-test.json",
29+
huggingface_test => "tests/spec/purl-spec/tests/types/huggingface-test.json",
30+
luarocks_test => "tests/spec/purl-spec/tests/types/luarocks-test.json",
31+
maven_test => "tests/spec/purl-spec/tests/types/maven-test.json",
32+
mlflow_test => "tests/spec/purl-spec/tests/types/mlflow-test.json",
33+
npm_test => "tests/spec/purl-spec/tests/types/npm-test.json",
34+
nuget_test => "tests/spec/purl-spec/tests/types/nuget-test.json",
35+
oci_test => "tests/spec/purl-spec/tests/types/oci-test.json",
36+
pub_test => "tests/spec/purl-spec/tests/types/pub-test.json",
37+
pypi_test => "tests/spec/purl-spec/tests/types/pypi-test.json",
38+
qpkg_test => "tests/spec/purl-spec/tests/types/qpkg-test.json",
39+
rpm_test => "tests/spec/purl-spec/tests/types/rpm-test.json",
40+
swid_test => "tests/spec/purl-spec/tests/types/swid-test.json",
41+
swift_test => "tests/spec/purl-spec/tests/types/swift-test.json",
42+
}
43+

0 commit comments

Comments
 (0)