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(()) +}