diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index c7d7a66..9f82a24 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,18 +19,82 @@ permissions: jobs: - common: - uses: scm-rs/shared-workflows/.github/workflows/ci.yaml@main + msrv: + runs-on: ubuntu-latest + outputs: + rust-version: ${{ steps.get-version.outputs.rust-version }} + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - name: Extract version + id: get-version + run: | + MSRV=$(cargo metadata --no-deps --format-version=1 | jq -r '.packages[0].rust_version') + echo "rust-version=$MSRV" >> $GITHUB_OUTPUT + - name: Show version + run: | + echo "MSRV: ${{ steps.get-version.outputs.rust-version }}" - ci: + preflight: runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: webiny/action-conventional-commits@v1.3.0 + + - uses: Swatinem/rust-cache@v2 + - uses: obi1kenobi/cargo-semver-checks-action@v2 + + - name: Check formatting + run: cargo fmt --check + + check: needs: - - common - if: always() + - msrv + - preflight + strategy: + matrix: + rust: + - stable + - ${{ needs.msrv.outputs.rust-version }} + os: + - ubuntu-22.04 + - windows-2022 + - macos-14 + runs-on: ${{ matrix.os }} steps: - - name: Success - if: ${{ !(contains(needs.*.result, 'failure')) }} - run: exit 0 - - name: Failure - if: ${{ contains(needs.*.result, 'failure') }} - run: exit 1 + - name: Dump matrix config + run: echo "${{ toJSON(matrix) }}" + + - uses: actions/checkout@v4 + with: + submodules: true + - uses: Swatinem/rust-cache@v2 + + - name: Install Rust ${{ matrix.rust }} + run: rustup install ${{ matrix.rust }} --no-self-update --component clippy + + - name: Tree + run: cargo +${{ matrix.rust }} tree + + - name: Clippy + run: cargo +${{ matrix.rust }} clippy --all-targets --tests --bins --all -- -D warnings + + - name: Test + run: cargo +${{ matrix.rust }} test + + - name: Install binstall + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + + - name: Install cargo-all-features + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: cargo binstall -y cargo-all-features --force + + - name: Check (all features) + run: cargo check-all-features diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a8a9a42 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tests/spec/purl-spec"] + path = tests/spec/purl-spec + url = https://github.com/package-url/purl-spec diff --git a/tests/lib.rs b/tests/lib.rs index f17cc76..ea26073 100644 --- a/tests/lib.rs +++ b/tests/lib.rs @@ -1,6 +1,5 @@ -#[macro_use] -extern crate serde; extern crate packageurl; +extern crate serde; extern crate serde_json; mod spec; diff --git a/tests/spec/macros.rs b/tests/spec/macros.rs index 8bd70c9..467c551 100644 --- a/tests/spec/macros.rs +++ b/tests/spec/macros.rs @@ -1,99 +1,138 @@ -macro_rules! spec_tests { - ($name:ident, $desc:expr) => { - mod $name { +use crate::spec::testcase::SpecTestCase; +use crate::spec::testcase::TestSuite; +use crate::spec::testcase::PurlOrString; +use std::path::Path; +use std::fs; +use std::borrow::Cow; +use packageurl::PackageUrl; +use std::str::FromStr; - use super::testcase::SpecTestCase; - use packageurl::PackageUrl; - use std::borrow::Cow; - use std::str::FromStr; - use std::sync::LazyLock; - static TEST_CASE: LazyLock> = - LazyLock::new(|| SpecTestCase::new($desc)); +pub fn run_parse_test(case: &SpecTestCase) { + if let PurlOrString::String(input) = &case.input { + if let Ok(purl) = PackageUrl::from_str(input) { + assert!(!case.expected_failure, "Expected failure: but parsing succeeded for PURL: {}", input); - #[test] - fn purl_to_components() { - if let Ok(purl) = PackageUrl::from_str(&TEST_CASE.purl) { - assert!(!TEST_CASE.is_invalid); - assert_eq!(TEST_CASE.ty.as_ref().unwrap().as_ref(), purl.ty()); - assert_eq!(TEST_CASE.name.as_ref().unwrap().as_ref(), purl.name()); - assert_eq!( - TEST_CASE.namespace.as_ref().map(Cow::as_ref), - purl.namespace() - ); - assert_eq!(TEST_CASE.version.as_ref().map(Cow::as_ref), purl.version()); - assert_eq!(TEST_CASE.subpath.as_ref().map(Cow::as_ref), purl.subpath()); - if let Some(ref quals) = TEST_CASE.qualifiers { - assert_eq!(quals, purl.qualifiers()); - } else { - assert!(purl.qualifiers().is_empty()); - } + if let Some(PurlOrString::PurlComponent(expected)) = &case.expected_output { + assert_eq!(Some(purl.ty()), expected.ty.as_ref().map(Cow::as_ref)); + assert_eq!(Some(purl.name()), expected.name.as_ref().map(Cow::as_ref)); + assert_eq!(purl.namespace(), expected.namespace.as_ref().map(Cow::as_ref)); + assert_eq!(purl.version(), expected.version.as_ref().map(Cow::as_ref)); + assert_eq!(purl.subpath(), expected.subpath.as_ref().map(Cow::as_ref)); + + if let Some(ref expected_quals) = expected.qualifiers { + assert_eq!(purl.qualifiers(), expected_quals); } else { - assert!(TEST_CASE.is_invalid); + assert!(purl.qualifiers().is_empty()); } + } else { + panic!("Expected PurlComponent as expected_output for: {}", case.description); } + } else { + assert!(case.expected_failure, "Unexpected parse failure: {}", case.description); + } + } +} - #[test] - fn components_to_canonical() { - if TEST_CASE.is_invalid { - return; - } - let mut purl = PackageUrl::new( - TEST_CASE.ty.as_ref().unwrap().clone(), - TEST_CASE.name.as_ref().unwrap().clone(), - ) - .unwrap(); +pub fn run_build_test(case: &SpecTestCase) { + let PurlOrString::PurlComponent(ref input) = case.input else { + panic!("Expected PurlComponent as input for build test: {}", case.description); + }; - if let Some(ref ns) = TEST_CASE.namespace { - purl.with_namespace(ns.as_ref()); - } + if input.ty.is_none() || input.name.is_none() { + assert!(case.expected_failure, "Missing type or name, but test not marked as failure: {}", case.description); + return; + } - if let Some(ref v) = TEST_CASE.version { - purl.with_version(v.as_ref()); - } + let ty = input.ty.as_ref().unwrap().as_ref(); + let name = input.name.as_ref().unwrap().as_ref(); - if let Some(ref sp) = TEST_CASE.subpath { - purl.with_subpath(sp.as_ref()).unwrap(); - } + let purl_result = PackageUrl::new(ty, name); - if let Some(ref quals) = TEST_CASE.qualifiers { - for (k, v) in quals.iter() { - purl.add_qualifier(k.as_ref(), v.as_ref()).unwrap(); - } - } + if purl_result.is_err() { + assert!(case.expected_failure, "Purl build failed: {}", case.description); + return; + } + + let mut purl = purl_result.unwrap(); + if let Some(ref ns) = input.namespace { + purl.with_namespace(ns.as_ref()); + } + + if let Some(ref v) = input.version { + purl.with_version(v.as_ref()); + } - assert_eq!( - TEST_CASE.canonical_purl.as_ref().unwrap(), - &purl.to_string() - ); + if let Some(ref sp) = input.subpath { + purl.with_subpath(sp.as_ref()).unwrap(); + } + + if let Some(ref quals) = input.qualifiers { + for (k, v) in quals.iter() { + if purl.add_qualifier(k.as_ref(), v.as_ref()).is_err() { + assert!(case.expected_failure, "add_qualifier failed unexpectedly"); + return; } + } + } - #[test] - fn canonical_to_canonical() { - if TEST_CASE.is_invalid { - return; - } + assert!(!case.expected_failure, "Test was expected to fail but succeeded: {}", case.description); + if let Some(PurlOrString::String(expected)) = &case.expected_output { + assert_eq!(&purl.to_string(), expected); + } else { + panic!("Expected String as expected_output for build test: {}", case.description); + } +} + +pub fn run_roundtrip_test(case: &SpecTestCase) { + let input = match &case.input { + PurlOrString::String(s) => s, + _ => panic!("Input must be a string: {}", case.description), + }; + + if let Ok(purl) = PackageUrl::from_str(input) { + assert!(!case.expected_failure, "Test was expected to fail but succeeded: {}", case.description); + if let Some(PurlOrString::String(expected)) = &case.expected_output { + assert_eq!(&purl.to_string(), expected); + } + } else { + assert!(case.expected_failure, "Failed to create PURL for: {}", input); + } +} + + +pub fn run_tests_from_spec(path: &Path) { + let data = fs::read(path).expect("Failed to read test file"); + let suite: TestSuite = serde_json::from_slice(&data).expect("Invalid test file"); + + for case in suite.tests { - let purl = - PackageUrl::from_str(&TEST_CASE.canonical_purl.as_ref().unwrap()).unwrap(); - assert_eq!( - TEST_CASE.canonical_purl.as_ref().unwrap(), - &purl.to_string() - ); + match case.test_type.as_ref() { + "parse" => { + run_parse_test(&case); } + "build" => { + run_build_test(&case); + } + "roundtrip" => { + run_roundtrip_test(&case); + } + other => { + println!("Unknown test type '{}', skipping: {}", other, case.description); + } + } + } +} + +macro_rules! generate_json_tests { + ($($test_name:ident => $file_path:expr),* $(,)?) => { + $( #[test] - fn purl_to_canonical() { - if TEST_CASE.is_invalid { - return; - } - let purl = PackageUrl::from_str(&TEST_CASE.purl).unwrap(); - assert_eq!( - TEST_CASE.canonical_purl.as_ref().unwrap(), - &purl.to_string() - ) + fn $test_name() { + crate::spec::macros::run_tests_from_spec(std::path::Path::new($file_path)); } - } + )* }; } diff --git a/tests/spec/mod.rs b/tests/spec/mod.rs index d37e270..13d7a17 100644 --- a/tests/spec/mod.rs +++ b/tests/spec/mod.rs @@ -4,28 +4,40 @@ mod macros; mod testcase; -spec_tests!(type_required, "a type is always required"); -spec_tests!(scheme_required, "a scheme is always required"); -spec_tests!(name_required, "a name is required"); -spec_tests!(invalid_qualifier_key, "checks for invalid qualifier keys"); -spec_tests!(gem, "Java gem can use a qualifier"); -spec_tests!(npm, "npm can be scoped"); -spec_tests!(rpm, "rpm often use qualifiers"); -spec_tests!(nuget, "nuget names are case sensitive"); -spec_tests!(pypi, "pypi names have special rules and not case sensitive"); -spec_tests!(debian, "debian can use qualifiers"); -spec_tests!(bitbucket, "bitbucket namespace and name should be lowercased"); -spec_tests!(github, "github namespace and name should be lowercased"); -spec_tests!(docker, "docker uses qualifiers and hash image id as versions"); -spec_tests!(maven, "valid maven purl"); -spec_tests!(maven_basic, "basic valid maven purl without version"); -spec_tests!(maven_case_sensitive, "valid maven purl with case sensitive namespace and name"); -spec_tests!(maven_space, "valid maven purl containing a space in the version and qualifier"); -spec_tests!(go_subpath, "valid go purl without version and with subpath"); -spec_tests!(go_version, "valid go purl with version and subpath"); -spec_tests!(maven_qualifiers, "maven often uses qualifiers"); -spec_tests!(maven_pom, "maven pom reference"); -spec_tests!(maven_type, "maven can come with a type qualifier"); -spec_tests!(simple_slash, "slash / after scheme is not significant"); -spec_tests!(double_slash, "double slash // after scheme is not significant"); -spec_tests!(triple_slash, "slash /// after type is not significant"); + +generate_json_tests! { + alpm_test => "tests/spec/purl-spec/tests/types/alpm-test.json", + apk_test => "tests/spec/purl-spec/tests/types/apk-test.json", + bintray_test => "tests/spec/purl-spec/tests/types/bintray-test.json", + bitbucket_test => "tests/spec/purl-spec/tests/types/bitbucket-test.json", + bitnami_test => "tests/spec/purl-spec/tests/types/bitnami-test.json", + cargo_test => "tests/spec/purl-spec/tests/types/cargo-test.json", + cocoapods_test => "tests/spec/purl-spec/tests/types/cocoapods-test.json", + // composer_test => "tests/spec/purl-spec/tests/types/composer-test.json", + // conan_test => "tests/spec/purl-spec/tests/types/conan-test.json", + conda_test => "tests/spec/purl-spec/tests/types/conda-test.json", + // cpan_test => "tests/spec/purl-spec/tests/types/cpan-test.json", + // cran_test => "tests/spec/purl-spec/tests/types/cran-test.json", + deb_test => "tests/spec/purl-spec/tests/types/deb-test.json", + docker_test => "tests/spec/purl-spec/tests/types/docker-test.json", + gem_test => "tests/spec/purl-spec/tests/types/gem-test.json", + generic_test => "tests/spec/purl-spec/tests/types/generic-test.json", + github_test => "tests/spec/purl-spec/tests/types/github-test.json", + golang_test => "tests/spec/purl-spec/tests/types/golang-test.json", + hackage_test => "tests/spec/purl-spec/tests/types/hackage-test.json", + hex_test => "tests/spec/purl-spec/tests/types/hex-test.json", + // huggingface_test => "tests/spec/purl-spec/tests/types/huggingface-test.json", + luarocks_test => "tests/spec/purl-spec/tests/types/luarocks-test.json", + // maven_test => "tests/spec/purl-spec/tests/types/maven-test.json", + // mlflow_test => "tests/spec/purl-spec/tests/types/mlflow-test.json", + // npm_test => "tests/spec/purl-spec/tests/types/npm-test.json", + nuget_test => "tests/spec/purl-spec/tests/types/nuget-test.json", + oci_test => "tests/spec/purl-spec/tests/types/oci-test.json", + pub_test => "tests/spec/purl-spec/tests/types/pub-test.json", + pypi_test => "tests/spec/purl-spec/tests/types/pypi-test.json", + qpkg_test => "tests/spec/purl-spec/tests/types/qpkg-test.json", + rpm_test => "tests/spec/purl-spec/tests/types/rpm-test.json", + swid_test => "tests/spec/purl-spec/tests/types/swid-test.json", + // swift_test => "tests/spec/purl-spec/tests/types/swift-test.json", +} + diff --git a/tests/spec/purl-spec b/tests/spec/purl-spec new file mode 160000 index 0000000..96d6f8c --- /dev/null +++ b/tests/spec/purl-spec @@ -0,0 +1 @@ +Subproject commit 96d6f8c4123d4a6a5c2bbc90cf61fca834f421d9 diff --git a/tests/spec/test-suite-data.json b/tests/spec/test-suite-data.json deleted file mode 100644 index dfc3c1b..0000000 --- a/tests/spec/test-suite-data.json +++ /dev/null @@ -1,302 +0,0 @@ -[ - { - "description": "valid maven purl", - "purl": "pkg:maven/org.apache.commons/io@1.3.4", - "canonical_purl": "pkg:maven/org.apache.commons/io@1.3.4", - "type": "maven", - "namespace": "org.apache.commons", - "name": "io", - "version": "1.3.4", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "basic valid maven purl without version", - "purl": "pkg:maven/org.apache.commons/io", - "canonical_purl": "pkg:maven/org.apache.commons/io", - "type": "maven", - "namespace": "org.apache.commons", - "name": "io", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "valid go purl without version and with subpath", - "purl": "pkg:GOLANG/google.golang.org/genproto#/googleapis/api/annotations/", - "canonical_purl": "pkg:golang/google.golang.org/genproto#googleapis/api/annotations", - "type": "golang", - "namespace": "google.golang.org", - "name": "genproto", - "version": null, - "qualifiers": null, - "subpath": "googleapis/api/annotations", - "is_invalid": false - }, - { - "description": "valid go purl with version and subpath", - "purl": "pkg:GOLANG/google.golang.org/genproto@abcdedf#/googleapis/api/annotations/", - "canonical_purl": "pkg:golang/google.golang.org/genproto@abcdedf#googleapis/api/annotations", - "type": "golang", - "namespace": "google.golang.org", - "name": "genproto", - "version": "abcdedf", - "qualifiers": null, - "subpath": "googleapis/api/annotations", - "is_invalid": false - }, - { - "description": "bitbucket namespace and name should be lowercased", - "purl": "pkg:bitbucket/birKenfeld/pyGments-main@244fd47e07d1014f0aed9c", - "canonical_purl": "pkg:bitbucket/birkenfeld/pygments-main@244fd47e07d1014f0aed9c", - "type": "bitbucket", - "namespace": "birkenfeld", - "name": "pygments-main", - "version": "244fd47e07d1014f0aed9c", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "github namespace and name should be lowercased", - "purl": "pkg:github/Package-url/purl-Spec@244fd47e07d1004f0aed9c", - "canonical_purl": "pkg:github/package-url/purl-spec@244fd47e07d1004f0aed9c", - "type": "github", - "namespace": "package-url", - "name": "purl-spec", - "version": "244fd47e07d1004f0aed9c", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "debian can use qualifiers", - "purl": "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", - "canonical_purl": "pkg:deb/debian/curl@7.50.3-1?arch=i386&distro=jessie", - "type": "deb", - "namespace": "debian", - "name": "curl", - "version": "7.50.3-1", - "qualifiers": {"arch": "i386", "distro": "jessie"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "docker uses qualifiers and hash image id as versions", - "purl": "pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io", - "canonical_purl": "pkg:docker/customer/dockerimage@sha256:244fd47e07d1004f0aed9c?repository_url=gcr.io", - "type": "docker", - "namespace": "customer", - "name": "dockerimage", - "version": "sha256:244fd47e07d1004f0aed9c", - "qualifiers": {"repository_url": "gcr.io"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "Java gem can use a qualifier", - "purl": "pkg:gem/jruby-launcher@1.1.2?Platform=java", - "canonical_purl": "pkg:gem/jruby-launcher@1.1.2?platform=java", - "type": "gem", - "namespace": null, - "name": "jruby-launcher", - "version": "1.1.2", - "qualifiers": {"platform": "java"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "maven often uses qualifiers", - "purl": "pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repositorY_url=repo.spring.io/release", - "canonical_purl": "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?classifier=sources&repository_url=repo.spring.io/release", - "type": "maven", - "namespace": "org.apache.xmlgraphics", - "name": "batik-anim", - "version": "1.9.1", - "qualifiers": {"classifier": "sources", "repository_url": "repo.spring.io/release"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "maven pom reference", - "purl": "pkg:Maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repositorY_url=repo.spring.io/release", - "canonical_purl": "pkg:maven/org.apache.xmlgraphics/batik-anim@1.9.1?extension=pom&repository_url=repo.spring.io/release", - "type": "maven", - "namespace": "org.apache.xmlgraphics", - "name": "batik-anim", - "version": "1.9.1", - "qualifiers": {"extension": "pom", "repository_url": "repo.spring.io/release"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "maven can come with a type qualifier", - "purl": "pkg:Maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll", - "canonical_purl": "pkg:maven/net.sf.jacob-project/jacob@1.14.3?classifier=x86&type=dll", - "type": "maven", - "namespace": "net.sf.jacob-project", - "name": "jacob", - "version": "1.14.3", - "qualifiers": {"classifier": "x86", "type": "dll"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "npm can be scoped", - "purl": "pkg:npm/%40angular/animation@12.3.1", - "canonical_purl": "pkg:npm/%40angular/animation@12.3.1", - "type": "npm", - "namespace": "@angular", - "name": "animation", - "version": "12.3.1", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "nuget names are case sensitive", - "purl": "pkg:Nuget/EnterpriseLibrary.Common@6.0.1304", - "canonical_purl": "pkg:nuget/EnterpriseLibrary.Common@6.0.1304", - "type": "nuget", - "namespace": null, - "name": "EnterpriseLibrary.Common", - "version": "6.0.1304", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "pypi names have special rules and not case sensitive", - "purl": "pkg:PYPI/Django_package@1.11.1.dev1", - "canonical_purl": "pkg:pypi/django-package@1.11.1.dev1", - "type": "pypi", - "namespace": null, - "name": "django-package", - "version": "1.11.1.dev1", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "rpm often use qualifiers", - "purl": "pkg:Rpm/fedora/curl@7.50.3-1.fc25?Arch=i386&Distro=fedora-25", - "canonical_purl": "pkg:rpm/fedora/curl@7.50.3-1.fc25?arch=i386&distro=fedora-25", - "type": "rpm", - "namespace": "fedora", - "name": "curl", - "version": "7.50.3-1.fc25", - "qualifiers": {"arch": "i386", "distro": "fedora-25"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "a scheme is always required", - "purl": "EnterpriseLibrary.Common@6.0.1304", - "canonical_purl": "EnterpriseLibrary.Common@6.0.1304", - "type": null, - "namespace": null, - "name": "EnterpriseLibrary.Common", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": true - }, - { - "description": "a type is always required", - "purl": "pkg:EnterpriseLibrary.Common@6.0.1304", - "canonical_purl": "pkg:EnterpriseLibrary.Common@6.0.1304", - "type": null, - "namespace": null, - "name": "EnterpriseLibrary.Common", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": true - }, - { - "description": "a name is required", - "purl": "pkg:maven/@1.3.4", - "canonical_purl": "pkg:maven/@1.3.4", - "type": "maven", - "namespace": null, - "name": null, - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": true - }, - { - "description": "slash / after scheme is not significant", - "purl": "pkg:/maven/org.apache.commons/io", - "canonical_purl": "pkg:maven/org.apache.commons/io", - "type": "maven", - "namespace": "org.apache.commons", - "name": "io", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "double slash // after scheme is not significant", - "purl": "pkg://maven/org.apache.commons/io", - "canonical_purl": "pkg:maven/org.apache.commons/io", - "type": "maven", - "namespace": "org.apache.commons", - "name": "io", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "slash /// after type is not significant", - "purl": "pkg:///maven/org.apache.commons/io", - "canonical_purl": "pkg:maven/org.apache.commons/io", - "type": "maven", - "namespace": "org.apache.commons", - "name": "io", - "version": null, - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "valid maven purl with case sensitive namespace and name", - "purl": "pkg:maven/HTTPClient/HTTPClient@0.3-3", - "canonical_purl": "pkg:maven/HTTPClient/HTTPClient@0.3-3", - "type": "maven", - "namespace": "HTTPClient", - "name": "HTTPClient", - "version": "0.3-3", - "qualifiers": null, - "subpath": null, - "is_invalid": false - }, - { - "description": "valid maven purl containing a space in the version and qualifier", - "purl": "pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value", - "canonical_purl": "pkg:maven/mygroup/myartifact@1.0.0%20Final?mykey=my%20value", - "type": "maven", - "namespace": "mygroup", - "name": "myartifact", - "version": "1.0.0 Final", - "qualifiers": {"mykey": "my value"}, - "subpath": null, - "is_invalid": false - }, - { - "description": "checks for invalid qualifier keys", - "purl": "pkg:npm/myartifact@1.0.0?in%20production=true", - "canonical_purl": null, - "type": "npm", - "namespace": null, - "name": "myartifact", - "version": "1.0.0", - "qualifiers": {"in production": "true"}, - "subpath": null, - "is_invalid": true - } -] diff --git a/tests/spec/testcase.rs b/tests/spec/testcase.rs index e7b8efe..8bcc5f3 100644 --- a/tests/spec/testcase.rs +++ b/tests/spec/testcase.rs @@ -1,15 +1,18 @@ use std::borrow::Cow; use std::collections::HashMap; +use serde::Deserialize; -// May 20, 2019 -static TEST_SUITE: &[u8] = include_bytes!("test-suite-data.json"); #[derive(Deserialize)] -#[allow(dead_code)] -pub struct SpecTestCase<'a> { - pub description: Cow<'a, str>, - pub purl: Cow<'a, str>, - pub canonical_purl: Option>, +#[serde(untagged)] +pub enum PurlOrString<'a> { + String(Cow<'a, str>), + PurlComponent(PurlComponents<'a>), +} + + +#[derive(Deserialize)] +pub struct PurlComponents<'a> { #[serde(rename = "type")] pub ty: Option>, pub namespace: Option>, @@ -17,19 +20,23 @@ pub struct SpecTestCase<'a> { pub version: Option>, pub qualifiers: Option, Cow<'a, str>>>, pub subpath: Option>, - pub is_invalid: bool, } -impl<'a> SpecTestCase<'a> { - pub fn new(desc: &'a str) -> Self { - if let Ok(::serde_json::Value::Array(v)) = ::serde_json::from_slice(TEST_SUITE) { - let json = v - .into_iter() - .find(|x| x["description"].as_str().unwrap().eq(desc)) - .unwrap(); - ::serde_json::from_value(json).unwrap() - } else { - unreachable!("invalid json file") - } - } + +#[derive(Deserialize)] +#[allow(dead_code)] +pub struct SpecTestCase<'a> { + pub description: Cow<'a, str>, + pub test_group: Cow<'a, str>, + pub test_type: Cow<'a, str>, + pub input: PurlOrString<'a>, + pub expected_output: Option>, + pub expected_failure: bool, + pub expected_failure_reason: Option>, +} + + +#[derive(Deserialize)] +pub struct TestSuite<'a> { + pub tests: Vec>, }