From 9b22c66b41e290d71d9279847e8f36dabcb6fe4b Mon Sep 17 00:00:00 2001 From: Kai Hudalla Date: Tue, 8 Apr 2025 10:17:00 +0200 Subject: [PATCH] Use Gherkin to define TCK for UUID and UUri serialization The up-tck component had been using Gherkin for defining the TCK's test scenarios. The idea was that these could be used for verifying a language library's compliance with the specification. This is a first step in the direction described in https://github.com/eclipse-uprotocol/up-tck/issues/116. The TCK's test scenarios have been adapted to cover more cases and, in particular, also cover failure cases. The corresponding unit tests have been replaced by Cucumber based tests and the Gherkin files have been amended with OpenFastTrace specification identifiers and are now also included when doing the requirements tracing run. --- .env.oft-current | 2 +- .env.oft-latest | 2 +- .github/workflows/check.yaml | 33 +- Cargo.lock | 922 ++++++++++++++++++- Cargo.toml | 23 +- features/uuid/protobuf_serialization.feature | 33 + features/uuid/string_serialization.feature | 64 ++ features/uuri/pattern_matching.feature | 57 ++ features/uuri/protobuf_serialization.feature | 46 + features/uuri/uri_serialization.feature | 85 ++ src/uri.rs | 140 --- src/uuid.rs | 11 - tests/common/mod.rs | 32 + tests/tck_uuid.rs | 128 +++ tests/tck_uuri.rs | 185 ++++ 15 files changed, 1570 insertions(+), 193 deletions(-) create mode 100644 features/uuid/protobuf_serialization.feature create mode 100644 features/uuid/string_serialization.feature create mode 100644 features/uuri/pattern_matching.feature create mode 100644 features/uuri/protobuf_serialization.feature create mode 100644 features/uuri/uri_serialization.feature create mode 100644 tests/common/mod.rs create mode 100644 tests/tck_uuid.rs create mode 100644 tests/tck_uuri.rs diff --git a/.env.oft-current b/.env.oft-current index e9652d6..e4d297d 100644 --- a/.env.oft-current +++ b/.env.oft-current @@ -17,7 +17,7 @@ UP_SPEC_FILE_PATTERNS="up-spec/*.adoc up-spec/*.md up-spec/basics up-spec/up-l1/ # The file patterns that specify this component's resources which contain specification items # that cover the requirements -COMPONENT_FILE_PATTERNS="*.adoc *.md *.rs .github examples src tests tools" +COMPONENT_FILE_PATTERNS="*.adoc *.md *.rs .github examples features src tests tools" OFT_FILE_PATTERNS="$UP_SPEC_FILE_PATTERNS $COMPONENT_FILE_PATTERNS" OFT_TAGS="" diff --git a/.env.oft-latest b/.env.oft-latest index ef99f5e..6abb155 100644 --- a/.env.oft-latest +++ b/.env.oft-latest @@ -17,7 +17,7 @@ UP_SPEC_FILE_PATTERNS="up-spec/*.adoc up-spec/*.md up-spec/basics up-spec/up-l1/ # The file patterns that specify this component's resources which contain specification items # that cover the requirements -COMPONENT_FILE_PATTERNS="*.adoc *.md *.rs .github examples src tests tools" +COMPONENT_FILE_PATTERNS="*.adoc *.md *.rs .github examples features src tests tools" OFT_FILE_PATTERNS="$UP_SPEC_FILE_PATTERNS $COMPONENT_FILE_PATTERNS" OFT_TAGS="_,LanguageLibrary" diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 11f04c7..2d9efd2 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -23,6 +23,7 @@ on: pull_request: paths: - "src/**" + - "tests/**" - "Cargo.*" - "build.rs" - "deny.toml" @@ -134,13 +135,11 @@ jobs: cargo hack check --feature-powerset --no-dev-deps # [impl->req~up-language-ci-test~1] - nextest: + test: # Subset of feature-combos, on only one OS - more complete testing in test-featurematrix.yaml outputs: test_results_url: ${{ steps.test_results.outputs.artifact-url }} runs-on: ubuntu-latest - env: - NEXTEST_EXPERIMENTAL_LIBTEST_JSON: 1 strategy: matrix: feature-flags: ["", "--no-default-features", "--all-features"] @@ -151,22 +150,18 @@ jobs: - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.RUST_TOOLCHAIN }} - - uses: Swatinem/rust-cache@v2 - # install tool to convert cargo's JSON test output to JUNIT format - - run: | - cargo install cargo2junit - # Using nextest because it's faster than built-in test - - uses: taiki-e/install-action@nextest - - name: Run cargo nextest + + - name: Run lib tests + run: | + mkdir -p ${GITHUB_WORKSPACE}/target + RUSTC_BOOTSTRAP=1 cargo test --no-fail-fast --lib ${{ matrix.feature-flags }} -- -Z unstable-options --format junit --report-time > ${GITHUB_WORKSPACE}/target/lib-test-results.xml + - name: Run doc tests run: | - cargo nextest run ${{ matrix.feature-flags }} --profile ci - - name: Run doctests + RUSTC_BOOTSTRAP=1 cargo test --no-fail-fast --doc ${{ matrix.feature-flags }} -- -Z unstable-options --format junit --report-time > ${GITHUB_WORKSPACE}/target/doc-test-results.xml + - name: Run TCK tests run: | - # we use tee to let cargo test print to the console - RUSTC_BOOTSTRAP=1 cargo test --doc ${{ matrix.feature-flags }} -- -Z unstable-options --format json --report-time | tee target/doctest-results.json - # write output to same directory as the one used by cargo nextest in order - # to flatten the directory hierarchy when uploading the test results - cat target/doctest-results.json | cargo2junit > target/nextest/ci/doctest-results.xml + # the Cucumber based tests write a results file in JUnit format to "tck-[test_name]-results.xml" + cargo test --no-fail-fast --test 'tck_*' ${{ matrix.feature-flags }} -- --junit-out-folder=${GITHUB_WORKSPACE}/target - name: Upload all-features test results artifact id: test_results @@ -174,5 +169,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-results - # this will include the nextest and doctest result files in the archive's root folder - path: target/nextest/ci/*-results.xml + # include all test result files + path: target/*-results.xml diff --git a/Cargo.lock b/Cargo.lock index 7df5712..a8e5481 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -26,12 +26,56 @@ dependencies = [ "memchr", ] +[[package]] +name = "anstream" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" +dependencies = [ + "anstyle", + "once_cell", + "windows-sys 0.59.0", +] + [[package]] name = "anyhow" version = "1.0.95" @@ -46,9 +90,15 @@ checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "backtrace" version = "0.3.74" @@ -70,6 +120,22 @@ version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +[[package]] +name = "bstr" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bytecount" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce89b21cab1437276d2650d57e971f9d548a2d9037cc231abdc0562b97498ce" + [[package]] name = "byteorder" version = "1.5.0" @@ -88,18 +154,206 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "clap_lex" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" + +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + +[[package]] +name = "console" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" +dependencies = [ + "encode_unicode", + "libc", + "once_cell", + "unicode-width", + "windows-sys 0.59.0", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "cucumber" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cd12917efc3a8b069a4975ef3cb2f2d835d42d04b3814d90838488f9dd9bf69" +dependencies = [ + "anyhow", + "clap", + "console", + "cucumber-codegen", + "cucumber-expressions", + "derive_more", + "drain_filter_polyfill", + "either", + "futures", + "gherkin", + "globwalk", + "humantime", + "inventory", + "itertools", + "junit-report", + "lazy-regex", + "linked-hash-map", + "once_cell", + "pin-project", + "regex", + "sealed", + "smart-default", +] + +[[package]] +name = "cucumber-codegen" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e19cd9e8e7cfd79fbf844eb6a7334117973c01f6bad35571262b00891e60f1c" +dependencies = [ + "cucumber-expressions", + "inflections", + "itertools", + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", + "synthez", +] + +[[package]] +name = "cucumber-expressions" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d794fed319eea24246fb5f57632f7ae38d61195817b7eb659455aa5bdd7c1810" +dependencies = [ + "derive_more", + "either", + "nom", + "nom_locate", + "regex", + "regex-syntax 0.7.5", +] + +[[package]] +name = "deranged" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28cfac68e08048ae1883171632c2aef3ebc555621ae56fbccce1cbf22dd7f058" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derive-getters" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2c35ab6e03642397cdda1dd58abbc05d418aef8e36297f336d5aba060fe8df" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive_more" +version = "0.99.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3da29a38df43d6f156149c9b43ded5e018ddff2a855cf2cfd62e8cd7d079c69f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "downcast" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1435fa1053d8b2fbbe9be7e97eca7f33d37b28409959813daefc1446a14247f1" +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + [[package]] name = "either" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "equivalent" version = "1.0.1" @@ -134,6 +388,95 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -145,18 +488,77 @@ dependencies = [ "wasi", ] +[[package]] +name = "gherkin" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b79820c0df536d1f3a089a2fa958f61cb96ce9e0f3f8f507f5a31179567755" +dependencies = [ + "heck 0.4.1", + "peg", + "quote", + "serde", + "serde_json", + "syn 2.0.96", + "textwrap", + "thiserror", + "typed-builder", +] + [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" +[[package]] +name = "globset" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax 0.8.5", +] + +[[package]] +name = "globwalk" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" +dependencies = [ + "bitflags", + "ignore", + "walkdir", +] + [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "home" version = "0.5.9" @@ -166,6 +568,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "humantime" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" + +[[package]] +name = "ignore" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + [[package]] name = "indexmap" version = "2.7.0" @@ -176,6 +600,77 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inflections" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a257582fdcde896fd96463bf2d40eefea0580021c0712a0e2b028b60b47a837a" + +[[package]] +name = "inventory" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab08d7cd2c5897f2c949e5383ea7c7db03fb19130ffcfbf7eda795137ae3cb83" +dependencies = [ + "rustversion", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "junit-report" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06c3a3342e6720a82d7d179f380e9841b73a1dd49344e33959fdfe571ce56b55" +dependencies = [ + "derive-getters", + "quick-xml", + "strip-ansi-escapes", + "time", +] + +[[package]] +name = "lazy-regex" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60c7310b93682b36b98fa7ea4de998d3463ccbebd94d935d6b48ba5b6ffa7126" +dependencies = [ + "lazy-regex-proc_macros", + "once_cell", + "regex", +] + +[[package]] +name = "lazy-regex-proc_macros" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ba01db5ef81e17eb10a5e0f2109d1b3a3e29bac3070fdbd7d156bf7dbd206a1" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.96", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -188,12 +683,24 @@ version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + [[package]] name = "log" version = "0.4.25" @@ -212,6 +719,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.8.3" @@ -244,9 +757,36 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom_locate" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e3c83c053b0713da60c5b8de47fe8e494fe3ece5267b2f23090a07a053ba8f3" +dependencies = [ + "bytecount", + "memchr", + "nom", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "object" version = "0.36.7" @@ -268,12 +808,71 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4030760ffd992bef45b0ae3f10ce1aba99e33464c90d14dd7c039884963ddc7a" +[[package]] +name = "peg" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f76678828272f177ac33b7e2ac2e3e73cc6c1cd1e3e387928aa69562fa51367" +dependencies = [ + "peg-macros", + "peg-runtime", +] + +[[package]] +name = "peg-macros" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "636d60acf97633e48d266d7415a9355d4389cea327a193f87df395d88cd2b14d" +dependencies = [ + "peg-runtime", + "proc-macro2", + "quote", +] + +[[package]] +name = "peg-runtime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9555b1514d2d99d78150d3c799d4c357a3e2c2a8062cd108e93a06d9057629c5" + +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -427,6 +1026,15 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dc55d7dec32ecaf61e0bd90b3d2392d721a28b95cfd23c3e176eccefbeab2f2" +[[package]] +name = "quick-xml" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1004a344b30a54e2ee58d66a71b32d2db2feb0a31f9a2d302bf0536f15de2a33" +dependencies = [ + "memchr", +] + [[package]] name = "quote" version = "1.0.38" @@ -475,7 +1083,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.8.5", ] [[package]] @@ -486,9 +1094,15 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.8.5", ] +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "regex-syntax" version = "0.8.5" @@ -510,10 +1124,140 @@ dependencies = [ "bitflags", "errno", "libc", - "linux-raw-sys", + "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "sealed" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a8caec23b7800fb97971a1c6ae365b6239aaeddfb934d6265f8505e795699d" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smart-default" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb01866308440fc64d6c44d9e86c5cc17adfe33c4d6eed55da9145044d0ffc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "smawk" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" + +[[package]] +name = "strip-ansi-escapes" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a8f8038e7e7969abb3f1b7c2a811225e9296da208539e0f79c5251d6cac0025" +dependencies = [ + "vte", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.96" @@ -525,6 +1269,39 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synthez" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d2c2202510a1e186e63e596d9318c91a8cbe85cd1a56a7be0c333e5f59ec8d" +dependencies = [ + "syn 2.0.96", + "synthez-codegen", + "synthez-core", +] + +[[package]] +name = "synthez-codegen" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f724aa6d44b7162f3158a57bccd871a77b39a4aef737e01bcdff41f4772c7746" +dependencies = [ + "syn 2.0.96", + "synthez-core", +] + +[[package]] +name = "synthez-core" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78bfa6ec52465e2425fd43ce5bbbe0f0b623964f7c63feb6b10980e816c654ea" +dependencies = [ + "proc-macro2", + "quote", + "sealed", + "syn 2.0.96", +] + [[package]] name = "tempfile" version = "3.15.0" @@ -535,7 +1312,17 @@ dependencies = [ "fastrand", "getrandom", "once_cell", - "rustix", + "rustix 0.38.43", + "windows-sys 0.59.0", +] + +[[package]] +name = "terminal_size" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed" +dependencies = [ + "rustix 1.0.5", "windows-sys 0.59.0", ] @@ -563,7 +1350,7 @@ dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -574,10 +1361,21 @@ checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", "test-case-core", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" +dependencies = [ + "smawk", + "unicode-linebreak", + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.69" @@ -595,7 +1393,38 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", +] + +[[package]] +name = "time" +version = "0.3.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" + +[[package]] +name = "time-macros" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -617,7 +1446,7 @@ checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -640,18 +1469,53 @@ dependencies = [ "once_cell", ] +[[package]] +name = "typed-builder" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fe83c85a85875e8c4cb9ce4a890f05b23d38cd0d47647db7895d3d2a79566d2" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29a3151c41d0b13e3d011f98adc24434560ef06673a155a6c7f66b9879eecce2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "unicode-ident" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +[[package]] +name = "unicode-linebreak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" + +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "up-rust" version = "0.5.0" dependencies = [ "async-trait", "bytes", + "clap", + "cucumber", + "hex", "mediatype", "mockall", "protobuf", @@ -676,6 +1540,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "uuid-simd" version = "0.8.0" @@ -692,6 +1562,25 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" +[[package]] +name = "vte" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "231fdcd7ef3037e8330d8e17e61011a2c244126acc0a982f4040ac3f9f0bc077" +dependencies = [ + "memchr", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -707,7 +1596,16 @@ dependencies = [ "either", "home", "once_cell", - "rustix", + "rustix 0.38.43", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", ] [[package]] @@ -810,5 +1708,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] diff --git a/Cargo.toml b/Cargo.toml index c8ae07f..e2693ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,22 +51,19 @@ protobuf = { version = "3.7.2", features = ["with-bytes"] } rand = { version = "0.8.0" } thiserror = { version = "1.0.69", optional = true } tokio = { version = "1.44", default-features = false, optional = true } -tracing = { version = "0.1", default-features = false, features = [ - "log", - "std", -] } +tracing = { version = "0.1", default-features = false, features = ["log", "std"] } uriparse = { version = "0.6" } -uuid-simd = { version = "0.8", default-features = false, features = [ - "std", - "detect", -] } +uuid-simd = { version = "0.8", default-features = false, features = ["std", "detect"] } [build-dependencies] protobuf-codegen = { version = "3.7.2" } protoc-bin-vendored = { version = "3.1" } [dev-dependencies] -mockall = "0.13" +clap = { version = "4.5.35" } +cucumber = { version = "0.21.1", features = ["output-junit"] } +hex = { version = "0.4" } +mockall = { version = "0.13" } test-case = { version = "3.3" } tokio = { version = "1.44", default-features = false, features = [ "macros", @@ -96,6 +93,14 @@ required-features = ["communication", "util"] name = "simple_rpc" required-features = ["communication", "util"] +[[test]] +name = "tck_uuri" +harness = false # allows Cucumber to print output instead of libtest + +[[test]] +name = "tck_uuid" +harness = false # allows Cucumber to print output instead of libtest + [lints.rust] # this prevents cargo from complaining about code blocks # excluded from tarpaulin coverage checks diff --git a/features/uuid/protobuf_serialization.feature b/features/uuid/protobuf_serialization.feature new file mode 100644 index 0000000..1695119 --- /dev/null +++ b/features/uuid/protobuf_serialization.feature @@ -0,0 +1,33 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: Efficient binary encoding of uProtocol UUIDs + + Scenario Outline: + Developers using a uProtocol language library should be able to get the binary + encoding of a uProtocol UUID instance as specified by the UUID proto3 definition file. + + [utest->req~uuid-proto~1] + + The byte sequences representing the Protocol Buffer encodings have been created + using https://www.protobufpal.com/ based on the UUID proto3 definition from the + uProtocol specification. + + Given a UUID having MSB and LSB + When serializing the UUID to its protobuf wire format + Then the original UUID can be recreated from the protobuf wire format + And the same UUID can be deserialized from + + Examples: + | uuid_msb | uuid_lsb | byte_sequence | + | 0x0000000000017000 | 0x8010101010101a1a | 090070010000000000111a1a101010101080 | diff --git a/features/uuid/string_serialization.feature b/features/uuid/string_serialization.feature new file mode 100644 index 0000000..2ede7cb --- /dev/null +++ b/features/uuid/string_serialization.feature @@ -0,0 +1,64 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: String representation of uProtocol UUIDs + + Scenario Outline: + Developers using a uProtocol language library should be able to get the + string representation of a UUID instance as specified by uProtocol's UUID + specification. + + [utest->dsn~uuid-spec~1] + + Given a UUID having MSB and LSB + When serializing the UUID to a hyphenated string + Then the resulting hyphenated string is + And the original UUID can be recreated from the hyphenated string + + Examples: + | uuid_msb | uuid_lsb | hyphenated_string | + | 0x0000000000017000 | 0x8010101010101a1a | 00000000-0001-7000-8010-101010101a1a | + + Scenario Outline: + Developers using a uProtocol language library should not be able to create a UUID from a + hyphenated string that does not comply with uProtocol's UUID specification. + + In particular, it should not be possible to create UUIDs having the wrong version + or variant identifier. + + [utest->dsn~uuid-spec~1] + + Given a UUID string representation + When deserializing the hyphenated string to a UUID + Then the attempt fails + + Examples: + | uuid_string | reason for failure | + | 00000000-0001-0000-8000-0000000000ab | wrong version (0b0000) | + | 00000000-0001-1000-8000-0000000000ab | wrong version (0b0001) | + | 00000000-0001-2000-8000-0000000000ab | wrong version (0b0010) | + | 00000000-0001-3000-8000-0000000000ab | wrong version (0b0011) | + | 00000000-0001-4000-8000-0000000000ab | wrong version (0b0100) | + | 00000000-0001-5000-8000-0000000000ab | wrong version (0b0101) | + | 00000000-0001-6000-8000-0000000000ab | wrong version (0b0110) | + | 00000000-0001-8000-8000-0000000000ab | wrong version (0b1000) | + | 00000000-0001-9000-8000-0000000000ab | wrong version (0b1001) | + | 00000000-0001-a000-8000-0000000000ab | wrong version (0b1010) | + | 00000000-0001-b000-8000-0000000000ab | wrong version (0b1011) | + | 00000000-0001-c000-8000-0000000000ab | wrong version (0b1100) | + | 00000000-0001-d000-8000-0000000000ab | wrong version (0b1101) | + | 00000000-0001-e000-8000-0000000000ab | wrong version (0b1110) | + | 00000000-0001-f000-8000-0000000000ab | wrong version (0b1111) | + | 00000000-0001-7000-0000-0000000000ab | wrong variant (0b00) | + | 00000000-0001-7000-4000-0000000000ab | wrong variant (0b01) | + | 00000000-0001-7000-c000-0000000000ab | wrong variant (0b11) | diff --git a/features/uuri/pattern_matching.feature b/features/uuri/pattern_matching.feature new file mode 100644 index 0000000..4dbf01e --- /dev/null +++ b/features/uuri/pattern_matching.feature @@ -0,0 +1,57 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: Matching endpoint identifiers (UUri) against patterns + + Scenario Outline: + Developers using a uProtocol language library should be able to verify that a specific + endpoint identifier matches a given pattern as specified by the UUri specification. + + // [utest->dsn~uri-pattern-matching~2] + + Given a URI string + When deserializing the URI to a UUri + Then the UUri matches pattern + + Examples: + | uri | pattern | + | /1/1/A1FB | /1/1/A1FB | + | /1/1/A1FB | //*/1/1/A1FB | + | //vcu.my_vin/1/1/A1FB | //vcu.my_vin/1/1/A1FB | + | /1/1/A1FB | //*/1/1/A1FB | + | //vcu.my_vin/1/1/A1FB | //*/FFFF/1/A1FB | + | /1/1/A1FB | //*/FFFFFFFF/1/A1FB | + | //vcu.my_vin/1/1/A1FB | //*/FFFFFFFF/FF/A1FB | + | /1/1/A1FB | //*/FFFFFFFF/FF/FFFF | + | //vcu.my_vin/10A0101/3/A1FB | //*/FFFFFFFF/3/A1FB | + + Scenario Outline: + Developers using a uProtocol language library should be able to verify that a specific + endpoint identifier does not match a given pattern as specified by the UUri specification. + + // [utest->dsn~uri-pattern-matching~2] + + Given a URI string + When deserializing the URI to a UUri + Then the UUri does not match pattern + + Examples: + | uri | pattern | + | /1/1/A1FB | //mcu1/1/1/A1FB | + | //vcu.my_vin/1/1/A1FB | //VCU.my_vin/1/1/A1FB | + | //vcu.my_vin/1/1/A1FB | //mcu1/1/1/A1FB | + | /B1A5/1/A1FB | /25B1/1/A1FB | + | /B1A5/1/A1FB | /2B1A5/1/A1FB | + | /10B1A5/1/A1FB | /40B1A5/1/A1FB | + | /B1A5/1/A1FB | /B1A5/4/A1FB | + | /B1A5/1/A1FB | /B1A5/1/90FB | diff --git a/features/uuri/protobuf_serialization.feature b/features/uuri/protobuf_serialization.feature new file mode 100644 index 0000000..4371c34 --- /dev/null +++ b/features/uuri/protobuf_serialization.feature @@ -0,0 +1,46 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: Efficient binary encoding of endpoint identifiers (UUri) + + Scenario Outline: + Developers using a uProtocol language library should be able to get the binary + encoding of a UUri instance as specified by the UUri proto3 definition file. + + // [utest->req~uri-data-model-proto~1] + + The byte sequences representing the Protocol Buffer encodings have been created + using https://www.protobufpal.com/ based on the UUri proto3 definition from the + uProtocol specification. + + Given a UUri having authority + And having entity identifier + And having major version + And having resource identifier + When serializing the UUri to its protobuf wire format + Then the original UUri can be recreated from the protobuf wire format + And the same UUri can be deserialized from + + Examples: + | authority_name | entity_id | version | resource_id | byte_sequence | + | "" | 0x00000001 | 0x01 | 0xa1fb | 1001180120fbc302 | + | "my_vin" | 0x10000001 | 0x02 | 0x001a | 0a066d795f76696e1081808080011802201a | + | "*" | 0x00000101 | 0xa0 | 0xa1fb | 0a012a10810218a00120fbc302 | + | "mcu1" | 0x0000FFFF | 0x01 | 0xa1fb | 0a046d63753110ffff03180120fbc302 | + | "vcu.my_vin" | 0x01a40101 | 0x01 | 0x8000 | 0a0a7663752e6d795f76696e108182900d180120808002 | + | "vcu.my_vin" | 0xFFFF0101 | 0x01 | 0xa1fb | 0a0a7663752e6d795f76696e108182fcff0f180120fbc302 | + | "vcu.my_vin" | 0xFFFFFFFF | 0x01 | 0xa1fb | 0a0a7663752e6d795f76696e10ffffffff0f180120fbc302 | + | "vcu.my_vin" | 0x00000101 | 0x00 | 0xa1fb | 0a0a7663752e6d795f76696e10810220fbc302 | + | "vcu.my_vin" | 0x00000101 | 0xFF | 0xa1fb | 0a0a7663752e6d795f76696e10810218ff0120fbc302 | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0x0000 | 0a0a7663752e6d795f76696e1081021801 | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0xFFFF | 0a0a7663752e6d795f76696e108102180120ffff03 | diff --git a/features/uuri/uri_serialization.feature b/features/uuri/uri_serialization.feature new file mode 100644 index 0000000..eac95ca --- /dev/null +++ b/features/uuri/uri_serialization.feature @@ -0,0 +1,85 @@ +# +# Copyright (c) 2025 Contributors to the Eclipse Foundation +# +# See the NOTICE file(s) distributed with this work for additional +# information regarding copyright ownership. +# +# This program and the accompanying materials are made available under the +# terms of the Apache License Version 2.0 which is available at +# https://www.apache.org/licenses/LICENSE-2.0 +# +# SPDX-FileType: SOURCE +# SPDX-License-Identifier: Apache-2.0 +# +Feature: String representation of endpoint identfiers (UUri) + + Scenario Outline: + Developers using a uProtocol language library should be able to get the URI + string representation of a UUri instance as specified by the UUri specification. + + // [utest->req~uri-serialization~1] + // [utest->dsn~uri-scheme~1] + // [utest->dsn~uri-host-only~2] + // [utest->dsn~uri-authority-mapping~1] + // [utest->dsn~uri-path-mapping~1] + + Given a UUri having authority + And having entity identifier + And having major version + And having resource identifier + When serializing the UUri to a URI + Then the resulting URI string is + And the original UUri can be recreated from the URI string + + Examples: + | authority_name | entity_id | version | resource_id | uri_string | + | "" | 0x00000001 | 0x01 | 0xa1fb | up:/1/1/A1FB | + | "my_vin" | 0x10000001 | 0x02 | 0x001a | up://my_vin/10000001/2/1A | + | "*" | 0x00000101 | 0xa0 | 0xa1fb | up://*/101/A0/A1FB | + | "mcu1" | 0x0000FFFF | 0x01 | 0xa1fb | up://mcu1/FFFF/1/A1FB | + | "vcu.my_vin" | 0x01a40101 | 0x01 | 0x8000 | up://vcu.my_vin/1A40101/1/8000 | + | "vcu.my_vin" | 0xFFFF0101 | 0x01 | 0xa1fb | up://vcu.my_vin/FFFF0101/1/A1FB | + | "vcu.my_vin" | 0xFFFFFFFF | 0x01 | 0xa1fb | up://vcu.my_vin/FFFFFFFF/1/A1FB | + | "vcu.my_vin" | 0x00000101 | 0x00 | 0xa1fb | up://vcu.my_vin/101/0/A1FB | + | "vcu.my_vin" | 0x00000101 | 0xFF | 0xa1fb | up://vcu.my_vin/101/FF/A1FB | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0x0000 | up://vcu.my_vin/101/1/0 | + | "vcu.my_vin" | 0x00000101 | 0x01 | 0xFFFF | up://vcu.my_vin/101/1/FFFF | + + Scenario Outline: + Developers using a uProtocol language library should not be able to create a UUri from a + URI string that does not comply with the UUri specification. + + // [utest->req~uri-serialization~1] + // [utest->dsn~uri-scheme~1] + // [utest->dsn~uri-host-only~2] + // [utest->dsn~uri-authority-mapping~1] + // [utest->dsn~uri-path-mapping~1] + + Given a URI string + When deserializing the URI to a UUri + Then the attempt fails + + Examples: + | uri_string | reason for failure | + | "" | not a URI | + | "/" | not a URI | + | "//" | not a URI | + | "up:/" | not a URI | + | "up://" | not a URI | + | xy://vcu.my_vin/101/1/A1FB | unsupported schema | + | up://""/101/1/A1FB | authority name with reserved character | + | up://vcu#my-vin/101/1/A1FB | authority name with reserved character | + | up://vcu%my-vin/101/1/A1FB | authority name with reserved character | + | ////1/A1FB | missing authority and entity | + | /////A1FB | missing authority, entity and version | + | up://vcu.my_vin/101/1/A1FB?foo=bar | URI with query | + | up://vcu.my_vin/101/1/A1FB#foo | URI with fragment | + | up://vcu.my_vin:1516/101/1/A1FB | authority with port | + | up://user:pwd@vcu.my_vin/101/1/A1FB | authority with user info | + | up://"vcu.my-vin"/0/1/A1FB | invalid entity ID | + | up:/1G1/1/A1FB | non-hex entity ID | + | up:/123456789/1/A1FB | entity ID exceeds max length | + | up:/101/G/A1FB | non-hex version | + | /101/123/A1FB | version exceeds max length | + | /101/1/G1FB | non-hex resource ID | + | /101/1/12345 | resource ID exceeds max length | diff --git a/src/uri.rs b/src/uri.rs index 483c3ef..4144ba8 100644 --- a/src/uri.rs +++ b/src/uri.rs @@ -971,7 +971,6 @@ impl UUri { #[cfg(test)] mod tests { use super::*; - use protobuf::Message; use test_case::test_case; // [utest->dsn~uri-authority-name-length~1] @@ -1014,97 +1013,6 @@ mod tests { assert!(uuri.check_validity().is_err()); } - // [utest->req~uri-serialization~1] - // [utest->dsn~uri-scheme~1] - // [utest->dsn~uri-host-only~2] - // [utest->dsn~uri-authority-mapping~1] - // [utest->dsn~uri-path-mapping~1] - #[test_case(""; "for empty string")] - #[test_case("/"; "for single slash")] - #[test_case("up:/"; "for scheme and single slash")] - #[test_case("//"; "for double slash")] - #[test_case("up://"; "for scheme and double slash")] - #[test_case("custom://my-vehicle/8000/2/1"; "for unsupported scheme")] - #[test_case("////2/1"; "for missing authority and entity")] - #[test_case("/////1"; "for missing authority, entity and version")] - #[test_case("up://MYVIN/1A23/1/a13?foo=bar"; "for URI with query")] - #[test_case("up://MYVIN/1A23/1/a13#foobar"; "for URI with fragement")] - #[test_case("up://MYVIN:1000/1A23/1/A13"; "for authority with port")] - #[test_case("up://user:pwd@MYVIN/1A23/1/A13"; "for authority with userinfo")] - #[test_case("up://MY#VIN/55A1/1/1"; "for invalid authority")] - #[test_case("up://MYVIN/55T1/1/1"; "for non-hex entity ID")] - #[test_case("up://MYVIN/123456789/1/1"; "for entity ID exceeding max length")] - #[test_case("up://MYVIN/55A1//1"; "for empty version")] - #[test_case("up://MYVIN/55A1/T/1"; "for non-hex version")] - #[test_case("up://MYVIN/55A1/123/1"; "for version exceeding max length")] - #[test_case("up://MYVIN/55A1/1/"; "for empty resource ID")] - #[test_case("up://MYVIN/55A1/1/1T"; "for non-hex resource ID")] - #[test_case("up://MYVIN/55A1/1/10001"; "for resource ID exceeding max length")] - fn test_from_string_fails(string: &str) { - let parsing_result = UUri::from_str(string); - assert!(parsing_result.is_err()); - } - - // [utest->req~uri-serialization~1] - // [utest->dsn~uri-scheme~1] - // [utest->dsn~uri-host-only~2] - // [utest->dsn~uri-authority-mapping~1] - // [utest->dsn~uri-path-mapping~1] - #[test_case("UP:/8000/1/2", - UUri { - authority_name: String::default(), - ue_id: 0x0000_8000, - ue_version_major: 0x01, - resource_id: 0x0002, - ..Default::default() - }; - "for local service with version and resource")] - #[test_case("/108000/1/2", - UUri { - authority_name: String::default(), - ue_id: 0x0010_8000, - ue_version_major: 0x01, - resource_id: 0x0002, - ..Default::default() - }; - "for local service instance with version and resource")] - #[test_case("/8000/1/0", - UUri { - authority_name: String::default(), - ue_id: 0x0000_8000, - ue_version_major: 0x01, - resource_id: 0x0000, - ..Default::default() - }; - "for local rpc service response")] - #[test_case("up://VCU.MY_CAR_VIN/108000/1/2", - UUri { - authority_name: "VCU.MY_CAR_VIN".to_string(), - ue_id: 0x0010_8000, - ue_version_major: 0x01, - resource_id: 0x0002, - ..Default::default() - }; - "for remote uri")] - #[test_case("//*/FFFF/FF/FFFF", - UUri { - authority_name: "*".to_string(), - ue_id: 0x0000_FFFF, - ue_version_major: 0xFF, - resource_id: 0xFFFF, - ..Default::default() - }; - "for remote uri with wildcards")] - fn test_from_string_succeeds(uri: &str, expected_uuri: UUri) { - let parsing_result = UUri::from_str(uri); - if parsing_result.is_err() { - println!("error: {}", parsing_result.as_ref().unwrap_err()); - } - assert!(parsing_result.is_ok()); - let parsed_uuri = parsing_result.unwrap(); - assert_eq!(expected_uuri, parsed_uuri); - } - #[test_case("//*/A100/1/1"; "for any authority")] #[test_case("//VIN/FFFF/1/1"; "for any entity type")] #[test_case("//VIN/FFFF0ABC/1/1"; "for any entity instance")] @@ -1115,21 +1023,6 @@ mod tests { assert!(uuri.verify_no_wildcards().is_err()); } - // [utest->req~uri-data-model-proto~1] - #[test] - fn test_protobuf_serialization() { - let uri = UUri { - authority_name: "MYVIN".to_string(), - ue_id: 0x0000_1a4f, - ue_version_major: 0x10, - resource_id: 0xb392, - ..Default::default() - }; - let pb = uri.write_to_bytes().unwrap(); - let deserialized_uri = UUri::parse_from_bytes(pb.as_slice()).unwrap(); - assert_eq!(uri, deserialized_uri); - } - // [utest->dsn~uri-authority-name-length~1] #[test] fn test_from_str_fails_for_authority_exceeding_max_length() { @@ -1164,37 +1057,4 @@ mod tests { fn test_try_from_parts_fails_for_invalid_authority(authority: &str) { assert!(UUri::try_from_parts(authority, 0xa100, 0x01, 0x6501).is_err()); } - - // [utest->dsn~uri-pattern-matching~2] - #[test_case("//authority/A410/3/1003", "//authority/A410/3/1003"; "for identical URIs")] - #[test_case("//*/A410/3/1003", "//authority/A410/3/1003"; "for pattern with wildcard authority")] - #[test_case("//*/A410/3/1003", "/A410/3/1003"; "for pattern with wildcard authority and local candidate URI")] - #[test_case("//authority/FFFF/3/1003", "//authority/A410/3/1003"; "for pattern with wildcard entity ID")] - #[test_case("//authority/FFFFA410/3/1003", "//authority/2A410/3/1003"; "for pattern with wildcard entity instance")] - #[test_case("//authority/A410/FF/1003", "//authority/A410/3/1003"; "for pattern with wildcard entity version")] - #[test_case("//authority/A410/3/FFFF", "//authority/A410/3/1003"; "for pattern with wildcard resource")] - fn test_matches_succeeds(pattern: &str, candidate: &str) { - let pattern_uri = - UUri::try_from(pattern).expect("should have been able to create pattern UUri"); - let candidate_uri = - UUri::try_from(candidate).expect("should have been able to create candidate UUri"); - assert!(pattern_uri.matches(&candidate_uri)); - } - - // [utest->dsn~uri-pattern-matching~2] - #[test_case("//Authority/A410/3/1003", "//authority/A410/3/1003"; "for pattern with upper case authority")] - #[test_case("/A410/3/1003", "//authority/A410/3/1003"; "for local pattern and candidate URI with authority")] - #[test_case("//other/A410/3/1003", "//authority/A410/3/1003"; "for pattern with different authority")] - #[test_case("//authority/45/3/1003", "//authority/A410/3/1003"; "for pattern with different entity ID")] - #[test_case("//authority/A410/3/1003", "//authority/2A410/3/1003"; "for pattern with default entity instance")] - #[test_case("//authority/30A410/3/1003", "//authority/2A410/3/1003"; "for pattern with different entity instance")] - #[test_case("//authority/A410/1/1003", "//authority/A410/3/1003"; "for pattern with different entity version")] - #[test_case("//authority/A410/3/ABCD", "//authority/A410/3/1003"; "for pattern with different resource")] - fn test_matches_fails(pattern: &str, candidate: &str) { - let pattern_uri = - UUri::try_from(pattern).expect("should have been able to create pattern UUri"); - let candidate_uri = - UUri::try_from(candidate).expect("should have been able to create candidate UUri"); - assert!(!pattern_uri.matches(&candidate_uri)); - } } diff --git a/src/uuid.rs b/src/uuid.rs index 03a3cdb..0ea269e 100644 --- a/src/uuid.rs +++ b/src/uuid.rs @@ -339,8 +339,6 @@ impl FromStr for UUID { #[cfg(test)] mod tests { - use protobuf::Message; - use super::*; // [utest->dsn~uuid-spec~1] @@ -399,13 +397,4 @@ mod tests { assert_eq!(String::from(&uuid), "00000000-0001-7000-8010-101010101a1a"); assert_eq!(String::from(uuid), "00000000-0001-7000-8010-101010101a1a"); } - - // [utest->req~uuid-proto~1] - #[test] - fn test_protobuf_serialization() { - let uuid = UUID::build(); - let bytes = uuid.write_to_bytes().unwrap(); - let deserialized_uuid = UUID::parse_from_bytes(bytes.as_slice()).unwrap(); - assert_eq!(uuid, deserialized_uuid); - } } diff --git a/tests/common/mod.rs b/tests/common/mod.rs new file mode 100644 index 0000000..a95b5fc --- /dev/null +++ b/tests/common/mod.rs @@ -0,0 +1,32 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use std::fs::{self, File}; + +use cucumber::cli; + +#[derive(cli::Args)] +pub(crate) struct CustomTckOpts { + /// The folder to write the JUnit report to. + #[arg(long, value_name = "PATH")] + pub junit_out_folder: Option, +} + +impl CustomTckOpts { + pub(crate) fn get_junit_out_file(&self, tck_test_name: &str) -> Option { + self.junit_out_folder.as_ref().map(|path| { + fs::File::create(format!("{}/tck-{}-results.xml", path, tck_test_name)) + .expect("failed to create JUnit report file") + }) + } +} diff --git a/tests/tck_uuid.rs b/tests/tck_uuid.rs new file mode 100644 index 0000000..7282cfc --- /dev/null +++ b/tests/tck_uuid.rs @@ -0,0 +1,128 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use std::str::FromStr; + +use common::CustomTckOpts; +use cucumber::{cli, given, then, when, writer, World}; +use protobuf::Message; +use up_rust::UUID; + +mod common; + +const FEATURES_PATH: &str = "features/uuid"; + +fn value_as_u64(value: String) -> u64 { + if value.starts_with("0x") || value.starts_with("0X") { + u64::from_str_radix(trimhex(&value), 16).expect("not a hex number") + } else { + value.parse().expect("not an integer number") + } +} + +fn trimhex(s: &str) -> &str { + s.strip_prefix("0x") + .unwrap_or(s.strip_prefix("0X").unwrap_or(s)) +} + +#[derive(cucumber::World, Default, Debug)] +struct UUIDWorld { + uuid: UUID, + hyphenated_string: String, + protobuf: Vec, + error: Option>, +} + +#[given(expr = "a UUID string representation {word}")] +async fn with_hyphenated_string(w: &mut UUIDWorld, hyphenated_string: String) { + w.hyphenated_string = hyphenated_string; +} + +#[given(expr = "a UUID having MSB {word} and LSB {word}")] +async fn with_msb_lsb(w: &mut UUIDWorld, msb_hex_string: String, lsb_hex_string: String) { + w.uuid.msb = value_as_u64(msb_hex_string); + w.uuid.lsb = value_as_u64(lsb_hex_string); +} + +#[when(expr = "serializing the UUID to a hyphenated string")] +async fn serialize_to_hyphenated_string(w: &mut UUIDWorld) { + w.hyphenated_string = w.uuid.to_hyphenated_string(); +} + +#[when(expr = "serializing the UUID to its protobuf wire format")] +async fn serialize_to_protobuf(w: &mut UUIDWorld) { + w.protobuf = w + .uuid + .write_to_bytes() + .expect("failed to serialize UUID to protobuf"); +} + +#[when(expr = "deserializing the hyphenated string to a UUID")] +async fn deserialize_from_hyphenated_string(w: &mut UUIDWorld) { + match UUID::from_str(&w.hyphenated_string) { + Ok(uuid) => { + w.uuid = uuid; + } + Err(e) => { + w.error = Some(Box::from(e)); + } + } +} + +#[then(expr = "the attempt fails")] +async fn assert_failure(w: &mut UUIDWorld) { + assert!(w.error.is_some()); +} + +#[then(expr = "the resulting hyphenated string is {word}")] +async fn assert_hyphenated_string(w: &mut UUIDWorld, expected_string: String) { + assert_eq!(w.hyphenated_string, expected_string); +} + +#[then(expr = "the original UUID can be recreated from the hyphenated string")] +async fn assert_original_uuid_can_be_recreated_from_hyphenated_string(w: &mut UUIDWorld) { + assert!(w + .hyphenated_string + .parse::() + .is_ok_and(|uuid| w.uuid.eq(&uuid))); +} + +#[then(expr = "the original UUID can be recreated from the protobuf wire format")] +async fn assert_original_uuid_can_be_recreated_from_protobuf(w: &mut UUIDWorld) { + assert!(UUID::parse_from_bytes(&w.protobuf).is_ok_and(|uuid| w.uuid.eq(&uuid))); +} + +#[then(expr = "the same UUID can be deserialized from {word}")] +async fn assert_deserialize_uuid_from_protobuf(w: &mut UUIDWorld, hex_string: String) { + let buf = hex::decode(trimhex(&hex_string)).expect("not a valid hex string"); + assert!(UUID::parse_from_bytes(buf.as_slice()).is_ok_and(|uuid| w.uuid.eq(&uuid))); +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + let opts = cli::Opts::<_, _, _, CustomTckOpts>::parsed(); + if let Some(file) = opts.custom.get_junit_out_file("uuid") { + UUIDWorld::cucumber() + .with_cli(opts) + .with_writer(cucumber::writer::JUnit::new(file, 0)) + .run(FEATURES_PATH) + .await; + } else { + UUIDWorld::cucumber() + .with_cli(opts) + .with_writer(writer::Basic::stdout()) + .run(FEATURES_PATH) + .await; + } + Ok(()) +} diff --git a/tests/tck_uuri.rs b/tests/tck_uuri.rs new file mode 100644 index 0000000..de0427c --- /dev/null +++ b/tests/tck_uuri.rs @@ -0,0 +1,185 @@ +/******************************************************************************** + * Copyright (c) 2025 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +use std::str::FromStr; + +use common::CustomTckOpts; +use cucumber::{cli, given, then, when, writer, World}; +use protobuf::Message; +use up_rust::UUri; + +mod common; + +const FEATURES_PATH: &str = "features/uuri"; + +fn value_as_u8(value: String) -> u8 { + if value.starts_with("0x") || value.starts_with("0X") { + u8::from_str_radix(trimhex(&value), 16).expect("not a hex number") + } else { + value.parse().expect("not an integer number") + } +} + +fn value_as_u16(value: String) -> u16 { + if value.starts_with("0x") || value.starts_with("0X") { + u16::from_str_radix(trimhex(&value), 16).expect("not a hex number") + } else { + value.parse().expect("not an integer number") + } +} + +fn value_as_u32(value: String) -> u32 { + if value.starts_with("0x") || value.starts_with("0X") { + u32::from_str_radix(trimhex(&value), 16).expect("not a hex number") + } else { + value.parse().expect("not an integer number") + } +} + +fn trimhex(s: &str) -> &str { + s.strip_prefix("0x") + .unwrap_or(s.strip_prefix("0X").unwrap_or(s)) +} + +#[derive(cucumber::World, Default, Debug)] +struct UUriWorld { + uuri: UUri, + uri: String, + protobuf: Vec, + error: Option>, +} + +#[given(expr = "a URI string {word}")] +async fn with_uri_string(w: &mut UUriWorld, uri_string: String) { + w.uri = uri_string; +} + +#[given(expr = "a UUri having authority {string}")] +async fn with_authority(w: &mut UUriWorld, authority_name: String) { + w.uuri.authority_name = authority_name; +} + +#[given(expr = "having entity identifier {word}")] +async fn with_entity_id(w: &mut UUriWorld, entity_id: String) { + w.uuri.ue_id = value_as_u32(entity_id); +} + +#[given(expr = "having major version {word}")] +async fn with_major_version(w: &mut UUriWorld, major_version: String) { + w.uuri.ue_version_major = value_as_u32(major_version); +} + +#[given(expr = "having resource identifier {word}")] +async fn with_resource_id(w: &mut UUriWorld, resource_id: String) { + w.uuri.resource_id = value_as_u32(resource_id); +} + +#[when(expr = "serializing the UUri to a URI")] +async fn serialize_to_uri(w: &mut UUriWorld) { + w.uri = w.uuri.to_uri(true); +} + +#[when(expr = "serializing the UUri to its protobuf wire format")] +async fn serialize_to_protobuf(w: &mut UUriWorld) { + w.protobuf = w + .uuri + .write_to_bytes() + .expect("failed to serialize UUri to protobuf"); +} + +#[when(expr = "deserializing the URI to a UUri")] +async fn deserialize_from_uri(w: &mut UUriWorld) { + match UUri::from_str(w.uri.as_str()) { + Ok(uuri) => { + w.uuri = uuri; + } + Err(e) => { + w.error = Some(Box::from(e)); + } + } +} + +#[then(expr = "the resulting URI string is {word}")] +async fn assert_uri_string(w: &mut UUriWorld, expected_uri: String) { + assert_eq!(w.uri, expected_uri); +} + +#[then(expr = "the UUri has authority {string}")] +async fn assert_authority(w: &mut UUriWorld, value: String) { + assert_eq!(w.uuri.authority_name(), value); +} + +#[then(expr = "has entity identifier {word}")] +async fn assert_entity_id(w: &mut UUriWorld, entity_id: String) { + assert_eq!(w.uuri.ue_id, value_as_u32(entity_id)); +} + +#[then(expr = "has major version {word}")] +async fn assert_major_version(w: &mut UUriWorld, major_version: String) { + assert_eq!(w.uuri.uentity_major_version(), value_as_u8(major_version)); +} + +#[then(expr = "has resource identifier {word}")] +async fn assert_resource_id(w: &mut UUriWorld, resource_id: String) { + assert_eq!(w.uuri.resource_id(), value_as_u16(resource_id)); +} + +#[then(expr = "the attempt fails")] +async fn assert_failure(w: &mut UUriWorld) { + assert!(w.error.is_some()); +} + +#[then(expr = "the original UUri can be recreated from the protobuf wire format")] +async fn assert_original_uuri_can_be_recreated_from_protobuf(w: &mut UUriWorld) { + assert!(UUri::parse_from_bytes(&w.protobuf).is_ok_and(|uuri| w.uuri.eq(&uuri))); +} + +#[then(expr = "the same UUri can be deserialized from {word}")] +async fn assert_uuri_can_be_deserialized_from_bytes(w: &mut UUriWorld, hex_string: String) { + let buf = hex::decode(trimhex(&hex_string)).expect("not a valid hex string"); + assert!(UUri::parse_from_bytes(buf.as_slice()).is_ok_and(|uuri| w.uuri.eq(&uuri))); +} + +#[then(expr = "the original UUri can be recreated from the URI string")] +async fn assert_original_uuri_can_be_recreated_from_uri_string(w: &mut UUriWorld) { + assert!(w.uri.parse::().is_ok_and(|uuri| w.uuri.eq(&uuri))); +} + +#[then(expr = "the UUri matches pattern {word}")] +async fn assert_uuri_matches_pattern(w: &mut UUriWorld, pattern: String) { + assert!(pattern.parse::().is_ok_and(|p| p.matches(&w.uuri))); +} + +#[then(expr = "the UUri does not match pattern {word}")] +async fn assert_uuri_does_not_match_pattern(w: &mut UUriWorld, pattern: String) { + assert!(pattern.parse::().is_ok_and(|p| !p.matches(&w.uuri))); +} + +#[tokio::main] +async fn main() -> Result<(), std::io::Error> { + let opts = cli::Opts::<_, _, _, CustomTckOpts>::parsed(); + if let Some(file) = opts.custom.get_junit_out_file("uuri") { + UUriWorld::cucumber() + .with_cli(opts) + .with_writer(cucumber::writer::JUnit::new(file, 0)) + .run(FEATURES_PATH) + .await; + } else { + UUriWorld::cucumber() + .with_cli(opts) + .with_writer(writer::Basic::stdout()) + .run(FEATURES_PATH) + .await; + } + Ok(()) +}