From 92cee939f859b4da2a13592836b88164b1d35c11 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 00:59:52 +0100 Subject: [PATCH 01/96] import initial code from internal - doesn't yet compile, fixing in next commits Signed-off-by: mimir-d --- .gitignore | 2 + Cargo.lock | 668 +++++++++++++ Cargo.toml | 29 + src/emitters.rs | 187 ++++ src/lib.rs | 19 + src/macros.rs | 687 +++++++++++++ src/models.rs | 685 +++++++++++++ src/objects.rs | 1791 ++++++++++++++++++++++++++++++++++ src/runner.rs | 2447 +++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 6515 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/emitters.rs create mode 100644 src/lib.rs create mode 100644 src/macros.rs create mode 100644 src/models.rs create mode 100644 src/objects.rs create mode 100644 src/runner.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ccb5166 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..26a2dd6 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,668 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + +[[package]] +name = "assert-json-diff" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e4f2b81832e72834d7518d8487a0396a28cc408186a2e8854c0f98011faf12" +dependencies = [ + "serde", + "serde_json", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-targets", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cc" +version = "1.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets", +] + +[[package]] +name = "chrono-tz" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6dd8046d00723a59a2f8c5f295c515b9bb9a331ee4f8f3d4dd49e428acd3b6" +dependencies = [ + "chrono", + "chrono-tz-build", + "phf", +] + +[[package]] +name = "chrono-tz-build" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94fea34d77a245229e7746bd2beb786cd2a896f306ff491fb8cecb3074b10a7" +dependencies = [ + "parse-zoneinfo", + "phf_codegen", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn", + "unicode-xid", +] + +[[package]] +name = "gimli" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "ocptv" +version = "0.1.0" +dependencies = [ + "anyhow", + "assert-json-diff", + "chrono", + "chrono-tz", + "derive_more", + "serde", + "serde_json", + "thiserror", + "tokio", +] + +[[package]] +name = "once_cell" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "parse-zoneinfo" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24" +dependencies = [ + "regex", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "portable-atomic" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "regex" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "pin-project-lite", + "tokio-macros", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..45ef96f --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "ocptv" +version = "0.1.0" +description = "Strongly typed Rust API for OCPTV output" +authors = ["OCP Test & Validation Project"] +keywords = ["ocptv", "hardware", "validation"] +repository = "https://github.com/opencomputeproject/ocp-diag-core-rust" +license = "MIT" +edition = "2021" + +[dependencies] +chrono = "0.4.38" +chrono-tz = "0.10.0" +derive_more = { version = "1.0.0", features = ["full"] } +serde = { version = "1.0.210", features = ["derive"] } +serde_json = "1.0.128" +thiserror = "1.0.64" +tokio = { version = "1.40.0", features = [ + "rt", + "rt-multi-thread", + "macros", + "io-util", + "fs", + "sync", +] } + +[dev-dependencies] +anyhow = "1.0.89" +assert-json-diff = "2.0.2" diff --git a/src/emitters.rs b/src/emitters.rs new file mode 100644 index 0000000..0a68d50 --- /dev/null +++ b/src/emitters.rs @@ -0,0 +1,187 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use core::fmt::Debug; +use std::clone::Clone; +use std::io; +use std::io::Write; +use std::path::Path; +use std::sync::atomic; +use std::sync::Arc; + +use tokio::fs::File; +use tokio::io::AsyncWriteExt; +use tokio::sync::Mutex; + +use crate::models; + +#[derive(Debug, thiserror::Error, derive_more::Display)] +#[non_exhaustive] +pub enum WriterError { + IoError(#[from] io::Error), +} + +pub(crate) enum WriterType { + Stdout(StdoutWriter), + File(FileWriter), + Buffer(BufferWriter), +} + +pub struct FileWriter { + file: Arc>, +} + +impl FileWriter { + pub async fn new>(path: P) -> Result { + let file = File::create(path).await.map_err(WriterError::IoError)?; + Ok(FileWriter { + file: Arc::new(Mutex::new(file)), + }) + } + + async fn write(&self, s: &str) -> Result<(), WriterError> { + let mut handle = self.file.lock().await; + let mut buf = Vec::::new(); + writeln!(buf, "{}", s)?; + handle.write_all(&buf).await.map_err(WriterError::IoError)?; + handle.flush().await.map_err(WriterError::IoError)?; + Ok(()) + } +} + +#[derive(Debug)] +pub struct BufferWriter { + buffer: Arc>>, +} + +impl BufferWriter { + pub fn new(buffer: Arc>>) -> Self { + Self { buffer } + } + + async fn write(&self, s: &str) -> Result<(), WriterError> { + self.buffer.lock().await.push(s.to_string()); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct StdoutWriter {} + +impl StdoutWriter { + pub fn new() -> Self { + StdoutWriter {} + } + + async fn write(&self, s: &str) -> Result<(), WriterError> { + let mut handle = io::stdout().lock(); + writeln!(handle, "{}", s).map_err(WriterError::IoError)?; + Ok(()) + } +} + +pub struct JsonEmitter { + sequence_no: Arc, + timezone: chrono_tz::Tz, + writer: WriterType, +} + +impl JsonEmitter { + pub(crate) fn new(timezone: chrono_tz::Tz, writer: WriterType) -> Self { + JsonEmitter { + timezone, + writer, + sequence_no: Arc::new(atomic::AtomicU64::new(0)), + } + } + + fn serialize_artifact(&self, object: &models::OutputArtifactDescendant) -> serde_json::Value { + let now = chrono::Local::now(); + let now_tz = now.with_timezone(&self.timezone); + let out_artifact = models::OutputArtifactSpec { + descendant: object.clone(), + now: now_tz, + sequence_number: self.next_sequence_no(), + }; + serde_json::json!(out_artifact) + } + + fn next_sequence_no(&self) -> u64 { + self.sequence_no.fetch_add(1, atomic::Ordering::SeqCst); + self.sequence_no.load(atomic::Ordering::SeqCst) + } + + pub async fn emit(&self, object: &models::OutputArtifactDescendant) -> Result<(), WriterError> { + let serialized = self.serialize_artifact(object); + match self.writer { + WriterType::File(ref file) => file.write(&serialized.to_string()).await?, + WriterType::Stdout(ref stdout) => stdout.write(&serialized.to_string()).await?, + WriterType::Buffer(ref buffer) => buffer.write(&serialized.to_string()).await?, + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use anyhow::anyhow; + use anyhow::Result; + use assert_json_diff::assert_json_include; + use serde_json::json; + + use super::*; + use crate::objects::*; + + #[tokio::test] + async fn test_emit_using_buffer_writer() -> Result<()> { + let expected = json!({ + "schemaVersion": { + "major": 2, + "minor": 0 + }, + "sequenceNumber": 1 + }); + + let buffer = Arc::new(Mutex::new(vec![])); + let writer = BufferWriter::new(buffer.clone()); + let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + + let version = SchemaVersion::new(); + emitter.emit(&version.to_artifact()).await?; + + let deserialized = serde_json::from_str::( + buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, + )?; + assert_json_include!(actual: deserialized, expected: expected); + + Ok(()) + } + + #[tokio::test] + async fn test_sequence_number_increments_at_each_call() -> Result<()> { + let expected_1 = json!({"schemaVersion": {"major":2,"minor":0},"sequenceNumber":1}); + let expected_2 = json!({"schemaVersion": {"major":2,"minor":0},"sequenceNumber":2}); + + let buffer = Arc::new(Mutex::new(vec![])); + let writer = BufferWriter::new(buffer.clone()); + let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + let version = SchemaVersion::new(); + emitter.emit(&version.to_artifact()).await?; + emitter.emit(&version.to_artifact()).await?; + + let deserialized = serde_json::from_str::( + buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, + )?; + assert_json_include!(actual: deserialized, expected: expected_1); + + let deserialized = serde_json::from_str::( + buffer.lock().await.get(1).ok_or(anyhow!("no outputs"))?, + )?; + assert_json_include!(actual: deserialized, expected: expected_2); + + Ok(()) + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9e3999e --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,19 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +mod emitters; +mod macros; +mod models; +mod objects; +mod runner; + +pub use emitters::*; +pub use models::LogSeverity; +pub use models::TestResult; +pub use models::TestStatus; +pub use objects::*; +pub use runner::*; +pub use serde_json::Value; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..7b937f9 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,687 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +//! OCPTV library macros +//! +//! This module contains a set of macros which are exported from the ocptv +//! library. + +/// Emits an artifact of type Error. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error +/// +/// Equivalent to the crate::runner::TestRun::error_with_details method. +/// +/// It accepts both a symptom and a message, or just a symptom. +/// Information about the source file and line number is automatically added. +/// +/// # Examples +/// +/// ## Passing only symptom +/// +/// ``` +/// use std::io::Write; +/// use std::io::{self}; +/// +/// use ocptv_formatter::ocptv_error; +/// use ocptv_formatter::Error; +/// use ocptv_formatter::TestResult; +/// use ocptv_formatter::TestRun; +/// use ocptv_formatter::TestStatus; +/// +/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); +/// test_run.start().unwrap(); +/// ocptv_error!(test_run, "symptom"); +/// test_run +/// .end(TestStatus::Complete, TestResult::Pass) +/// .unwrap(); +/// ``` +/// +/// ## Passing both symptom and message +/// +/// ``` +/// use std::io::Write; +/// use std::io::{self}; +/// +/// use ocptv_formatter::ocptv_error; +/// use ocptv_formatter::Error; +/// use ocptv_formatter::TestResult; +/// use ocptv_formatter::TestRun; +/// use ocptv_formatter::TestStatus; +/// +/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); +/// test_run.start().unwrap(); +/// ocptv_error!(test_run, "symptom", "Error message"); +/// test_run +/// .end(TestStatus::Complete, TestResult::Pass) +/// .unwrap(); +/// ``` +#[macro_export] +macro_rules! ocptv_error { + ($runner:expr , $symptom:expr, $msg:expr) => { + async { + $runner + .error_with_details( + &$crate::Error::builder($symptom) + .message($msg) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; + ($runner:expr, $symptom:expr) => { + async { + $runner + .error_with_details( + &$crate::Error::builder($symptom) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +/// The following macros emit an artifact of type Log. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log +/// +/// Equivalent to the crate::runner::TestRun::log_with_details method. +/// +/// They accept message as only parameter. +/// Information about the source file and line number is automatically added. +/// +/// There is one macro for each severity level: DEBUG, INFO, WARNING, ERROR, and FATAL. +/// +/// # Examples +/// +/// ## DEBUG +/// +/// ``` +/// use std::io::Write; +/// use std::io::{self}; +/// +/// use ocptv_formatter::ocptv_log_debug; +/// use ocptv_formatter::ocptv_log_error; +/// use ocptv_formatter::ocptv_log_fatal; +/// use ocptv_formatter::ocptv_log_info; +/// use ocptv_formatter::ocptv_log_warning; +/// use ocptv_formatter::LogSeverity; +/// use ocptv_formatter::TestResult; +/// use ocptv_formatter::TestRun; +/// use ocptv_formatter::TestStatus; +/// +/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); +/// test_run.start().unwrap(); +/// ocptv_log_debug!(test_run, "Log message"); +/// test_run +/// .end(TestStatus::Complete, TestResult::Pass) +/// .unwrap(); +/// ``` + +#[macro_export] +macro_rules! ocptv_log_debug { + ($runner:expr, $msg:expr) => { + async { + $runner + .log_with_details( + &$crate::Log::builder($msg) + .severity($crate::LogSeverity::Debug) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +#[macro_export] +macro_rules! ocptv_log_info { + ($runner:expr, $msg:expr) => { + async { + $runner + .log_with_details( + &$crate::Log::builder($msg) + .severity($crate::LogSeverity::Info) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +#[macro_export] +macro_rules! ocptv_log_warning { + ($runner:expr, $msg:expr) => { + async { + $runner + .log_with_details( + &$crate::Log::builder($msg) + .severity($crate::LogSeverity::Warning) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +#[macro_export] +macro_rules! ocptv_log_error { + ($runner:expr, $msg:expr) => { + async { + $runner + .log_with_details( + &$crate::Log::builder($msg) + .severity($crate::LogSeverity::Error) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +#[macro_export] +macro_rules! ocptv_log_fatal { + ($runner:expr, $msg:expr) => { + async { + $runner + .log_with_details( + &$crate::Log::builder($msg) + .severity($crate::LogSeverity::Fatal) + .source(file!(), line!() as i32) + .build(), + ) + .await + } + }; +} + +#[cfg(test)] +mod tests { + + use std::sync::Arc; + + use anyhow::Result; + use assert_json_diff::assert_json_include; + use serde_json::json; + use tokio::sync::Mutex; + + use crate::objects::*; + use crate::runner::*; + + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"symptom":"symptom"}}}); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_error!(test_run, "symptom", "Error message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("error") + .ok_or(anyhow::Error::msg("error key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"error":{"message":null,"softwareInfoIds":null,"symptom":"symptom"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_error!(test_run, "symptom").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("error") + .ok_or(anyhow::Error::msg("error key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_debug() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"DEBUG"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_log_debug!(test_run, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_info() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"INFO"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_log_info!(test_run, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_warning() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"WARNING"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_log_warning!(test_run, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_error() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"ERROR"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_log_error!(test_run, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_fatal() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"FATAL"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + ocptv_log_fatal!(test_run, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testRunArtifact") + .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"symptom":"symptom"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + + ocptv_error!(step, "symptom", "Error message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("error") + .ok_or(anyhow::Error::msg("error key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"error":{"message":null,"softwareInfoIds":null,"symptom":"symptom"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + + ocptv_error!(step, "symptom").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("error") + .ok_or(anyhow::Error::msg("error key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_debug_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"DEBUG"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + ocptv_log_debug!(step, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_info_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"INFO"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + ocptv_log_info!(step, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_warning_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"WARNING"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + ocptv_log_warning!(step, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_error_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"ERROR"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + ocptv_log_error!(step, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } + + #[tokio::test] + async fn test_ocptv_log_fatal_in_step() -> Result<()> { + let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"FATAL"}}}); + + let dut = DutInfo::builder("dut_id").build(); + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build(); + + let step = test_run.step("step_name")?; + ocptv_log_fatal!(step, "log message").await?; + + let actual = serde_json::from_str::( + buffer + .lock() + .await + .first() + .ok_or(anyhow::Error::msg("Buffer is empty"))?, + )?; + assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual + .get("testStepArtifact") + .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .get("log") + .ok_or(anyhow::Error::msg("log key does not exist"))?; + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + Ok(()) + } +} diff --git a/src/models.rs b/src/models.rs new file mode 100644 index 0000000..9e54a62 --- /dev/null +++ b/src/models.rs @@ -0,0 +1,685 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use chrono::DateTime; +use serde::Deserialize; +use serde::Serialize; +use serde_json::Map; +use serde_json::Value; + +pub const SPEC_VERSION: (i8, i8) = (2, 0); + +mod rfc3339_format { + use chrono::DateTime; + use chrono::SecondsFormat; + use serde::Deserialize; + use serde::Deserializer; + use serde::Serializer; + use serde::{self}; + + pub fn serialize(date: &DateTime, serializer: S) -> Result + where + S: Serializer, + { + let s = date.to_rfc3339_opts(SecondsFormat::Millis, true); + serializer.serialize_str(&s) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; + Ok(dt.with_timezone(&chrono_tz::Tz::UTC)) + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum TestRunArtifactDescendant { + #[serde(rename = "testRunStart")] + TestRunStart(TestRunStartSpec), + #[serde(rename = "testRunEnd")] + TestRunEnd(TestRunEndSpec), + #[serde(rename = "log")] + Log(LogSpec), + #[serde(rename = "error")] + Error(ErrorSpec), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum OutputArtifactDescendant { + #[serde(rename = "schemaVersion")] + SchemaVersion(SchemaVersionSpec), + #[serde(rename = "testRunArtifact")] + TestRunArtifact(TestRunArtifactSpec), + #[serde(rename = "testStepArtifact")] + TestStepArtifact(TestStepArtifactSpec), +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum TestStepArtifactDescendant { + #[serde(rename = "testStepStart")] + TestStepStart(TestStepStartSpec), + #[serde(rename = "testStepEnd")] + TestStepEnd(TestStepEndSpec), + #[serde(rename = "measurement")] + Measurement(MeasurementSpec), + #[serde(rename = "measurementSeriesStart")] + MeasurementSeriesStart(MeasurementSeriesStartSpec), + #[serde(rename = "measurementSeriesEnd")] + MeasurementSeriesEnd(MeasurementSeriesEndSpec), + #[serde(rename = "measurementSeriesElement")] + MeasurementSeriesElement(MeasurementSeriesElementSpec), + #[serde(rename = "diagnosis")] + Diagnosis(DiagnosisSpec), + #[serde(rename = "log")] + Log(LogSpec), + #[serde(rename = "error")] + Error(ErrorSpec), + #[serde(rename = "file")] + File(FileSpec), + #[serde(rename = "extension")] + Extension(ExtensionSpec), +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum ValidatorType { + #[serde(rename = "EQUAL")] + Equal, + #[serde(rename = "NOT_EQUAL")] + NotEqual, + #[serde(rename = "LESS_THAN")] + LessThan, + #[serde(rename = "LESS_THAN_OR_EQUAL")] + LessThenOrEqual, + #[serde(rename = "GREATER_THAN")] + GreaterThen, + #[serde(rename = "GREATER_THAN_OR_EQUAL")] + GreaterThenOrEqual, + #[serde(rename = "REGEX_MATCH")] + RegexMatch, + #[serde(rename = "REGEX_NO_MATCH")] + RegexNoMatch, + #[serde(rename = "IN_SET")] + InSet, + #[serde(rename = "NOT_IN_SET")] + NotInSet, +} + +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum SubcomponentType { + #[serde(rename = "UNSPECIFIED")] + Unspecified, + #[serde(rename = "ASIC")] + Asic, + #[serde(rename = "ASIC-SUBSYSTEM")] + AsicSubsystem, + #[serde(rename = "BUS")] + Bus, + #[serde(rename = "FUNCTION")] + Function, + #[serde(rename = "CONNECTOR")] + Connector, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum ExtensionContentType { + #[serde(rename = "float")] + Float(f64), + #[serde(rename = "int")] + Int(i64), + #[serde(rename = "bool")] + Bool(bool), + #[serde(rename = "str")] + Str(String), +} + +/// Outcome of a diagnosis operation. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis/$defs/type +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum DiagnosisType { + #[serde(rename = "PASS")] + Pass, + #[serde(rename = "FAIL")] + Fail, + #[serde(rename = "UNKNOWN")] + Unknown, +} + +/// Represents the final execution status of a test. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststatus +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_status.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStatus +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "testStatus")] +pub enum TestStatus { + #[serde(rename = "COMPLETE")] + Complete, + #[serde(rename = "ERROR")] + Error, + #[serde(rename = "SKIP")] + Skip, +} + +/// Represents the final outcome of a test execution. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testresult +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd/$defs/testResult +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "testResult")] +pub enum TestResult { + #[serde(rename = "PASS")] + Pass, + #[serde(rename = "FAIL")] + Fail, + #[serde(rename = "NOT_APPLICABLE")] + NotApplicable, +} +/// Known log severity variants. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#severity +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log/$defs/severity +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +pub enum LogSeverity { + #[serde(rename = "DEBUG")] + Debug, + #[serde(rename = "INFO")] + Info, + #[serde(rename = "WARNING")] + Warning, + #[serde(rename = "ERROR")] + Error, + #[serde(rename = "FATAL")] + Fatal, +} + +/// Type specification for a software component of the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwaretype +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo/properties/softwareType +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "softwareType")] +pub enum SoftwareType { + #[serde(rename = "UNSPECIFIED")] + Unspecified, + #[serde(rename = "FIRMWARE")] + Firmware, + #[serde(rename = "SYSTEM")] + System, + #[serde(rename = "APPLICATION")] + Application, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct OutputArtifactSpec { + #[serde(flatten)] + pub descendant: OutputArtifactDescendant, + // TODO : manage different timezones + #[serde(rename = "timestamp")] + #[serde(with = "rfc3339_format")] + pub now: DateTime, + #[serde(rename = "sequenceNumber")] + pub sequence_number: u64, +} + +/// Low-level model for the `schemaVersion` spec object. +/// Specifies the version that should be used to interpret following json outputs. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/root.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "schemaVersion")] +pub struct SchemaVersionSpec { + #[serde(rename = "major")] + pub major: i8, + #[serde(rename = "minor")] + pub minor: i8, +} + +/// Low-level model for the `testRunArtifact` spec object. +/// Container for the run level artifacts. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-run-artifacts +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_artifact.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunArtifact +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct TestRunArtifactSpec { + #[serde(flatten)] + pub descendant: TestRunArtifactDescendant, +} + +/// Low-level model for the `testRunStart` spec object. +/// Start marker for the beginning of a diagnostic test. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_start.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "testRunStart")] +pub struct TestRunStartSpec { + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "version")] + pub version: String, + #[serde(rename = "commandLine")] + pub command_line: String, + #[serde(rename = "parameters")] + pub parameters: Map, + #[serde(rename = "dutInfo")] + pub dut_info: DutInfoSpec, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `dutInfo` spec object. +/// Contains all relevant information describing the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#dutinfo +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename = "dutInfo")] +pub struct DutInfoSpec { + #[serde(rename = "dutInfoId")] + pub id: String, + #[serde(rename = "name")] + pub name: Option, + #[serde(rename = "platformInfos")] + pub platform_infos: Option>, + #[serde(rename = "softwareInfos")] + pub software_infos: Option>, + #[serde(rename = "hardwareInfos")] + pub hardware_infos: Option>, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `platformInfo` spec object. +/// Describe platform specific attributes of the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#platforminfo +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename = "platformInfo")] +pub struct PlatformInfoSpec { + #[serde(rename = "info")] + pub info: String, +} + +/// Low-level model for the `softwareInfo` spec object. +/// Represents information of a discovered or exercised software component of the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwareinfo +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "softwareInfo")] +pub struct SoftwareInfoSpec { + #[serde(rename = "softwareInfoId")] + pub id: String, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "version")] + pub version: Option, + #[serde(rename = "revision")] + pub revision: Option, + #[serde(rename = "softwareType")] + pub software_type: Option, + #[serde(rename = "computerSystem")] + pub computer_system: Option, +} + +/// Low-level model for the `hardwareInfo` spec object. +/// Represents information of an enumerated or exercised hardware component of the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#hardwareinfo +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename = "hardwareInfo")] +pub struct HardwareInfoSpec { + #[serde(rename = "hardwareInfoId")] + pub id: String, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "version")] + pub version: Option, + #[serde(rename = "revision")] + pub revision: Option, + #[serde(rename = "location")] + pub location: Option, + #[serde(rename = "serialNumber")] + pub serial_no: Option, + #[serde(rename = "partNumber")] + pub part_no: Option, + #[serde(rename = "manufacturer")] + pub manufacturer: Option, + #[serde(rename = "manufacturerPartNumber")] + pub manufacturer_part_no: Option, + #[serde(rename = "odataId")] + pub odata_id: Option, + #[serde(rename = "computerSystem")] + pub computer_system: Option, + #[serde(rename = "manager")] + pub manager: Option, +} + +/// Low-level model for the `testRunEnd` spec object. +/// End marker signaling the finality of a diagnostic test. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "testRunEnd")] +pub struct TestRunEndSpec { + #[serde(rename = "status")] + pub status: TestStatus, + #[serde(rename = "result")] + pub result: TestResult, +} + +/// Low-level model for the `error` spec object. +/// Represents an error encountered by the diagnostic software. It may refer to a DUT +/// component or the diagnostic itself. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/error.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error +#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[serde(rename = "error")] +pub struct ErrorSpec { + #[serde(rename = "symptom")] + pub symptom: String, + #[serde(rename = "message")] + pub message: Option, + // TODO: support this field during serialization to print only the id of SoftwareInfo struct + #[serde(rename = "softwareInfoIds")] + pub software_infos: Option>, + #[serde(rename = "sourceLocation")] + pub source_location: Option, +} + +/// Low-level model for `log` spec object. +/// Is currently relevant for test run and test step artifact types. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "log")] +pub struct LogSpec { + #[serde(rename = "severity")] + pub severity: LogSeverity, + #[serde(rename = "message")] + pub message: String, + #[serde(rename = "sourceLocation")] + pub source_location: Option, +} + +/// Provides information about which file/line of the source code in +/// the diagnostic package generated the output. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#sourcelocation +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/source_location.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] +#[serde(rename = "sourceLocation")] +pub struct SourceLocationSpec { + #[serde(rename = "file")] + pub file: String, + #[serde(rename = "line")] + pub line: i32, +} + +/// Low-level model for the `testStepArtifact` spec object. +/// Container for the step level artifacts. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct TestStepArtifactSpec { + #[serde(flatten)] + pub descendant: TestStepArtifactDescendant, +} + +/// Low-level model for the `testStepStart` spec object. +/// Start marker for a test step inside a diagnosis run. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_start.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "testStepStart")] +pub struct TestStepStartSpec { + #[serde(rename = "name")] + pub name: String, +} + +/// Low-level model for the `testStepEnd` spec object. +/// End marker for a test step inside a diagnosis run. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_end.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "testStepEnd")] +pub struct TestStepEndSpec { + #[serde(rename = "status")] + pub status: TestStatus, +} + +/// Low-level model for the `measurement` spec object. +/// Represents an individual measurement taken by the diagnostic regarding the DUT. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "measurement")] +pub struct MeasurementSpec { + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "value")] + pub value: Value, + #[serde(rename = "unit")] + pub unit: Option, + #[serde(rename = "validators")] + pub validators: Option>, + #[serde(rename = "hardwareInfoId")] + pub hardware_info_id: Option, + #[serde(rename = "subcomponent")] + pub subcomponent: Option, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `validator` spec object. +/// Contains the validation logic that the diagnostic applied for a specific measurement. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#validator +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/validator.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "validator")] +pub struct ValidatorSpec { + #[serde(rename = "name")] + pub name: Option, + #[serde(rename = "type")] + pub validator_type: ValidatorType, + #[serde(rename = "value")] + pub value: Value, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `subcomponent` spec object. +/// Represents a physical subcomponent of a DUT hardware element. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#subcomponent +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/subcomponent.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[serde(rename = "subcomponent")] +pub struct SubcomponentSpec { + #[serde(rename = "type")] + pub subcomponent_type: Option, + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "location")] + pub location: Option, + #[serde(rename = "version")] + pub version: Option, + #[serde(rename = "revision")] + pub revision: Option, +} + +/// Low-level model for the `measurementSeriesStart` spec object. +/// Start marker for a time based series of measurements. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_start.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "measurementSeriesStart")] +pub struct MeasurementSeriesStartSpec { + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "unit")] + pub unit: Option, + #[serde(rename = "measurementSeriesId")] + pub series_id: String, + #[serde(rename = "validators")] + pub validators: Option>, + #[serde(rename = "hardwareInfoId")] + pub hardware_info: Option, + #[serde(rename = "subComponent")] + pub subcomponent: Option, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `measurementSeriesEnd` spec object. +/// End marker for a time based series of measurements. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_end.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "measurementSeriesEnd")] +pub struct MeasurementSeriesEndSpec { + #[serde(rename = "measurementSeriesId")] + pub series_id: String, + #[serde(rename = "totalCount")] + pub total_count: u64, +} + +/// Low-level model for the `measurementSeriesElement` spec object. +/// Equivalent to the `Measurement` model but inside a time based series. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_element.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesElement +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "measurementSeriesElement")] +pub struct MeasurementSeriesElementSpec { + #[serde(rename = "index")] + pub index: u64, + #[serde(rename = "value")] + pub value: Value, + #[serde(with = "rfc3339_format")] + pub timestamp: DateTime, + #[serde(rename = "measurementSeriesId")] + pub series_id: String, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `diagnosis` spec object. +/// Contains the verdict given by the diagnostic regarding the DUT that was inspected. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "diagnosis")] +pub struct DiagnosisSpec { + #[serde(rename = "verdict")] + pub verdict: String, + #[serde(rename = "type")] + pub diagnosis_type: DiagnosisType, + #[serde(rename = "message")] + pub message: Option, + #[serde(rename = "validators")] + pub hardware_info: Option, + #[serde(rename = "subComponent")] + pub subcomponent: Option, + #[serde(rename = "sourceLocation")] + pub source_location: Option, +} + +/// Low-level model for the `file` spec object. +/// Represents a file artifact that was generated by running the diagnostic. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/file.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "file")] +pub struct FileSpec { + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "uri")] + pub uri: String, + #[serde(rename = "isSnapshot")] + pub is_snapshot: bool, + #[serde(rename = "description")] + pub description: Option, + #[serde(rename = "contentType")] + pub content_type: Option, + #[serde(rename = "metadata")] + pub metadata: Option>, +} + +/// Low-level model for the `extension` spec object. +/// Left as an implementation detail, the `Extension` just has a name and arbitrary data. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension +/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json +/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename = "extension")] +pub struct ExtensionSpec { + #[serde(rename = "name")] + pub name: String, + #[serde(rename = "content")] + pub content: ExtensionContentType, +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use assert_json_diff::assert_json_include; + use chrono::SecondsFormat; + use serde_json::json; + + use super::*; + + #[test] + fn test_rfc3339_format_serialize() -> Result<()> { + let test_date = "2022-01-01T00:00:00.000Z"; + let msr = MeasurementSeriesElementSpec { + index: 0, + value: Value::from(1.0), + timestamp: DateTime::parse_from_rfc3339(test_date)?.with_timezone(&chrono_tz::UTC), + series_id: "test".to_string(), + metadata: None, + }; + let json = serde_json::to_value(msr)?; + assert_json_include!(actual: json, expected: json!({ + "timestamp": test_date, + })); + + Ok(()) + } + + #[test] + fn test_rfc3339_format_deserialize() -> Result<()> { + let test_date = "2022-01-01T00:00:00.000Z"; + let json = json!({"index":0,"measurementSeriesId":"test","metadata":null,"timestamp":"2022-01-01T00:00:00.000Z","value":1.0}); + + let msr = serde_json::from_value::(json)?; + assert_eq!( + msr.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), + test_date + ); + + Ok(()) + } +} diff --git a/src/objects.rs b/src/objects.rs new file mode 100644 index 0000000..992ed11 --- /dev/null +++ b/src/objects.rs @@ -0,0 +1,1791 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use chrono::DateTime; +use serde_json::Map; +use serde_json::Value; + +use crate::models; + +pub enum ArtifactContext { + TestRun, + TestStep, +} + +pub struct SchemaVersion { + major: i8, + minor: i8, +} + +impl SchemaVersion { + pub fn new() -> SchemaVersion { + SchemaVersion { + major: models::SPEC_VERSION.0, + minor: models::SPEC_VERSION.1, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { + major: self.major, + minor: self.minor, + }) + } +} + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct DutInfo { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfo { + pub fn builder(id: &str) -> DutInfoBuilder { + DutInfoBuilder::new(id) + } + + pub fn new(id: &str) -> DutInfo { + DutInfoBuilder::new(id).build() + } + + pub(crate) fn to_spec(&self) -> models::DutInfoSpec { + models::DutInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + platform_infos: self + .platform_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + software_infos: self + .software_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + hardware_infos: self + .hardware_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + metadata: self.metadata.clone(), + } + } +} + +pub struct DutInfoBuilder { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfoBuilder { + pub fn new(id: &str) -> DutInfoBuilder { + DutInfoBuilder { + id: id.to_string(), + name: None, + platform_infos: None, + software_infos: None, + hardware_infos: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> DutInfoBuilder { + self.name = Some(value.to_string()); + self + } + + pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { + self.platform_infos = match self.platform_infos { + Some(mut platform_infos) => { + platform_infos.push(platform_info.clone()); + Some(platform_infos) + } + None => Some(vec![platform_info.clone()]), + }; + self + } + + pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.clone()); + Some(software_infos) + } + None => Some(vec![software_info.clone()]), + }; + self + } + + pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { + self.hardware_infos = match self.hardware_infos { + Some(mut hardware_infos) => { + hardware_infos.push(hardware_info.clone()); + Some(hardware_infos) + } + None => Some(vec![hardware_info.clone()]), + }; + self + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> DutInfoBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> DutInfo { + DutInfo { + id: self.id, + name: self.name, + platform_infos: self.platform_infos, + software_infos: self.software_infos, + hardware_infos: self.hardware_infos, + metadata: self.metadata, + } + } +} + +pub struct TestRunStart { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: DutInfo, +} + +impl TestRunStart { + pub fn builder( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + name: self.name.clone(), + version: self.version.clone(), + command_line: self.command_line.clone(), + parameters: self.parameters.clone(), + metadata: self.metadata.clone(), + dut_info: self.dut_info.to_spec(), + }), + }) + } +} + +pub struct TestRunStartBuilder { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: DutInfo, +} + +impl TestRunStartBuilder { + pub fn new( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder { + name: name.to_string(), + version: version.to_string(), + command_line: command_line.to_string(), + parameters: parameters.clone(), + metadata: None, + dut_info: dut_info.clone(), + } + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRunStart { + TestRunStart { + name: self.name, + version: self.version, + command_line: self.command_line, + parameters: self.parameters, + metadata: self.metadata, + dut_info: self.dut_info, + } + } +} + +pub struct TestRunEnd { + status: models::TestStatus, + result: models::TestResult, +} + +impl TestRunEnd { + pub fn builder() -> TestRunEndBuilder { + TestRunEndBuilder::new() + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + status: self.status.clone(), + result: self.result.clone(), + }), + }) + } +} + +#[derive(Debug)] +pub struct TestRunEndBuilder { + status: models::TestStatus, + result: models::TestResult, +} + +impl TestRunEndBuilder { + pub fn new() -> TestRunEndBuilder { + TestRunEndBuilder { + status: models::TestStatus::Complete, + result: models::TestResult::Pass, + } + } + pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { + self.status = value; + self + } + + pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { + self.result = value; + self + } + + pub fn build(self) -> TestRunEnd { + TestRunEnd { + status: self.status, + result: self.result, + } + } +} + +pub struct Log { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl Log { + pub fn builder(message: &str) -> LogBuilder { + LogBuilder::new(message) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct LogBuilder { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl LogBuilder { + fn new(message: &str) -> Self { + LogBuilder { + severity: models::LogSeverity::Info, + message: message.to_string(), + source_location: None, + } + } + pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { + self.severity = value; + self + } + pub fn source(mut self, file: &str, line: i32) -> LogBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + + pub fn build(self) -> Log { + Log { + severity: self.severity, + message: self.message, + source_location: self.source_location, + } + } +} + +pub struct Error { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl Error { + pub fn builder(symptom: &str) -> ErrorBuilder { + ErrorBuilder::new(symptom) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct ErrorBuilder { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl ErrorBuilder { + fn new(symptom: &str) -> Self { + ErrorBuilder { + symptom: symptom.to_string(), + message: None, + source_location: None, + software_infos: None, + } + } + pub fn message(mut self, value: &str) -> ErrorBuilder { + self.message = Some(value.to_string()); + self + } + pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> ErrorBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.to_spec()); + Some(software_infos) + } + None => Some(vec![software_info.to_spec()]), + }; + self + } + + pub fn build(self) -> Error { + Error { + symptom: self.symptom, + message: self.message, + source_location: self.source_location, + software_infos: self.software_infos, + } + } +} + +pub struct TestStepStart { + name: String, +} + +impl TestStepStart { + pub fn new(name: &str) -> TestStepStart { + TestStepStart { + name: name.to_string(), + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepStart( + models::TestStepStartSpec { + name: self.name.clone(), + }, + ), + }) + } +} + +pub struct TestStepEnd { + status: models::TestStatus, +} + +impl TestStepEnd { + pub fn new(status: models::TestStatus) -> TestStepEnd { + TestStepEnd { status } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { + status: self.status.clone(), + }), + }) + } +} + +#[derive(Clone)] +pub struct Validator { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl Validator { + pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { + ValidatorBuilder::new(validator_type, value) + } + pub fn to_spec(&self) -> models::ValidatorSpec { + models::ValidatorSpec { + name: self.name.clone(), + validator_type: self.validator_type.clone(), + value: self.value.clone(), + metadata: self.metadata.clone(), + } + } +} + +#[derive(Debug)] +pub struct ValidatorBuilder { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl ValidatorBuilder { + fn new(validator_type: models::ValidatorType, value: Value) -> Self { + ValidatorBuilder { + validator_type, + value: value.clone(), + name: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> ValidatorBuilder { + self.name = Some(value.to_string()); + self + } + pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> Validator { + Validator { + name: self.name, + validator_type: self.validator_type, + value: self.value, + metadata: self.metadata, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct HardwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfo { + pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { + HardwareInfoBuilder::new(id, name) + } + pub fn to_spec(&self) -> models::HardwareInfoSpec { + models::HardwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + location: self.location.clone(), + serial_no: self.serial_no.clone(), + part_no: self.part_no.clone(), + manufacturer: self.manufacturer.clone(), + manufacturer_part_no: self.manufacturer_part_no.clone(), + odata_id: self.odata_id.clone(), + computer_system: self.computer_system.clone(), + manager: self.manager.clone(), + } + } +} + +#[derive(Debug)] +pub struct HardwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + HardwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + location: None, + serial_no: None, + part_no: None, + manufacturer: None, + manufacturer_part_no: None, + odata_id: None, + computer_system: None, + manager: None, + } + } + pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + self.location = Some(value.to_string()); + self + } + pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + self.serial_no = Some(value.to_string()); + self + } + pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.part_no = Some(value.to_string()); + self + } + pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer = Some(value.to_string()); + self + } + pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer_part_no = Some(value.to_string()); + self + } + pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + self.odata_id = Some(value.to_string()); + self + } + pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + self.manager = Some(value.to_string()); + self + } + + pub fn build(self) -> HardwareInfo { + HardwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + location: self.location, + serial_no: self.serial_no, + part_no: self.part_no, + manufacturer: self.manufacturer, + manufacturer_part_no: self.manufacturer_part_no, + odata_id: self.odata_id, + computer_system: self.computer_system, + manager: self.manager, + } + } +} + +#[derive(Debug, Clone)] +pub struct Subcomponent { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl Subcomponent { + pub fn builder(name: &str) -> SubcomponentBuilder { + SubcomponentBuilder::new(name) + } + pub fn to_spec(&self) -> models::SubcomponentSpec { + models::SubcomponentSpec { + subcomponent_type: self.subcomponent_type.clone(), + name: self.name.clone(), + location: self.location.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + } + } +} + +#[derive(Debug)] +pub struct SubcomponentBuilder { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl SubcomponentBuilder { + fn new(name: &str) -> Self { + SubcomponentBuilder { + subcomponent_type: None, + name: name.to_string(), + location: None, + version: None, + revision: None, + } + } + pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { + self.subcomponent_type = Some(value); + self + } + pub fn version(mut self, value: &str) -> SubcomponentBuilder { + self.version = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> SubcomponentBuilder { + self.location = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SubcomponentBuilder { + self.revision = Some(value.to_string()); + self + } + + pub fn build(self) -> Subcomponent { + Subcomponent { + subcomponent_type: self.subcomponent_type, + name: self.name, + location: self.location, + version: self.version, + revision: self.revision, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PlatformInfo { + info: String, +} + +impl PlatformInfo { + pub fn builder(info: &str) -> PlatformInfoBuilder { + PlatformInfoBuilder::new(info) + } + + pub fn to_spec(&self) -> models::PlatformInfoSpec { + models::PlatformInfoSpec { + info: self.info.clone(), + } + } +} + +#[derive(Debug)] +pub struct PlatformInfoBuilder { + info: String, +} + +impl PlatformInfoBuilder { + fn new(info: &str) -> Self { + PlatformInfoBuilder { + info: info.to_string(), + } + } + + pub fn build(self) -> PlatformInfo { + PlatformInfo { info: self.info } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SoftwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfo { + pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { + SoftwareInfoBuilder::new(id, name) + } + + pub fn to_spec(&self) -> models::SoftwareInfoSpec { + models::SoftwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + software_type: self.software_type.clone(), + computer_system: self.computer_system.clone(), + } + } +} + +#[derive(Debug)] +pub struct SoftwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + SoftwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + software_type: None, + computer_system: None, + } + } + pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { + self.software_type = Some(value); + self + } + pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + + pub fn build(self) -> SoftwareInfo { + SoftwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + software_type: self.software_type, + computer_system: self.computer_system, + } + } +} + +/// This structure represents a Measurement message. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// +/// # Examples +/// +/// ## Create a Measurement object with the `new` method +/// +/// ``` +/// use ocptv_formatter::Measurement; +/// use ocptv_formatter::Value; +/// +/// let measurement = Measurement::new("name", Value::from(50)); +/// ``` +/// +/// ## Create a Measurement object with the `builder` method +/// +/// ``` +/// use ocptv_formatter::HardwareInfo; +/// use ocptv_formatter::Measurement; +/// use ocptv_formatter::Subcomponent; +/// use ocptv_formatter::Validator; +/// use ocptv_formatter::ValidatorType; +/// use ocptv_formatter::Value; +/// +/// let measurement = Measurement::builder("name", Value::from(50)) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) +/// .add_metadata("key", Value::from("value")) +/// .subcomponent(&Subcomponent::builder("name").build()) +/// .build(); +/// ``` +pub struct Measurement { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl Measurement { + /// Builds a new Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Measurement; + /// use ocptv_formatter::Value; + /// + /// let measurement = Measurement::new("name", Value::from(50)); + /// ``` + pub fn new(name: &str, value: Value) -> Self { + Measurement { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Builds a new Measurement object using [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::HardwareInfo; + /// use ocptv_formatter::Measurement; + /// use ocptv_formatter::Subcomponent; + /// use ocptv_formatter::Validator; + /// use ocptv_formatter::ValidatorType; + /// use ocptv_formatter::Value; + /// + /// let measurement = Measurement::builder("name", Value::from(50)) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) + /// .add_metadata("key", Value::from("value")) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// ``` + pub fn builder(name: &str, value: Value) -> MeasurementBuilder { + MeasurementBuilder::new(name, value) + } + + /// Creates an artifact from a Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Measurement; + /// use ocptv_formatter::Value; + /// + /// let measurement = Measurement::new("name", Value::from(50)); + /// let _ = measurement.to_artifact(); + /// ``` + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { + name: self.name.clone(), + unit: self.unit.clone(), + value: self.value.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info_id: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.id.clone()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }), + }) + } +} + +/// This structure builds a [`Measurement`] object. +/// +/// # Examples +/// +/// ``` +/// use ocptv_formatter::HardwareInfo; +/// use ocptv_formatter::Measurement; +/// use ocptv_formatter::MeasurementBuilder; +/// use ocptv_formatter::Subcomponent; +/// use ocptv_formatter::Validator; +/// use ocptv_formatter::ValidatorType; +/// use ocptv_formatter::Value; +/// +/// let builder = MeasurementBuilder::new("name", Value::from(50)) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) +/// .add_metadata("key", Value::from("value")) +/// .subcomponent(&Subcomponent::builder("name").build()); +/// let measurement = builder.build(); +/// ``` +pub struct MeasurementBuilder { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementBuilder { + /// Creates a new MeasurementBuilder. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50)); + /// ``` + pub fn new(name: &str, value: Value) -> Self { + MeasurementBuilder { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Add a [`Validator`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::HardwareInfo; + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Subcomponent; + /// use ocptv_formatter::Validator; + /// use ocptv_formatter::ValidatorType; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50)) + /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()); + /// ``` + pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { + self.validators = match self.validators { + Some(mut validators) => { + validators.push(validator.clone()); + Some(validators) + } + None => Some(vec![validator.clone()]), + }; + self + } + + /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::HardwareInfo; + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50)) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()); + /// ``` + pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + /// Add a [`Subcomponent`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Subcomponent; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50)) + /// .subcomponent(&Subcomponent::builder("name").build()); + /// ``` + pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + /// Add custom metadata to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Value; + /// + /// let builder = + /// MeasurementBuilder::new("name", Value::from(50)).add_metadata("key", Value::from("value")); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { + match self.metadata { + Some(ref mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + } + None => { + self.metadata = Some(Map::new()); + self.metadata + .as_mut() + .unwrap() + .insert(key.to_string(), value.clone()); + } + }; + self + } + + /// Add measurement unit to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50000)).unit("RPM"); + /// ``` + pub fn unit(mut self, unit: &str) -> MeasurementBuilder { + self.unit = Some(unit.to_string()); + self + } + + /// Builds a [`Measurement`] object from a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementBuilder; + /// use ocptv_formatter::Value; + /// + /// let builder = MeasurementBuilder::new("name", Value::from(50)); + /// let measurement = builder.build(); + /// ``` + pub fn build(self) -> Measurement { + Measurement { + name: self.name, + value: self.value, + unit: self.unit, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesStart { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStart { + pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { + MeasurementSeriesStartBuilder::new(name, series_id) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: self.name.clone(), + unit: self.unit.clone(), + series_id: self.series_id.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.to_spec()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }, + ), + }) + } + + pub fn get_series_id(&self) -> &str { + &self.series_id + } +} + +pub struct MeasurementSeriesStartBuilder { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStartBuilder { + pub fn new(name: &str, series_id: &str) -> Self { + MeasurementSeriesStartBuilder { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { + self.validators = match self.validators { + Some(mut validators) => { + validators.push(validator.clone()); + Some(validators) + } + None => Some(vec![validator.clone()]), + }; + self + } + + pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementSeriesStartBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementSeriesStartBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { + self.unit = Some(unit.to_string()); + self + } + + pub fn build(self) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: self.name, + unit: self.unit, + series_id: self.series_id, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesEnd { + series_id: String, + total_count: u64, +} + +impl MeasurementSeriesEnd { + pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { + MeasurementSeriesEnd { + series_id: series_id.to_string(), + total_count, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: self.series_id.clone(), + total_count: self.total_count, + }, + ), + }) + } +} + +pub struct MeasurementSeriesElement { + index: u64, + value: Value, + timestamp: DateTime, + series_id: String, + metadata: Option>, +} + +impl MeasurementSeriesElement { + pub(crate) fn new( + index: u64, + value: Value, + series: &MeasurementSeriesStart, + metadata: Option>, + ) -> MeasurementSeriesElement { + MeasurementSeriesElement { + index, + value: value.clone(), + timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + series_id: series.series_id.to_string(), + metadata, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( + models::MeasurementSeriesElementSpec { + index: self.index, + value: self.value.clone(), + timestamp: self.timestamp, + series_id: self.series_id.clone(), + metadata: self.metadata.clone(), + }, + ), + }) + } +} + +#[cfg(test)] +mod tests { + use assert_json_diff::assert_json_include; + use serde_json::Map; + use serde_json::Value; + + use super::*; + use crate::models; + use crate::models::ValidatorType; + + #[test] + fn test_schema_creation_from_builder() { + let version = super::SchemaVersion::new(); + assert_eq!(version.major, models::SPEC_VERSION.0); + assert_eq!(version.minor, models::SPEC_VERSION.1); + } + + #[test] + fn test_dut_creation_from_builder_with_defaults() { + let dut = super::DutInfo::builder("1234").build(); + assert_eq!(dut.id, "1234"); + } + + #[test] + fn test_log_output_as_test_run_descendant_to_artifact() { + let log = super::Log::builder("test") + .severity(super::models::LogSeverity::Info) + .build(); + let artifact = log.to_artifact(super::ArtifactContext::TestRun); + assert_eq!( + artifact, + super::models::OutputArtifactDescendant::TestRunArtifact( + super::models::TestRunArtifactSpec { + descendant: super::models::TestRunArtifactDescendant::Log( + super::models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + } + ), + } + ) + ); + } + + #[test] + fn test_log_output_as_test_step_descendant_to_artifact() { + let log = super::Log::builder("test") + .severity(super::models::LogSeverity::Info) + .build(); + let artifact = log.to_artifact(super::ArtifactContext::TestStep); + assert_eq!( + artifact, + super::models::OutputArtifactDescendant::TestStepArtifact( + super::models::TestStepArtifactSpec { + descendant: super::models::TestStepArtifactDescendant::Log( + super::models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + } + ), + } + ) + ); + } + + #[test] + fn test_error_output_as_test_run_descendant_to_artifact() { + let error = super::Error::builder("symptom") + .message("") + .add_software_info(&super::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + let artifact = error.to_artifact(super::ArtifactContext::TestRun); + assert_eq!( + artifact, + super::models::OutputArtifactDescendant::TestRunArtifact( + super::models::TestRunArtifactSpec { + descendant: super::models::TestRunArtifactDescendant::Error( + super::models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } + ), + } + ) + ); + } + + #[test] + fn test_error_output_as_test_step_descendant_to_artifact() { + let error = super::Error::builder("symptom") + .message("") + .add_software_info(&super::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + let artifact = error.to_artifact(super::ArtifactContext::TestStep); + assert_eq!( + artifact, + super::models::OutputArtifactDescendant::TestStepArtifact( + super::models::TestStepArtifactSpec { + descendant: super::models::TestStepArtifactDescendant::Error( + super::models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } + ), + } + ) + ); + } + + #[test] + fn test_measurement_as_test_step_descendant_to_artifact() { + let name = String::from("name"); + let value = Value::from(50); + let measurement = super::Measurement::new(&name, value.clone()); + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + } + + #[test] + fn test_measurement_builder_as_test_step_descendant_to_artifact() { + let name = String::from("name"); + let value = Value::from(50000); + let hardware_info = HardwareInfo::builder("id", "name").build(); + let validator = Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(); + let meta_key = "key"; + let meta_value = Value::from("value"); + let mut metadata = Map::new(); + metadata.insert(meta_key.to_string(), meta_value.clone()); + metadata.insert(meta_key.to_string(), meta_value.clone()); + let subcomponent = Subcomponent::builder("name").build(); + let unit = "RPM"; + let measurement = Measurement::builder(&name, value.clone()) + .hardware_info(&hardware_info) + .add_validator(&validator) + .add_validator(&validator) + .add_metadata(meta_key, meta_value.clone()) + .add_metadata(meta_key, meta_value.clone()) + .subcomponent(&subcomponent) + .unit(unit) + .build(); + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name: name.to_string(), + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + } + ), + }) + ); + } + + #[test] + fn test_measurement_series_start_to_artifact() { + let name = String::from("name"); + let series_id = String::from("series_id"); + let series = super::MeasurementSeriesStart::new(&name, &series_id); + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + } + + #[test] + fn test_measurement_series_start_builder_to_artifact() { + let name = String::from("name"); + let series_id = String::from("series_id"); + let validator = Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(); + let validator2 = + Validator::builder(models::ValidatorType::GreaterThen, Value::from(10)).build(); + let hw_info = HardwareInfo::builder("id", "name").build(); + let subcomponent = Subcomponent::builder("name").build(); + let series = super::MeasurementSeriesStart::builder(&name, &series_id) + .unit("unit") + .add_metadata("key", Value::from("value")) + .add_metadata("key2", Value::from("value2")) + .add_validator(&validator) + .add_validator(&validator2) + .hardware_info(&hw_info) + .subcomponent(&subcomponent) + .build(); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: name.to_string(), + unit: Some("unit".to_string()), + series_id: series_id.to_string(), + validators: Some(vec![validator.to_spec(), validator2.to_spec()]), + hardware_info: Some(hw_info.to_spec()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(Map::from_iter([ + ("key".to_string(), Value::from("value")), + ("key2".to_string(), Value::from("value2")) + ])), + } + ), + }) + ); + } + + #[test] + fn test_measurement_series_end_to_artifact() { + let series_id = String::from("series_id"); + let series = super::MeasurementSeriesEnd::new(&series_id, 1); + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: series_id.to_string(), + total_count: 1, + } + ), + }) + ); + } + + #[test] + fn test_dut_builder() { + let platform = super::PlatformInfo::builder("platform_info").build(); + let software = super::SoftwareInfo::builder("software_id", "name").build(); + let hardware = super::HardwareInfo::builder("hardware_id", "name").build(); + let dut = super::DutInfo::builder("1234") + .name("DUT") + .add_metadata("key", Value::from("value")) + .add_metadata("key2", Value::from("value2")) + .add_hardware_info(&hardware) + .add_hardware_info(&hardware) + .add_platform_info(&platform) + .add_platform_info(&platform) + .add_software_info(&software) + .add_software_info(&software) + .build(); + assert_eq!(dut.to_spec().id, "1234"); + assert_eq!(dut.to_spec().name.unwrap(), "DUT"); + assert_eq!(dut.to_spec().metadata.unwrap()["key"], "value"); + assert_eq!(dut.to_spec().metadata.unwrap()["key2"], "value2"); + assert_eq!( + dut.to_spec().hardware_infos.unwrap().first().unwrap().id, + "hardware_id" + ); + assert_eq!( + dut.to_spec().software_infos.unwrap().first().unwrap().id, + "software_id" + ); + assert_eq!( + dut.to_spec().platform_infos.unwrap().first().unwrap().info, + "platform_info" + ); + } + + #[test] + fn test_error() { + let expected_run = serde_json::json!({"testRunArtifact": {"error": {"message": "message", "softwareInfoIds": [{"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "software_id", "softwareType": null, "version": null}, {"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "software_id", "softwareType": null, "version": null}], "sourceLocation": {"file": "file.rs", "line": 1}, "symptom": "symptom"}}}); + let expected_step = serde_json::json!({"testStepArtifact":{"error":{"message":"message","softwareInfoIds":[{"computerSystem":null,"name":"name","revision":null,"softwareInfoId":"software_id","softwareType":null,"version":null},{"computerSystem":null,"name":"name","revision":null,"softwareInfoId":"software_id","softwareType":null,"version":null}],"sourceLocation":{"file":"file.rs","line":1},"symptom":"symptom"}}}); + + let software = super::SoftwareInfo::builder("software_id", "name").build(); + let error = super::ErrorBuilder::new("symptom") + .message("message") + .source("file.rs", 1) + .add_software_info(&software) + .add_software_info(&software) + .build(); + let spec = error.to_artifact(ArtifactContext::TestRun); + let actual = serde_json::json!(spec); + assert_json_include!(actual: actual, expected: &expected_run); + + let spec = error.to_artifact(ArtifactContext::TestStep); + let actual = serde_json::json!(spec); + assert_json_include!(actual: actual, expected: &expected_step); + } + + #[test] + fn test_validator() { + let validator = super::Validator::builder(ValidatorType::Equal, Value::from(30)) + .name("validator") + .add_metadata("key", Value::from("value")) + .add_metadata("key2", Value::from("value2")) + .build(); + + assert_eq!(validator.to_spec().name.unwrap(), "validator"); + assert_eq!(validator.to_spec().value, 30); + assert_eq!(validator.to_spec().validator_type, ValidatorType::Equal); + assert_eq!(validator.to_spec().metadata.unwrap()["key"], "value"); + assert_eq!(validator.to_spec().metadata.unwrap()["key2"], "value2"); + } + + #[test] + fn test_hardware_info() { + let info = super::HardwareInfo::builder("hardware_id", "hardware_name") + .version("version") + .revision("revision") + .location("location") + .serial_no("serial_no") + .part_no("part_no") + .manufacturer("manufacturer") + .manufacturer_part_no("manufacturer_part_no") + .odata_id("odata_id") + .computer_system("computer_system") + .manager("manager") + .build(); + + assert_eq!(info.to_spec().id, "hardware_id"); + assert_eq!(info.to_spec().name, "hardware_name"); + assert_eq!(info.to_spec().version.unwrap(), "version"); + assert_eq!(info.to_spec().revision.unwrap(), "revision"); + assert_eq!(info.to_spec().location.unwrap(), "location"); + assert_eq!(info.to_spec().serial_no.unwrap(), "serial_no"); + assert_eq!(info.to_spec().part_no.unwrap(), "part_no"); + assert_eq!(info.to_spec().manufacturer.unwrap(), "manufacturer"); + assert_eq!( + info.to_spec().manufacturer_part_no.unwrap(), + "manufacturer_part_no" + ); + assert_eq!(info.to_spec().odata_id.unwrap(), "odata_id"); + assert_eq!(info.to_spec().computer_system.unwrap(), "computer_system"); + assert_eq!(info.to_spec().manager.unwrap(), "manager"); + } + + #[test] + fn test_subcomponent() { + let sub = super::Subcomponent::builder("sub_name") + .subcomponent_type(models::SubcomponentType::Asic) + .version("version") + .location("location") + .revision("revision") + .build(); + + assert_eq!(sub.to_spec().name, "sub_name"); + assert_eq!(sub.to_spec().version.unwrap(), "version"); + assert_eq!(sub.to_spec().revision.unwrap(), "revision"); + assert_eq!(sub.to_spec().location.unwrap(), "location"); + assert_eq!( + sub.to_spec().subcomponent_type.unwrap(), + models::SubcomponentType::Asic + ); + } + + #[test] + fn test_platform_info() { + let info = super::PlatformInfo::builder("info").build(); + + assert_eq!(info.to_spec().info, "info"); + } + + #[test] + fn test_software_info() { + let info = super::SoftwareInfo::builder("software_id", "name") + .version("version") + .revision("revision") + .software_type(models::SoftwareType::Application) + .computer_system("system") + .build(); + + assert_eq!(info.to_spec().id, "software_id"); + assert_eq!(info.to_spec().name, "name"); + assert_eq!(info.to_spec().version.unwrap(), "version"); + assert_eq!(info.to_spec().revision.unwrap(), "revision"); + assert_eq!( + info.to_spec().software_type.unwrap(), + models::SoftwareType::Application + ); + assert_eq!(info.to_spec().computer_system.unwrap(), "system"); + } +} diff --git a/src/runner.rs b/src/runner.rs new file mode 100644 index 0000000..729be67 --- /dev/null +++ b/src/runner.rs @@ -0,0 +1,2447 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +//! OCPTV library runner +//! +//! This module contains the main entry point for the test runner. This is the +//! main object the user will interact with. + +use std::env; +use std::future::Future; +use std::path::Path; +use std::sync::atomic; +use std::sync::Arc; + +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::emitters; +use crate::models; +use crate::objects; + +/// The configuration repository for the TestRun. +pub struct Config { + timezone: chrono_tz::Tz, + writer: emitters::WriterType, +} + +impl Config { + /// Creates a new [`ConfigBuilder`] + /// + /// # Examples + /// ``` + /// use ocptv_formatter::Config; + /// + /// let builder = Config::builder(); + /// ``` + pub fn builder() -> ConfigBuilder { + ConfigBuilder::new() + } +} + +/// The builder for the [`Config`] object. +pub struct ConfigBuilder { + timezone: Option, + writer: Option, +} + +impl ConfigBuilder { + fn new() -> Self { + Self { + timezone: None, + writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } + + pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { + self.timezone = Some(timezone); + self + } + + pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { + self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( + buffer, + ))); + self + } + + pub async fn with_file_output>( + mut self, + path: P, + ) -> Result { + self.writer = Some(emitters::WriterType::File( + emitters::FileWriter::new(path).await?, + )); + Ok(self) + } + + pub fn build(self) -> Config { + Config { + timezone: self.timezone.unwrap_or(chrono_tz::UTC), + writer: self + .writer + .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } +} + +/// The outcome of a TestRun. +/// It's returned when the scope method of the [`TestRun`] object is used. +pub struct TestRunOutcome { + /// Reports the execution status of the test + pub status: models::TestStatus, + /// Reports the result of the test + pub result: models::TestResult, +} + +struct TestState { + emitter: emitters::JsonEmitter, +} + +impl TestState { + fn new(emitter: emitters::JsonEmitter) -> TestState { + TestState { emitter } + } +} + +/// The main diag test run. +/// This object describes a single run instance of the diag, and therefore drives the test session. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart + +pub struct TestRun { + name: String, + version: String, + parameters: Map, + dut: objects::DutInfo, + command_line: String, + metadata: Option>, + state: Arc>, +} + +impl TestRun { + /// Creates a new [`TestRunBuilder`] object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::DutInfo; + /// use ocptv_formatter::TestRun; + /// + /// let dut = DutInfo::builder("my_dut").build(); + /// let builder = TestRun::builder("run_name", &dut, "1.0"); + /// ``` + pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { + TestRunBuilder::new(name, dut, version) + } + + /// Creates a new [`TestRun`] object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// ``` + pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { + let dut = objects::DutInfo::new(dut_id); + TestRunBuilder::new(name, &dut, version).build() + } + + /// Starts the test run. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + let version = objects::SchemaVersion::new(); + self.state + .lock() + .await + .emitter + .emit(&version.to_artifact()) + .await?; + + let mut builder = objects::TestRunStart::builder( + &self.name, + &self.version, + &self.command_line, + &self.parameters, + &self.dut, + ); + if self.metadata.is_some() { + for m in self.metadata.as_ref().unwrap().into_iter() { + builder = builder.add_metadata(m.0, m.1.clone()) + } + } + let start = builder.build(); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the test run. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn end( + &self, + status: models::TestStatus, + result: models::TestResult, + ) -> Result<(), emitters::WriterError> { + let end = objects::TestRunEnd::builder() + .status(status) + .result(result) + .build(); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`TestRun`] object, taking care of starting and + /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`TestRun`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestRunOutcome; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.scope(|r| { + /// r.log(LogSeverity::Info, "First message")?; + /// TestRunOutcome { + /// status: TestStatus::Complete, + /// result: TestResult::Pass, + /// } + /// })?; + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a TestRun) -> R, + { + self.start().await?; + let outcome = func(self).await?; + self.end(outcome.status, outcome.result).await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// )?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + self.state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Log; + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// )?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.error("symptom")?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom and + /// another [`std::string::String`] as error message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.error("symptom", "error messasge")?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method acceps a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Error; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// test_run.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// )?; + /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + pub fn step(&self, name: &str) -> Result { + Ok(TestStep::new(name, self.state.clone())) + } +} + +/// Builder for the [`TestRun`] object. +pub struct TestRunBuilder { + name: String, + dut: objects::DutInfo, + version: String, + parameters: Map, + command_line: String, + metadata: Option>, + config: Option, +} + +impl TestRunBuilder { + fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + Self { + name: name.to_string(), + dut: dut.clone(), + version: version.to_string(), + parameters: Map::new(), + command_line: env::args().collect::>()[1..].join(" "), + metadata: None, + config: None, + } + } + + /// Adds a user defined parameter to the future [`TestRun`] object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::DutInfo; + /// use ocptv_formatter::TestRunBuilder; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_parameter("param1", "value1") + /// .build(); + /// ``` + pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { + self.parameters.insert(key.to_string(), value.clone()); + self + } + + /// Adds the command line used to run the test session to the future + /// [`TestRun`] object. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::DutInfo; + /// use ocptv_formatter::TestRunBuilder; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .command_line("my_diag --arg value") + /// .build(); + /// ``` + pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + self.command_line = cmd.to_string(); + self + } + + /// Adds the configuration for the test session to the future [`TestRun`] object + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Config; + /// use ocptv_formatter::DutInfo; + /// use ocptv_formatter::TestRunBuilder; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .config(Config::builder().build()) + /// .build(); + /// ``` + pub fn config(mut self, value: Config) -> TestRunBuilder { + self.config = Some(value); + self + } + + /// Adds user defined metadata to the future [`TestRun`] object + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::DutInfo; + /// use ocptv_formatter::TestRunBuilder; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_metadata("meta1", "value1") + /// .build(); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRun { + let config = self.config.unwrap_or(Config::builder().build()); + let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let state = TestState::new(emitter); + TestRun { + name: self.name, + dut: self.dut, + version: self.version, + parameters: self.parameters, + command_line: self.command_line, + metadata: self.metadata, + state: Arc::new(Mutex::new(state)), + } + } +} + +/// A single test step in the scope of a [`TestRun`]. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +pub struct TestStep { + name: String, + state: Arc>, + measurement_id_no: Arc, +} + +impl TestStep { + fn new(name: &str, state: Arc>) -> TestStep { + TestStep { + name: name.to_string(), + state, + measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), + } + } + + /// Starts the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + let start = objects::TestStepStart::new(&self.name); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend + /// + /// # Examples + // ``` + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { + let end = objects::TestStepEnd::new(status); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`TestStep`] object, taking care of starting and + /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`TestStep`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("first step")?; + /// step.scope(|s: &TestStep| -> Result { + /// s.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// )?; + /// Ok(TestStatus::Complete) + /// })?; + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a TestStep) -> R, + { + self.start().await?; + let status = func(self).await?; + self.end(status).await?; + Ok(()) + } + + /// Eemits Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// )?; + /// step.end(TestStatus::Complete)?; + /// ``` + /// ## Using macros + /// + /// ``` + /// use ocptv_formatter::ocptv_log_info; + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// ocptv_log_info!(step, "This is a log message with INFO severity")?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + self.state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Log; + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// )?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.error("symptom")?; + /// step.end(TestStatus::Complete)?; + /// ``` + /// + /// ## Using macros + /// + /// ``` + /// use ocptv_formatter::ocptv_log_info; + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// ocptv_error!(step, "symptom")?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom and + /// another [`std::string::String`] as error message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.error("symptom", "error message")?; + /// step.end(TestStatus::Complete)?; + /// ``` + /// + /// ## Using macros + /// + /// ``` + /// use ocptv_formatter::ocptv_log_info; + /// use ocptv_formatter::LogSeverity; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// ocptv_error!(step, "symptom", "error message")?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Error; + /// use ocptv_formatter::SoftwareInfo; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// )?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// step.add_measurement("name", Value::from(50))?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn add_measurement( + &self, + name: &str, + value: Value, + ) -> Result<(), emitters::WriterError> { + let measurement = objects::Measurement::new(name, value); + self.state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::Measurement; + /// use ocptv_formatter::SoftwareInfo; + /// use ocptv_formatter::Subcomponent; + /// use ocptv_formatter::TestResult; + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::TestStatus; + /// use ocptv_formatter::Validator; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let measurement = Measurement::builder("name", Value::from(50)) + /// .hardware_info_id("id") + /// .add_validator(&Validator::builder(models::ValidatorType::Equal, Value::from(30)).build()) + /// .add_metadata("key", Value::from("value")) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// step.add_measurement_with_details(&measurement)?; + /// step.end(TestStatus::Complete)?; + /// ``` + pub async fn add_measurement_with_details( + &self, + measurement: &objects::Measurement, + ) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Starts a Measurement Series (a time-series list of measurements). + /// This method accepts a [`std::string::String`] as series ID and + /// a [`std::string::String`] as series name. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// ``` + pub fn measurement_series(&self, name: &str) -> MeasurementSeries { + self.measurement_id_no + .fetch_add(1, atomic::Ordering::SeqCst); + let series_id: String = format!( + "series_{}", + self.measurement_id_no.load(atomic::Ordering::SeqCst) + ); + + MeasurementSeries::new(&series_id, name, self.state.clone()) + } + + /// Starts a Measurement Series (a time-series list of measurements). + /// This method accepts a [`objects::MeasurementSeriesStart`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::MeasurementSeriesStart; + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = + /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// ``` + pub fn measurement_series_with_details( + &self, + start: objects::MeasurementSeriesStart, + ) -> MeasurementSeries { + MeasurementSeries::new_with_details(start, self.state.clone()) + } +} + +/// The measurement series. +/// A Measurement Series is a time-series list of measurements. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +pub struct MeasurementSeries { + state: Arc>, + seq_no: Arc>, + start: objects::MeasurementSeriesStart, +} + +impl MeasurementSeries { + fn new(series_id: &str, name: &str, state: Arc>) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start: objects::MeasurementSeriesStart::new(name, series_id), + } + } + + fn new_with_details( + start: objects::MeasurementSeriesStart, + state: Arc>, + ) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start, + } + } + + async fn current_sequence_no(&self) -> u64 { + self.seq_no.lock().await.load(atomic::Ordering::SeqCst) + } + + async fn increment_sequence_no(&self) { + self.seq_no + .lock() + .await + .fetch_add(1, atomic::Ordering::SeqCst); + } + + /// Starts the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// series.start()?; + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&self.start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// series.start()?; + /// series.end()?; + /// ``` + pub async fn end(&self) -> Result<(), emitters::WriterError> { + let end = objects::MeasurementSeriesEnd::new( + self.start.get_series_id(), + self.current_sequence_no().await, + ); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::Value; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// series.start()?; + /// series.add_measurement(Value::from(60))?; + /// ``` + pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + None, + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// This method accepts additional metadata to add to the element. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::Value; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// series.start()?; + /// series.add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))])?; + /// ``` + pub async fn add_measurement_with_metadata( + &self, + value: Value, + metadata: Vec<(&str, Value)>, + ) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + Some(Map::from_iter( + metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), + )), + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ``` + /// use ocptv_formatter::TestRun; + /// use ocptv_formatter::Value; + /// + /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// test_run.start().await?; + /// let step = test_run.step("step_name")?; + /// step.start()?; + /// let series = step.measurement_series("series_id", "name"); + /// series.start()?; + /// series.scope(|s| { + /// s.add_measurement(Value::from(60))?; + /// s.add_measurement(Value::from(70))?; + /// s.add_measurement(Value::from(80))?; + /// Ok(()) + /// })?; + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + { + self.start().await?; + func(self).await?; + self.end().await?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + + use std::sync::Arc; + + use anyhow::Result; + use assert_json_diff::assert_json_include; + use serde_json::json; + use tokio::sync::Mutex; + + use super::*; + use crate::models::*; + use crate::objects::*; + + #[tokio::test] + async fn test_testrun_start_and_end() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_log() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await + .unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_log_with_details() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":{ + "file": "file", + "line": 1 + }}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await + .unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_error() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":null,"softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run.error("symptom").await.unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_error_with_message() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .error_with_msg("symptom", "Error message") + .await + .unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_error_with_details() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":[ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "id", + "softwareType": null, + "version": null + } + ],"sourceLocation":{ + "file": "file", + "line": 1 + },"symptom":"symptom"}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await + .unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_scope() { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"First message","severity":"INFO","sourceLocation":null}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let dut = DutInfo::builder("dut_id").build(); + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run + .scope(|r| async { + r.log(LogSeverity::Info, "First message").await?; + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + }) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + } + + #[tokio::test] + async fn test_testrun_with_step() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":5,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_log() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_log_with_details() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":{"file": "file", "line": 1}}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_error() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":null,"softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.error("symptom").await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_error_with_message() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.error_with_msg("symptom", "Error message").await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_error_with_details() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":[{"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "id", "softwareType": null, "version": null}],"sourceLocation":{"file": "file", "line": 1},"symptom":"symptom"}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_step_scope_log() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.scope(|s: &TestStep| async { + s.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + Ok(TestStatus::Complete) + }) + .await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurement":{"hardwareInfoId":null,"metadata":null,"name":"name","subcomponent":null,"unit":null,"validators":null,"value":50}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + step.add_measurement("name", Value::from(50)).await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_builder() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurement":{"hardwareInfoId":"id","metadata":{"key":"value"},"name":"name","subcomponent":{"location":null,"name":"name","revision":null,"type":null,"version":null},"unit":null,"validators":[{"metadata":null,"name":null,"type":"EQUAL","value":30}],"value":50}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let measurement = Measurement::builder("name", Value::from(50)) + .hardware_info(&objects::HardwareInfo::builder("id", "name").build()) + .add_validator( + &objects::Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(), + ) + .add_metadata("key", Value::from("value")) + .subcomponent(&objects::Subcomponent::builder("name").build()) + .build(); + step.add_measurement_with_details(&measurement).await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":0}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_multiple_measurement_series() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":0}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_2","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_2","totalCount":0}}}), + json!({"sequenceNumber":8,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":9,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; + let series_2 = step.measurement_series("name"); + series_2.start().await?; + series_2.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_with_details() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_id","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_id","totalCount":0}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = + step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + series.start().await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_with_details_and_start_builder() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":{"computerSystem":null,"hardwareInfoId":"id","location":null,"manager":null,"manufacturer":null,"manufacturerPartNumber":null,"name":"name","odataId":null,"partNumber":null,"revision":null,"serialNumber":null,"version":null},"measurementSeriesId":"series_id","metadata":{"key":"value"},"name":"name","subComponent":{"location":null,"name":"name","revision":null,"type":null,"version":null},"unit":null,"validators":[{"metadata":null,"name":null,"type":"EQUAL","value":30}]}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_id","totalCount":0}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series_with_details( + MeasurementSeriesStart::builder("name", "series_id") + .add_metadata("key", Value::from("value")) + .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .subcomponent(&Subcomponent::builder("name").build()) + .build(), + ); + series.start().await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_element() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":1}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + series.add_measurement(Value::from(60)).await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_element_index_no() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":null,"value":70}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":null,"value":80}}}), + json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), + json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series.add_measurement(Value::from(60)).await?; + series.add_measurement(Value::from(70)).await?; + series.add_measurement(Value::from(80)).await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":{"key": "value"},"value":60}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":1}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + series + .add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]) + .await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":{"key": "value"},"value":60}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":{"key2": "value2"},"value":70}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":{"key3": "value3"},"value":80}}}), + json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), + json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series + .add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]) + .await?; + series + .add_measurement_with_metadata(Value::from(70), vec![("key2", Value::from("value2"))]) + .await?; + series + .add_measurement_with_metadata(Value::from(80), vec![("key3", Value::from("value3"))]) + .await?; + series.end().await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_step_with_measurement_series_scope() -> Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), + json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), + json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), + json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":null,"value":70}}}), + json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":null,"value":80}}}), + json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), + json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), + json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .build(); + test_run.start().await?; + + let step = test_run.step("first step")?; + step.start().await?; + let series = step.measurement_series("name"); + series + .scope(|s| async { + s.add_measurement(Value::from(60)).await?; + s.add_measurement(Value::from(70)).await?; + s.add_measurement(Value::from(80)).await?; + Ok(()) + }) + .await?; + step.end(TestStatus::Complete).await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_config_builder() -> anyhow::Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), + json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .timezone(chrono_tz::Europe::Rome) + .with_file_output(std::env::temp_dir().join("file.txt")) + .await? + .build(), + ) + .build(); + test_run.start().await.unwrap(); + test_run + .error_with_msg("symptom", "Error message") + .await + .unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_instantiation_with_new() { + let test_run = TestRun::new("run_name", "dut_id", "1.0"); + test_run.start().await.unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + assert_eq!(test_run.dut.to_spec().id, "dut_id"); + } + + #[tokio::test] + async fn test_testrun_metadata() -> anyhow::Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value"},"name":"run_name","parameters":{},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .add_metadata("key", Value::from("value")) + .build(); + test_run.start().await.unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } + + #[tokio::test] + async fn test_testrun_builder() -> anyhow::Result<()> { + let expected = [ + json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), + json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"commandLine":"cmd_line", "dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value", "key2": "value2"},"name":"run_name","parameters":{"key": "value"},"version":"1.0"}}}), + json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + ]; + + let dut = DutInfo::builder("dut_id").build(); + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ) + .add_metadata("key", Value::from("value")) + .add_metadata("key2", Value::from("value2")) + .add_parameter("key", Value::from("value")) + .command_line("cmd_line") + .build(); + test_run.start().await.unwrap(); + test_run + .end(TestStatus::Complete, TestResult::Pass) + .await + .unwrap(); + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) + } +} From c44383d1b9a2aec002ee91c2ad43bd4d27ed3967 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 01:23:13 +0100 Subject: [PATCH 02/96] move current codebase to module `output` - the ocptv lib will contain other modules in the future (eg. parsing ocptv spec), so move the current codebase to submodule `output` - tests still don't work, initial export has issues to fix Signed-off-by: mimir-d --- src/lib.rs | 14 +-- src/{ => output}/emitters.rs | 4 +- src/{ => output}/macros.rs | 66 +++++------ src/output/mod.rs | 20 ++++ src/{ => output}/models.rs | 0 src/{ => output}/objects.rs | 96 ++++++++-------- src/{ => output}/runner.rs | 206 +++++++++++++++++------------------ 7 files changed, 207 insertions(+), 199 deletions(-) rename src/{ => output}/emitters.rs (98%) rename src/{ => output}/macros.rs (94%) create mode 100644 src/output/mod.rs rename src/{ => output}/models.rs (100%) rename src/{ => output}/objects.rs (96%) rename src/{ => output}/runner.rs (95%) diff --git a/src/lib.rs b/src/lib.rs index 9e3999e..e01580c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,16 +4,4 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -mod emitters; -mod macros; -mod models; -mod objects; -mod runner; - -pub use emitters::*; -pub use models::LogSeverity; -pub use models::TestResult; -pub use models::TestStatus; -pub use objects::*; -pub use runner::*; -pub use serde_json::Value; +pub mod output; diff --git a/src/emitters.rs b/src/output/emitters.rs similarity index 98% rename from src/emitters.rs rename to src/output/emitters.rs index 0a68d50..34c0e30 100644 --- a/src/emitters.rs +++ b/src/output/emitters.rs @@ -16,7 +16,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; -use crate::models; +use crate::output::models; #[derive(Debug, thiserror::Error, derive_more::Display)] #[non_exhaustive] @@ -133,7 +133,7 @@ mod tests { use serde_json::json; use super::*; - use crate::objects::*; + use crate::output::objects::*; #[tokio::test] async fn test_emit_using_buffer_writer() -> Result<()> { diff --git a/src/macros.rs b/src/output/macros.rs similarity index 94% rename from src/macros.rs rename to src/output/macros.rs index 7b937f9..0d24e0c 100644 --- a/src/macros.rs +++ b/src/output/macros.rs @@ -25,11 +25,11 @@ /// use std::io::Write; /// use std::io::{self}; /// -/// use ocptv_formatter::ocptv_error; -/// use ocptv_formatter::Error; -/// use ocptv_formatter::TestResult; -/// use ocptv_formatter::TestRun; -/// use ocptv_formatter::TestStatus; +/// use ocptv::ocptv_error; +/// use ocptv::output::Error; +/// use ocptv::output::TestResult; +/// use ocptv::output::TestRun; +/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); /// test_run.start().unwrap(); @@ -45,11 +45,11 @@ /// use std::io::Write; /// use std::io::{self}; /// -/// use ocptv_formatter::ocptv_error; -/// use ocptv_formatter::Error; -/// use ocptv_formatter::TestResult; -/// use ocptv_formatter::TestRun; -/// use ocptv_formatter::TestStatus; +/// use ocptv::ocptv_error; +/// use ocptv::output::Error; +/// use ocptv::output::TestResult; +/// use ocptv::output::TestRun; +/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); /// test_run.start().unwrap(); @@ -64,7 +64,7 @@ macro_rules! ocptv_error { async { $runner .error_with_details( - &$crate::Error::builder($symptom) + &$crate::output::Error::builder($symptom) .message($msg) .source(file!(), line!() as i32) .build(), @@ -76,7 +76,7 @@ macro_rules! ocptv_error { async { $runner .error_with_details( - &$crate::Error::builder($symptom) + &$crate::output::Error::builder($symptom) .source(file!(), line!() as i32) .build(), ) @@ -103,15 +103,15 @@ macro_rules! ocptv_error { /// use std::io::Write; /// use std::io::{self}; /// -/// use ocptv_formatter::ocptv_log_debug; -/// use ocptv_formatter::ocptv_log_error; -/// use ocptv_formatter::ocptv_log_fatal; -/// use ocptv_formatter::ocptv_log_info; -/// use ocptv_formatter::ocptv_log_warning; -/// use ocptv_formatter::LogSeverity; -/// use ocptv_formatter::TestResult; -/// use ocptv_formatter::TestRun; -/// use ocptv_formatter::TestStatus; +/// use ocptv::ocptv_log_debug; +/// use ocptv::ocptv_log_error; +/// use ocptv::ocptv_log_fatal; +/// use ocptv::ocptv_log_info; +/// use ocptv::ocptv_log_warning; +/// use ocptv::output::LogSeverity; +/// use ocptv::output::TestResult; +/// use ocptv::output::TestRun; +/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); /// test_run.start().unwrap(); @@ -127,8 +127,8 @@ macro_rules! ocptv_log_debug { async { $runner .log_with_details( - &$crate::Log::builder($msg) - .severity($crate::LogSeverity::Debug) + &$crate::output::Log::builder($msg) + .severity($crate::output::LogSeverity::Debug) .source(file!(), line!() as i32) .build(), ) @@ -143,8 +143,8 @@ macro_rules! ocptv_log_info { async { $runner .log_with_details( - &$crate::Log::builder($msg) - .severity($crate::LogSeverity::Info) + &$crate::output::Log::builder($msg) + .severity($crate::output::LogSeverity::Info) .source(file!(), line!() as i32) .build(), ) @@ -159,8 +159,8 @@ macro_rules! ocptv_log_warning { async { $runner .log_with_details( - &$crate::Log::builder($msg) - .severity($crate::LogSeverity::Warning) + &$crate::output::Log::builder($msg) + .severity($crate::output::LogSeverity::Warning) .source(file!(), line!() as i32) .build(), ) @@ -175,8 +175,8 @@ macro_rules! ocptv_log_error { async { $runner .log_with_details( - &$crate::Log::builder($msg) - .severity($crate::LogSeverity::Error) + &$crate::output::Log::builder($msg) + .severity($crate::output::LogSeverity::Error) .source(file!(), line!() as i32) .build(), ) @@ -191,8 +191,8 @@ macro_rules! ocptv_log_fatal { async { $runner .log_with_details( - &$crate::Log::builder($msg) - .severity($crate::LogSeverity::Fatal) + &$crate::output::Log::builder($msg) + .severity($crate::output::LogSeverity::Fatal) .source(file!(), line!() as i32) .build(), ) @@ -211,8 +211,8 @@ mod tests { use serde_json::json; use tokio::sync::Mutex; - use crate::objects::*; - use crate::runner::*; + use crate::output::objects::*; + use crate::output::runner::*; #[tokio::test] async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { diff --git a/src/output/mod.rs b/src/output/mod.rs new file mode 100644 index 0000000..8c9c5ed --- /dev/null +++ b/src/output/mod.rs @@ -0,0 +1,20 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +mod emitters; +mod macros; +mod models; +mod objects; +mod runner; + +pub use emitters::*; +pub use models::LogSeverity; +pub use models::TestResult; +pub use models::TestStatus; +pub use models::ValidatorType; +pub use objects::*; +pub use runner::*; +pub use serde_json::Value; diff --git a/src/models.rs b/src/output/models.rs similarity index 100% rename from src/models.rs rename to src/output/models.rs diff --git a/src/objects.rs b/src/output/objects.rs similarity index 96% rename from src/objects.rs rename to src/output/objects.rs index 992ed11..e4fea77 100644 --- a/src/objects.rs +++ b/src/output/objects.rs @@ -8,7 +8,7 @@ use chrono::DateTime; use serde_json::Map; use serde_json::Value; -use crate::models; +use crate::output::models; pub enum ArtifactContext { TestRun, @@ -886,8 +886,8 @@ impl SoftwareInfoBuilder { /// ## Create a Measurement object with the `new` method /// /// ``` -/// use ocptv_formatter::Measurement; -/// use ocptv_formatter::Value; +/// use ocptv::output::Measurement; +/// use ocptv::output::Value; /// /// let measurement = Measurement::new("name", Value::from(50)); /// ``` @@ -895,12 +895,12 @@ impl SoftwareInfoBuilder { /// ## Create a Measurement object with the `builder` method /// /// ``` -/// use ocptv_formatter::HardwareInfo; -/// use ocptv_formatter::Measurement; -/// use ocptv_formatter::Subcomponent; -/// use ocptv_formatter::Validator; -/// use ocptv_formatter::ValidatorType; -/// use ocptv_formatter::Value; +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; /// /// let measurement = Measurement::builder("name", Value::from(50)) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -925,8 +925,8 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv_formatter::Measurement; - /// use ocptv_formatter::Value; + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; /// /// let measurement = Measurement::new("name", Value::from(50)); /// ``` @@ -947,12 +947,12 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv_formatter::HardwareInfo; - /// use ocptv_formatter::Measurement; - /// use ocptv_formatter::Subcomponent; - /// use ocptv_formatter::Validator; - /// use ocptv_formatter::ValidatorType; - /// use ocptv_formatter::Value; + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::Measurement; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; /// /// let measurement = Measurement::builder("name", Value::from(50)) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -970,8 +970,8 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv_formatter::Measurement; - /// use ocptv_formatter::Value; + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; /// /// let measurement = Measurement::new("name", Value::from(50)); /// let _ = measurement.to_artifact(); @@ -1005,13 +1005,13 @@ impl Measurement { /// # Examples /// /// ``` -/// use ocptv_formatter::HardwareInfo; -/// use ocptv_formatter::Measurement; -/// use ocptv_formatter::MeasurementBuilder; -/// use ocptv_formatter::Subcomponent; -/// use ocptv_formatter::Validator; -/// use ocptv_formatter::ValidatorType; -/// use ocptv_formatter::Value; +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::MeasurementBuilder; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -1036,8 +1036,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Value; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)); /// ``` @@ -1058,12 +1058,12 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::HardwareInfo; - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Subcomponent; - /// use ocptv_formatter::Validator; - /// use ocptv_formatter::ValidatorType; - /// use ocptv_formatter::Value; + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)) /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()); @@ -1084,9 +1084,9 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::HardwareInfo; - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Value; + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)) /// .hardware_info(&HardwareInfo::builder("id", "name").build()); @@ -1101,9 +1101,9 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Subcomponent; - /// use ocptv_formatter::Value; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)) /// .subcomponent(&Subcomponent::builder("name").build()); @@ -1118,8 +1118,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Value; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; /// /// let builder = /// MeasurementBuilder::new("name", Value::from(50)).add_metadata("key", Value::from("value")); @@ -1145,8 +1145,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Value; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50000)).unit("RPM"); /// ``` @@ -1160,8 +1160,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementBuilder; - /// use ocptv_formatter::Value; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; /// /// let builder = MeasurementBuilder::new("name", Value::from(50)); /// let measurement = builder.build(); @@ -1383,8 +1383,8 @@ mod tests { use serde_json::Value; use super::*; - use crate::models; - use crate::models::ValidatorType; + use crate::output::models; + use crate::output::models::ValidatorType; #[test] fn test_schema_creation_from_builder() { diff --git a/src/runner.rs b/src/output/runner.rs similarity index 95% rename from src/runner.rs rename to src/output/runner.rs index 729be67..6856063 100644 --- a/src/runner.rs +++ b/src/output/runner.rs @@ -19,9 +19,9 @@ use serde_json::Map; use serde_json::Value; use tokio::sync::Mutex; -use crate::emitters; -use crate::models; -use crate::objects; +use crate::output::emitters; +use crate::output::models; +use crate::output::objects; /// The configuration repository for the TestRun. pub struct Config { @@ -34,7 +34,7 @@ impl Config { /// /// # Examples /// ``` - /// use ocptv_formatter::Config; + /// use ocptv::output::Config; /// /// let builder = Config::builder(); /// ``` @@ -129,8 +129,8 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::DutInfo; - /// use ocptv_formatter::TestRun; + /// use ocptv::output::DutInfo; + /// use ocptv::output::TestRun; /// /// let dut = DutInfo::builder("my_dut").build(); /// let builder = TestRun::builder("run_name", &dut, "1.0"); @@ -144,7 +144,7 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// ``` @@ -161,7 +161,7 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -204,9 +204,9 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -240,11 +240,11 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestRunOutcome; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestRunOutcome; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.scope(|r| { @@ -275,10 +275,10 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -311,11 +311,11 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::Log; - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::Log; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -345,9 +345,9 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -374,9 +374,9 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -406,10 +406,10 @@ impl TestRun { /// # Examples /// /// ``` - /// use ocptv_formatter::Error; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::Error; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -469,8 +469,8 @@ impl TestRunBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::DutInfo; - /// use ocptv_formatter::TestRunBuilder; + /// use ocptv::output::DutInfo; + /// use ocptv::output::TestRunBuilder; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -488,8 +488,8 @@ impl TestRunBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::DutInfo; - /// use ocptv_formatter::TestRunBuilder; + /// use ocptv::output::DutInfo; + /// use ocptv::output::TestRunBuilder; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -506,9 +506,9 @@ impl TestRunBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::Config; - /// use ocptv_formatter::DutInfo; - /// use ocptv_formatter::TestRunBuilder; + /// use ocptv::output::Config; + /// use ocptv::output::DutInfo; + /// use ocptv::output::TestRunBuilder; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -525,8 +525,8 @@ impl TestRunBuilder { /// # Examples /// /// ``` - /// use ocptv_formatter::DutInfo; - /// use ocptv_formatter::TestRunBuilder; + /// use ocptv::output::DutInfo; + /// use ocptv::output::TestRunBuilder; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -589,7 +589,7 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -613,8 +613,8 @@ impl TestStep { /// /// # Examples // ``` - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -643,9 +643,9 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -678,9 +678,9 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -695,10 +695,10 @@ impl TestStep { /// ## Using macros /// /// ``` - /// use ocptv_formatter::ocptv_log_info; - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::ocptv_log_info; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -730,10 +730,10 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::Log; - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::Log; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -765,9 +765,9 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -779,10 +779,10 @@ impl TestStep { /// ## Using macros /// /// ``` - /// use ocptv_formatter::ocptv_log_info; - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::ocptv_log_info; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -811,9 +811,9 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -825,10 +825,10 @@ impl TestStep { /// ## Using macros /// /// ``` - /// use ocptv_formatter::ocptv_log_info; - /// use ocptv_formatter::LogSeverity; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::ocptv_log_info; + /// use ocptv::output::LogSeverity; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -860,11 +860,11 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::Error; - /// use ocptv_formatter::SoftwareInfo; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::Error; + /// use ocptv::output::SoftwareInfo; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -898,9 +898,9 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -931,13 +931,13 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::Measurement; - /// use ocptv_formatter::SoftwareInfo; - /// use ocptv_formatter::Subcomponent; - /// use ocptv_formatter::TestResult; - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::TestStatus; - /// use ocptv_formatter::Validator; + /// use ocptv::output::Measurement; + /// use ocptv::output::SoftwareInfo; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::TestResult; + /// use ocptv::output::TestRun; + /// use ocptv::output::TestStatus; + /// use ocptv::output::Validator; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -973,7 +973,7 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -999,8 +999,8 @@ impl TestStep { /// # Examples /// /// ``` - /// use ocptv_formatter::MeasurementSeriesStart; - /// use ocptv_formatter::TestRun; + /// use ocptv::output::MeasurementSeriesStart; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; @@ -1064,7 +1064,7 @@ impl MeasurementSeries { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -1090,7 +1090,7 @@ impl MeasurementSeries { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; + /// use ocptv::output::TestRun; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -1121,8 +1121,8 @@ impl MeasurementSeries { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::Value; + /// use ocptv::output::TestRun; + /// use ocptv::output::Value; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -1157,8 +1157,8 @@ impl MeasurementSeries { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::Value; + /// use ocptv::output::TestRun; + /// use ocptv::output::Value; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -1201,8 +1201,8 @@ impl MeasurementSeries { /// # Examples /// /// ``` - /// use ocptv_formatter::TestRun; - /// use ocptv_formatter::Value; + /// use ocptv::output::TestRun; + /// use ocptv::output::Value; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -1240,8 +1240,8 @@ mod tests { use tokio::sync::Mutex; use super::*; - use crate::models::*; - use crate::objects::*; + use crate::output::models::*; + use crate::output::objects::*; #[tokio::test] async fn test_testrun_start_and_end() { From bbb3bfb76cef1df61debfaee5fd1b74e5965f6fa Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 02:48:49 +0100 Subject: [PATCH 03/96] fix doctests - invalid uses, missing proper syntax, etc Signed-off-by: mimir-d --- Cargo.lock | 53 +++++ Cargo.toml | 1 + src/output/macros.rs | 62 ++---- src/output/runner.rs | 514 +++++++++++++++++++++++++------------------ 4 files changed, 372 insertions(+), 258 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 26a2dd6..d03b988 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,28 @@ dependencies = [ "serde_json", ] +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -177,6 +199,12 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + [[package]] name = "gimli" version = "0.31.0" @@ -279,6 +307,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "tokio-test", ] [[package]] @@ -521,6 +550,30 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-test" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" +dependencies = [ + "async-stream", + "bytes", + "futures-core", + "tokio", + "tokio-stream", +] + [[package]] name = "unicode-ident" version = "1.0.13" diff --git a/Cargo.toml b/Cargo.toml index 45ef96f..c69a37e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,3 +27,4 @@ tokio = { version = "1.40.0", features = [ [dev-dependencies] anyhow = "1.0.89" assert-json-diff = "2.0.2" +tokio-test = "0.4.4" diff --git a/src/output/macros.rs b/src/output/macros.rs index 0d24e0c..4386327 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -21,42 +21,36 @@ /// /// ## Passing only symptom /// -/// ``` -/// use std::io::Write; -/// use std::io::{self}; +/// ```rust +/// # tokio_test::block_on(async { +/// # use ocptv::output::*; /// /// use ocptv::ocptv_error; -/// use ocptv::output::Error; -/// use ocptv::output::TestResult; -/// use ocptv::output::TestRun; -/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().unwrap(); +/// test_run.start().await?; /// ocptv_error!(test_run, "symptom"); -/// test_run -/// .end(TestStatus::Complete, TestResult::Pass) -/// .unwrap(); +/// test_run.end(TestStatus::Complete, TestResult::Pass).await?; +/// +/// # Ok::<(), WriterError>(()) +/// # }); /// ``` /// /// ## Passing both symptom and message /// -/// ``` -/// use std::io::Write; -/// use std::io::{self}; +/// ```rust +/// # tokio_test::block_on(async { +/// # use ocptv::output::*; /// /// use ocptv::ocptv_error; -/// use ocptv::output::Error; -/// use ocptv::output::TestResult; -/// use ocptv::output::TestRun; -/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().unwrap(); +/// test_run.start().await?; /// ocptv_error!(test_run, "symptom", "Error message"); -/// test_run -/// .end(TestStatus::Complete, TestResult::Pass) -/// .unwrap(); +/// test_run.end(TestStatus::Complete, TestResult::Pass).await?; +/// +/// # Ok::<(), WriterError>(()) +/// # }); /// ``` #[macro_export] macro_rules! ocptv_error { @@ -99,26 +93,19 @@ macro_rules! ocptv_error { /// /// ## DEBUG /// -/// ``` -/// use std::io::Write; -/// use std::io::{self}; +/// ```rust +/// # tokio_test::block_on(async { +/// # use ocptv::output::*; /// /// use ocptv::ocptv_log_debug; -/// use ocptv::ocptv_log_error; -/// use ocptv::ocptv_log_fatal; -/// use ocptv::ocptv_log_info; -/// use ocptv::ocptv_log_warning; -/// use ocptv::output::LogSeverity; -/// use ocptv::output::TestResult; -/// use ocptv::output::TestRun; -/// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().unwrap(); +/// test_run.start().await?; /// ocptv_log_debug!(test_run, "Log message"); -/// test_run -/// .end(TestStatus::Complete, TestResult::Pass) -/// .unwrap(); +/// test_run.end(TestStatus::Complete, TestResult::Pass).await?; +/// +/// # Ok::<(), WriterError>(()) +/// # }); /// ``` #[macro_export] @@ -203,7 +190,6 @@ macro_rules! ocptv_log_fatal { #[cfg(test)] mod tests { - use std::sync::Arc; use anyhow::Result; diff --git a/src/output/runner.rs b/src/output/runner.rs index 6856063..8958f87 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -33,8 +33,8 @@ impl Config { /// Creates a new [`ConfigBuilder`] /// /// # Examples - /// ``` - /// use ocptv::output::Config; + /// ```rust + /// # use ocptv::output::*; /// /// let builder = Config::builder(); /// ``` @@ -128,9 +128,8 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::DutInfo; - /// use ocptv::output::TestRun; + /// ```rust + /// # use ocptv::output::*; /// /// let dut = DutInfo::builder("my_dut").build(); /// let builder = TestRun::builder("run_name", &dut, "1.0"); @@ -143,8 +142,8 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// ``` @@ -160,11 +159,15 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn start(&self) -> Result<(), emitters::WriterError> { let version = objects::SchemaVersion::new(); @@ -203,14 +206,16 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn end( &self, @@ -239,21 +244,21 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestRunOutcome; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.scope(|r| { - /// r.log(LogSeverity::Info, "First message")?; - /// TestRunOutcome { + /// test_run.scope(|r| async { + /// r.log(LogSeverity::Info, "First message").await?; + /// Ok(TestRunOutcome { /// status: TestStatus::Complete, /// result: TestResult::Pass, - /// } - /// })?; + /// }) + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> where @@ -274,19 +279,20 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; /// test_run.log( /// LogSeverity::Info, /// "This is a log message with INFO severity", - /// )?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ).await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn log( &self, @@ -310,12 +316,9 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::Log; - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -324,8 +327,11 @@ impl TestRun { /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), - /// )?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ).await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { self.state @@ -344,15 +350,17 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; - /// test_run.error("symptom")?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// test_run.error("symptom").await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).build(); @@ -373,15 +381,17 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; - /// test_run.error("symptom", "error messasge")?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// test_run.error_with_msg("symptom", "error messasge").await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error_with_msg( &self, @@ -405,11 +415,9 @@ impl TestRun { /// /// # Examples /// - /// ``` - /// use ocptv::output::Error; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; @@ -419,8 +427,11 @@ impl TestRun { /// .source("file", 1) /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) /// .build(), - /// )?; - /// test_run.end(TestStatus::Complete, TestResult::Pass)?; + /// ).await?; + /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error_with_details( &self, @@ -452,7 +463,7 @@ pub struct TestRunBuilder { } impl TestRunBuilder { - fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { Self { name: name.to_string(), dut: dut.clone(), @@ -468,13 +479,12 @@ impl TestRunBuilder { /// /// # Examples /// - /// ``` - /// use ocptv::output::DutInfo; - /// use ocptv::output::TestRunBuilder; + /// ```rust + /// # use ocptv::output::*; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_parameter("param1", "value1") + /// .add_parameter("param1", "value1".into()) /// .build(); /// ``` pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { @@ -487,9 +497,8 @@ impl TestRunBuilder { /// /// # Examples /// - /// ``` - /// use ocptv::output::DutInfo; - /// use ocptv::output::TestRunBuilder; + /// ```rust + /// # use ocptv::output::*; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -505,10 +514,8 @@ impl TestRunBuilder { /// /// # Examples /// - /// ``` - /// use ocptv::output::Config; - /// use ocptv::output::DutInfo; - /// use ocptv::output::TestRunBuilder; + /// ```rust + /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") @@ -524,13 +531,12 @@ impl TestRunBuilder { /// /// # Examples /// - /// ``` - /// use ocptv::output::DutInfo; - /// use ocptv::output::TestRunBuilder; + /// ```rust + /// # use ocptv::output::*; /// /// let dut = DutInfo::builder("dut_id").build(); /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_metadata("meta1", "value1") + /// .add_metadata("meta1", "value1".into()) /// .build(); /// ``` pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { @@ -588,13 +594,18 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; + /// step.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn start(&self) -> Result<(), emitters::WriterError> { let start = objects::TestStepStart::new(&self.name); @@ -612,15 +623,20 @@ impl TestStep { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend /// /// # Examples - // ``` - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { let end = objects::TestStepEnd::new(status); @@ -642,21 +658,24 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("first step")?; - /// step.scope(|s: &TestStep| -> Result { + /// step.scope(|s| async { /// s.log( /// LogSeverity::Info, /// "This is a log message with INFO severity", - /// )?; + /// ).await?; /// Ok(TestStatus::Complete) - /// })?; + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> where @@ -677,35 +696,42 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; + /// step.start().await?; /// step.log( /// LogSeverity::Info, /// "This is a log message with INFO severity", - /// )?; - /// step.end(TestStatus::Complete)?; + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` /// ## Using macros /// - /// ``` + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// /// use ocptv::ocptv_log_info; - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// ocptv_log_info!(step, "This is a log message with INFO severity")?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn log( &self, @@ -729,23 +755,25 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::Log; - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; + /// step.start().await?; /// step.log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), - /// )?; - /// step.end(TestStatus::Complete)?; + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { self.state @@ -757,39 +785,45 @@ impl TestStep { Ok(()) } - /// Emits a Error message. + /// Emits an Error symptom. /// This method accepts a [`std::string::String`] to define the symptom. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; - /// step.error("symptom")?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// step.error("symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` /// /// ## Using macros /// - /// ``` - /// use ocptv::ocptv_log_info; - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// ocptv_error!(step, "symptom")?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// ocptv_error!(step, "symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).build(); @@ -802,7 +836,7 @@ impl TestStep { Ok(()) } - /// Emits a Error message. + /// Emits an Error message. /// This method accepts a [`std::string::String`] to define the symptom and /// another [`std::string::String`] as error message. /// @@ -810,32 +844,37 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; - /// step.error("symptom", "error message")?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// step.error_with_msg("symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` /// /// ## Using macros /// - /// ``` - /// use ocptv::ocptv_log_info; - /// use ocptv::output::LogSeverity; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; /// let step = test_run.step("step_name")?; - /// step.start()?; - /// ocptv_error!(step, "symptom", "error message")?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// ocptv_error!(step, "symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error_with_msg( &self, @@ -859,24 +898,24 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::Error; - /// use ocptv::output::SoftwareInfo; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; + /// step.start().await?; /// step.error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) /// .build(), - /// )?; - /// step.end(TestStatus::Complete)?; + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn error_with_details( &self, @@ -897,16 +936,18 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; - /// step.add_measurement("name", Value::from(50))?; - /// step.end(TestStatus::Complete)?; + /// step.start().await?; + /// step.add_measurement("name", 50.into()).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn add_measurement( &self, @@ -930,26 +971,26 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::SoftwareInfo; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::TestResult; - /// use ocptv::output::TestRun; - /// use ocptv::output::TestStatus; - /// use ocptv::output::Validator; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// + /// let hwinfo = HardwareInfo::builder("id", "fan").build(); /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let measurement = Measurement::builder("name", Value::from(50)) - /// .hardware_info_id("id") - /// .add_validator(&Validator::builder(models::ValidatorType::Equal, Value::from(30)).build()) - /// .add_metadata("key", Value::from("value")) + /// step.start().await?; + /// + /// let measurement = Measurement::builder("name", 5000.into()) + /// .hardware_info(&hwinfo) + /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) + /// .add_metadata("key", "value".into()) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); - /// step.add_measurement_with_details(&measurement)?; - /// step.end(TestStatus::Complete)?; + /// step.add_measurement_with_details(&measurement).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn add_measurement_with_details( &self, @@ -972,13 +1013,17 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); + /// step.start().await?; + /// let series = step.measurement_series("name"); + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub fn measurement_series(&self, name: &str) -> MeasurementSeries { self.measurement_id_no @@ -998,15 +1043,18 @@ impl TestStep { /// /// # Examples /// - /// ``` - /// use ocptv::output::MeasurementSeriesStart; - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// let step = test_run.step("step_name")?; - /// step.start()?; + /// step.start().await?; /// let series = /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub fn measurement_series_with_details( &self, @@ -1063,15 +1111,21 @@ impl MeasurementSeries { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); - /// series.start()?; + /// step.start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn start(&self) -> Result<(), emitters::WriterError> { self.state @@ -1089,16 +1143,22 @@ impl MeasurementSeries { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); - /// series.start()?; - /// series.end()?; + /// step.start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.end().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn end(&self) -> Result<(), emitters::WriterError> { let end = objects::MeasurementSeriesEnd::new( @@ -1120,17 +1180,22 @@ impl MeasurementSeries { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; - /// use ocptv::output::Value; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); - /// series.start()?; - /// series.add_measurement(Value::from(60))?; + /// step.start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement(Value::from(60)).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { let element = objects::MeasurementSeriesElement::new( @@ -1156,17 +1221,22 @@ impl MeasurementSeries { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; - /// use ocptv::output::Value; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); - /// series.start()?; - /// series.add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))])?; + /// step.start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn add_measurement_with_metadata( &self, @@ -1200,22 +1270,27 @@ impl MeasurementSeries { /// /// # Examples /// - /// ``` - /// use ocptv::output::TestRun; - /// use ocptv::output::Value; + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; /// /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// test_run.start().await?; + /// /// let step = test_run.step("step_name")?; - /// step.start()?; - /// let series = step.measurement_series("series_id", "name"); - /// series.start()?; - /// series.scope(|s| { - /// s.add_measurement(Value::from(60))?; - /// s.add_measurement(Value::from(70))?; - /// s.add_measurement(Value::from(80))?; + /// step.start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.scope(|s| async { + /// s.add_measurement(Value::from(60)).await?; + /// s.add_measurement(Value::from(70)).await?; + /// s.add_measurement(Value::from(80)).await?; /// Ok(()) - /// })?; + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); /// ``` pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> where @@ -1231,7 +1306,6 @@ impl MeasurementSeries { #[cfg(test)] mod tests { - use std::sync::Arc; use anyhow::Result; From a8a889c859c65cfc66b980a852bdfbe58ada47e2 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 13:44:54 +0100 Subject: [PATCH 04/96] unit test cleanup - non-functional cleanup and formatting for tests - replace stdout write with println! in StdoutWriter otherwise it escapes the test harness Signed-off-by: mimir-d --- DEVELOPERS.md | 11 + src/output/emitters.rs | 23 +- src/output/macros.rs | 252 +++++++++++++++----- src/output/models.rs | 2 +- src/output/objects.rs | 513 +++++++++++++++++++++++++---------------- src/output/runner.rs | 350 ++++++++++++++-------------- 6 files changed, 714 insertions(+), 437 deletions(-) create mode 100644 DEVELOPERS.md diff --git a/DEVELOPERS.md b/DEVELOPERS.md new file mode 100644 index 0000000..8751335 --- /dev/null +++ b/DEVELOPERS.md @@ -0,0 +1,11 @@ +### Coding style + +- unit tests should return a `Result` type. Example: + ```rust + #[test] + fn is_equal_to_42() -> anyhow::Result<()> { + let x = maybe_return_42()?; + assert_eq!(x, 42); + Ok(()) + } + ``` \ No newline at end of file diff --git a/src/output/emitters.rs b/src/output/emitters.rs index 34c0e30..733bdc6 100644 --- a/src/output/emitters.rs +++ b/src/output/emitters.rs @@ -77,8 +77,7 @@ impl StdoutWriter { } async fn write(&self, s: &str) -> Result<(), WriterError> { - let mut handle = io::stdout().lock(); - writeln!(handle, "{}", s).map_err(WriterError::IoError)?; + println!("{}", s); Ok(()) } } @@ -139,8 +138,8 @@ mod tests { async fn test_emit_using_buffer_writer() -> Result<()> { let expected = json!({ "schemaVersion": { - "major": 2, - "minor": 0 + "major": models::SPEC_VERSION.0, + "minor": models::SPEC_VERSION.1, }, "sequenceNumber": 1 }); @@ -162,8 +161,20 @@ mod tests { #[tokio::test] async fn test_sequence_number_increments_at_each_call() -> Result<()> { - let expected_1 = json!({"schemaVersion": {"major":2,"minor":0},"sequenceNumber":1}); - let expected_2 = json!({"schemaVersion": {"major":2,"minor":0},"sequenceNumber":2}); + let expected_1 = json!({ + "schemaVersion": { + "major": models::SPEC_VERSION.0, + "minor": models::SPEC_VERSION.1, + }, + "sequenceNumber": 1 + }); + let expected_2 = json!({ + "schemaVersion": { + "major": models::SPEC_VERSION.0, + "minor": models::SPEC_VERSION.1, + }, + "sequenceNumber": 2 + }); let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); diff --git a/src/output/macros.rs b/src/output/macros.rs index 4386327..39ab47a 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -192,6 +192,7 @@ macro_rules! ocptv_log_fatal { mod tests { use std::sync::Arc; + use anyhow::anyhow; use anyhow::Result; use assert_json_diff::assert_json_include; use serde_json::json; @@ -202,7 +203,15 @@ mod tests { #[tokio::test] async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"symptom":"symptom"}}}); + let expected = json!({ + "testRunArtifact":{ + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 1 + }); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); @@ -217,25 +226,34 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("error") - .ok_or(anyhow::Error::msg("error key does not exist"))?; + .ok_or(anyhow!("error key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_error_macro_with_symptom() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"error":{"message":null,"softwareInfoIds":null,"symptom":"symptom"}}}); + let expected = json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -250,25 +268,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("error") - .ok_or(anyhow::Error::msg("error key does not exist"))?; + .ok_or(anyhow!("error key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_debug() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"DEBUG"}}}); + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "DEBUG" + } + }, + "sequenceNumber":1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -283,25 +311,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_info() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"INFO"}}}); + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "INFO" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -316,25 +354,36 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; + assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_warning() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"WARNING"}}}); + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "WARNING" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -349,25 +398,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_error() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"ERROR"}}}); + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "ERROR" + } + }, + "sequenceNumber":1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -382,25 +441,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_fatal() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testRunArtifact":{"log":{"message":"log message","severity":"FATAL"}}}); + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "FATAL" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -415,25 +484,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testRunArtifact") - .ok_or(anyhow::Error::msg("testRunArtifact key does not exist"))? + .ok_or(anyhow!("testRunArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"symptom":"symptom"}}}); + let expected = json!({ + "testStepArtifact": { + "error": { + "message": "Error message", + "symptom":"symptom" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -450,25 +529,34 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("error") - .ok_or(anyhow::Error::msg("error key does not exist"))?; + .ok_or(anyhow!("error key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"error":{"message":null,"softwareInfoIds":null,"symptom":"symptom"}}}); + let expected = json!({ + "testStepArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -485,25 +573,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("error") - .ok_or(anyhow::Error::msg("error key does not exist"))?; + .ok_or(anyhow!("error key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_debug_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"DEBUG"}}}); + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "DEBUG" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -519,25 +617,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_info_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"INFO"}}}); + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "INFO" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -553,25 +661,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_warning_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"WARNING"}}}); + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity":"WARNING" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -587,25 +705,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_error_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"ERROR"}}}); + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "ERROR" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -621,25 +749,35 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } #[tokio::test] async fn test_ocptv_log_fatal_in_step() -> Result<()> { - let expected = json!({"sequenceNumber":1,"testStepArtifact":{"log":{"message":"log message","severity":"FATAL"}}}); + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "FATAL" + } + }, + "sequenceNumber": 1 + }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -655,19 +793,21 @@ mod tests { .lock() .await .first() - .ok_or(anyhow::Error::msg("Buffer is empty"))?, + .ok_or(anyhow!("Buffer is empty"))?, )?; assert_json_include!(actual: actual.clone(), expected: &expected); + let source = actual .get("testStepArtifact") - .ok_or(anyhow::Error::msg("testStepArtifact key does not exist"))? + .ok_or(anyhow!("testStepArtifact key does not exist"))? .get("log") - .ok_or(anyhow::Error::msg("log key does not exist"))?; + .ok_or(anyhow!("log key does not exist"))?; assert_ne!( source.get("sourceLocation"), None, "sourceLocation is not present in the serialized object" ); + Ok(()) } } diff --git a/src/output/models.rs b/src/output/models.rs index 9e54a62..fef3088 100644 --- a/src/output/models.rs +++ b/src/output/models.rs @@ -656,7 +656,7 @@ mod tests { let test_date = "2022-01-01T00:00:00.000Z"; let msr = MeasurementSeriesElementSpec { index: 0, - value: Value::from(1.0), + value: 1.0.into(), timestamp: DateTime::parse_from_rfc3339(test_date)?.with_timezone(&chrono_tz::UTC), series_id: "test".to_string(), metadata: None, diff --git a/src/output/objects.rs b/src/output/objects.rs index e4fea77..4b21a61 100644 --- a/src/output/objects.rs +++ b/src/output/objects.rs @@ -889,7 +889,7 @@ impl SoftwareInfoBuilder { /// use ocptv::output::Measurement; /// use ocptv::output::Value; /// -/// let measurement = Measurement::new("name", Value::from(50)); +/// let measurement = Measurement::new("name", 50.into()); /// ``` /// /// ## Create a Measurement object with the `builder` method @@ -902,10 +902,10 @@ impl SoftwareInfoBuilder { /// use ocptv::output::ValidatorType; /// use ocptv::output::Value; /// -/// let measurement = Measurement::builder("name", Value::from(50)) +/// let measurement = Measurement::builder("name", 50.into()) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) -/// .add_metadata("key", Value::from("value")) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` @@ -928,7 +928,7 @@ impl Measurement { /// use ocptv::output::Measurement; /// use ocptv::output::Value; /// - /// let measurement = Measurement::new("name", Value::from(50)); + /// let measurement = Measurement::new("name", 50.into()); /// ``` pub fn new(name: &str, value: Value) -> Self { Measurement { @@ -954,10 +954,10 @@ impl Measurement { /// use ocptv::output::ValidatorType; /// use ocptv::output::Value; /// - /// let measurement = Measurement::builder("name", Value::from(50)) + /// let measurement = Measurement::builder("name", 50.into()) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) - /// .add_metadata("key", Value::from("value")) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_metadata("key", "value".into()) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` @@ -973,7 +973,7 @@ impl Measurement { /// use ocptv::output::Measurement; /// use ocptv::output::Value; /// - /// let measurement = Measurement::new("name", Value::from(50)); + /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` pub fn to_artifact(&self) -> models::OutputArtifactDescendant { @@ -1013,10 +1013,10 @@ impl Measurement { /// use ocptv::output::ValidatorType; /// use ocptv::output::Value; /// -/// let builder = MeasurementBuilder::new("name", Value::from(50)) +/// let builder = MeasurementBuilder::new("name", 50.into()) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) -/// .add_metadata("key", Value::from("value")) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) /// .subcomponent(&Subcomponent::builder("name").build()); /// let measurement = builder.build(); /// ``` @@ -1039,7 +1039,7 @@ impl MeasurementBuilder { /// use ocptv::output::MeasurementBuilder; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50)); + /// let builder = MeasurementBuilder::new("name", 50.into()); /// ``` pub fn new(name: &str, value: Value) -> Self { MeasurementBuilder { @@ -1065,8 +1065,8 @@ impl MeasurementBuilder { /// use ocptv::output::ValidatorType; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50)) - /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()); + /// let builder = MeasurementBuilder::new("name", 50.into()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); /// ``` pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { self.validators = match self.validators { @@ -1088,7 +1088,7 @@ impl MeasurementBuilder { /// use ocptv::output::MeasurementBuilder; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50)) + /// let builder = MeasurementBuilder::new("name", 50.into()) /// .hardware_info(&HardwareInfo::builder("id", "name").build()); /// ``` pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementBuilder { @@ -1105,7 +1105,7 @@ impl MeasurementBuilder { /// use ocptv::output::Subcomponent; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50)) + /// let builder = MeasurementBuilder::new("name", 50.into()) /// .subcomponent(&Subcomponent::builder("name").build()); /// ``` pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementBuilder { @@ -1122,7 +1122,7 @@ impl MeasurementBuilder { /// use ocptv::output::Value; /// /// let builder = - /// MeasurementBuilder::new("name", Value::from(50)).add_metadata("key", Value::from("value")); + /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); /// ``` pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { match self.metadata { @@ -1130,11 +1130,9 @@ impl MeasurementBuilder { metadata.insert(key.to_string(), value.clone()); } None => { - self.metadata = Some(Map::new()); - self.metadata - .as_mut() - .unwrap() - .insert(key.to_string(), value.clone()); + let mut entries = serde_json::Map::new(); + entries.insert(key.to_owned(), value); + self.metadata = Some(entries); } }; self @@ -1148,7 +1146,7 @@ impl MeasurementBuilder { /// use ocptv::output::MeasurementBuilder; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50000)).unit("RPM"); + /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); /// ``` pub fn unit(mut self, unit: &str) -> MeasurementBuilder { self.unit = Some(unit.to_string()); @@ -1163,7 +1161,7 @@ impl MeasurementBuilder { /// use ocptv::output::MeasurementBuilder; /// use ocptv::output::Value; /// - /// let builder = MeasurementBuilder::new("name", Value::from(50)); + /// let builder = MeasurementBuilder::new("name", 50.into()); /// let measurement = builder.build(); /// ``` pub fn build(self) -> Measurement { @@ -1378,6 +1376,9 @@ impl MeasurementSeriesElement { #[cfg(test)] mod tests { + use anyhow::bail; + use anyhow::Result; + use assert_json_diff::assert_json_include; use serde_json::Map; use serde_json::Value; @@ -1387,117 +1388,116 @@ mod tests { use crate::output::models::ValidatorType; #[test] - fn test_schema_creation_from_builder() { - let version = super::SchemaVersion::new(); + fn test_schema_creation_from_builder() -> Result<()> { + let version = SchemaVersion::new(); assert_eq!(version.major, models::SPEC_VERSION.0); assert_eq!(version.minor, models::SPEC_VERSION.1); + Ok(()) } #[test] - fn test_dut_creation_from_builder_with_defaults() { - let dut = super::DutInfo::builder("1234").build(); + fn test_dut_creation_from_builder_with_defaults() -> Result<()> { + let dut = DutInfo::builder("1234").build(); assert_eq!(dut.id, "1234"); + Ok(()) } #[test] - fn test_log_output_as_test_run_descendant_to_artifact() { - let log = super::Log::builder("test") - .severity(super::models::LogSeverity::Info) + fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(super::ArtifactContext::TestRun); + + let artifact = log.to_artifact(ArtifactContext::TestRun); assert_eq!( artifact, - super::models::OutputArtifactDescendant::TestRunArtifact( - super::models::TestRunArtifactSpec { - descendant: super::models::TestRunArtifactDescendant::Log( - super::models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - } - ), - } - ) + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) ); + + Ok(()) } #[test] - fn test_log_output_as_test_step_descendant_to_artifact() { - let log = super::Log::builder("test") - .severity(super::models::LogSeverity::Info) + fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(super::ArtifactContext::TestStep); + + let artifact = log.to_artifact(ArtifactContext::TestStep); assert_eq!( artifact, - super::models::OutputArtifactDescendant::TestStepArtifact( - super::models::TestStepArtifactSpec { - descendant: super::models::TestStepArtifactDescendant::Log( - super::models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - } - ), - } - ) + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) ); + + Ok(()) } #[test] - fn test_error_output_as_test_run_descendant_to_artifact() { - let error = super::Error::builder("symptom") + fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") .message("") - .add_software_info(&super::SoftwareInfo::builder("id", "name").build()) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) .source("", 1) .build(); - let artifact = error.to_artifact(super::ArtifactContext::TestRun); + + let artifact = error.to_artifact(ArtifactContext::TestRun); assert_eq!( artifact, - super::models::OutputArtifactDescendant::TestRunArtifact( - super::models::TestRunArtifactSpec { - descendant: super::models::TestRunArtifactDescendant::Error( - super::models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - } - ), - } - ) + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) ); + + Ok(()) } #[test] - fn test_error_output_as_test_step_descendant_to_artifact() { - let error = super::Error::builder("symptom") + fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") .message("") - .add_software_info(&super::SoftwareInfo::builder("id", "name").build()) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) .source("", 1) .build(); - let artifact = error.to_artifact(super::ArtifactContext::TestStep); + + let artifact = error.to_artifact(ArtifactContext::TestStep); assert_eq!( artifact, - super::models::OutputArtifactDescendant::TestStepArtifact( - super::models::TestStepArtifactSpec { - descendant: super::models::TestStepArtifactDescendant::Error( - super::models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - } - ), - } - ) + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) ); + + Ok(()) } #[test] - fn test_measurement_as_test_step_descendant_to_artifact() { - let name = String::from("name"); + fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); let value = Value::from(50); - let measurement = super::Measurement::new(&name, value.clone()); + let measurement = Measurement::new(&name, value.clone()); + let artifact = measurement.to_artifact(); assert_eq!( artifact, @@ -1515,20 +1515,25 @@ mod tests { ), }) ); + + Ok(()) } #[test] - fn test_measurement_builder_as_test_step_descendant_to_artifact() { - let name = String::from("name"); + fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); let value = Value::from(50000); let hardware_info = HardwareInfo::builder("id", "name").build(); - let validator = Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let meta_key = "key"; let meta_value = Value::from("value"); let mut metadata = Map::new(); metadata.insert(meta_key.to_string(), meta_value.clone()); metadata.insert(meta_key.to_string(), meta_value.clone()); + let subcomponent = Subcomponent::builder("name").build(); + let unit = "RPM"; let measurement = Measurement::builder(&name, value.clone()) .hardware_info(&hardware_info) @@ -1539,13 +1544,14 @@ mod tests { .subcomponent(&subcomponent) .unit(unit) .build(); + let artifact = measurement.to_artifact(); assert_eq!( artifact, models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement( models::MeasurementSpec { - name: name.to_string(), + name, unit: Some(unit.to_string()), value, validators: Some(vec![validator.to_spec(), validator.to_spec()]), @@ -1556,13 +1562,16 @@ mod tests { ), }) ); + + Ok(()) } #[test] - fn test_measurement_series_start_to_artifact() { - let name = String::from("name"); - let series_id = String::from("series_id"); - let series = super::MeasurementSeriesStart::new(&name, &series_id); + fn test_measurement_series_start_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesStart::new(&name, &series_id); + let artifact = series.to_artifact(); assert_eq!( artifact, @@ -1580,21 +1589,22 @@ mod tests { ), }) ); + + Ok(()) } #[test] - fn test_measurement_series_start_builder_to_artifact() { - let name = String::from("name"); - let series_id = String::from("series_id"); - let validator = Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(); - let validator2 = - Validator::builder(models::ValidatorType::GreaterThen, Value::from(10)).build(); + fn test_measurement_series_start_builder_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); let hw_info = HardwareInfo::builder("id", "name").build(); let subcomponent = Subcomponent::builder("name").build(); - let series = super::MeasurementSeriesStart::builder(&name, &series_id) + let series = MeasurementSeriesStart::builder(&name, &series_id) .unit("unit") - .add_metadata("key", Value::from("value")) - .add_metadata("key2", Value::from("value2")) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .add_validator(&validator) .add_validator(&validator2) .hardware_info(&hw_info) @@ -1607,26 +1617,29 @@ mod tests { models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { - name: name.to_string(), + name, unit: Some("unit".to_string()), series_id: series_id.to_string(), validators: Some(vec![validator.to_spec(), validator2.to_spec()]), hardware_info: Some(hw_info.to_spec()), subcomponent: Some(subcomponent.to_spec()), - metadata: Some(Map::from_iter([ - ("key".to_string(), Value::from("value")), - ("key2".to_string(), Value::from("value2")) + metadata: Some(serde_json::Map::from_iter([ + ("key".to_string(), "value".into()), + ("key2".to_string(), "value2".into()) ])), } ), }) ); + + Ok(()) } #[test] - fn test_measurement_series_end_to_artifact() { - let series_id = String::from("series_id"); - let series = super::MeasurementSeriesEnd::new(&series_id, 1); + fn test_measurement_series_end_to_artifact() -> Result<()> { + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesEnd::new(&series_id, 1); + let artifact = series.to_artifact(); assert_eq!( artifact, @@ -1639,17 +1652,19 @@ mod tests { ), }) ); + + Ok(()) } #[test] - fn test_dut_builder() { - let platform = super::PlatformInfo::builder("platform_info").build(); - let software = super::SoftwareInfo::builder("software_id", "name").build(); - let hardware = super::HardwareInfo::builder("hardware_id", "name").build(); - let dut = super::DutInfo::builder("1234") + fn test_dut_builder() -> Result<()> { + let platform = PlatformInfo::builder("platform_info").build(); + let software = SoftwareInfo::builder("software_id", "name").build(); + let hardware = HardwareInfo::builder("hardware_id", "name").build(); + let dut = DutInfo::builder("1234") .name("DUT") - .add_metadata("key", Value::from("value")) - .add_metadata("key2", Value::from("value2")) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .add_hardware_info(&hardware) .add_hardware_info(&hardware) .add_platform_info(&platform) @@ -1657,63 +1672,159 @@ mod tests { .add_software_info(&software) .add_software_info(&software) .build(); - assert_eq!(dut.to_spec().id, "1234"); - assert_eq!(dut.to_spec().name.unwrap(), "DUT"); - assert_eq!(dut.to_spec().metadata.unwrap()["key"], "value"); - assert_eq!(dut.to_spec().metadata.unwrap()["key2"], "value2"); - assert_eq!( - dut.to_spec().hardware_infos.unwrap().first().unwrap().id, - "hardware_id" - ); - assert_eq!( - dut.to_spec().software_infos.unwrap().first().unwrap().id, - "software_id" - ); - assert_eq!( - dut.to_spec().platform_infos.unwrap().first().unwrap().info, - "platform_info" - ); + + let spec_dut = dut.to_spec(); + + assert_eq!(spec_dut.id, "1234"); + assert_eq!(spec_dut.name, Some("DUT".to_owned())); + + match spec_dut.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is empty"), + } + + match spec_dut.hardware_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "hardware_id"); + } + _ => bail!("hardware_infos is empty"), + }, + _ => bail!("hardware_infos is missing"), + } + + match spec_dut.software_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "software_id"); + } + _ => bail!("software_infos is empty"), + }, + _ => bail!("software_infos is missing"), + } + + match spec_dut.platform_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.info, "platform_info"); + } + _ => bail!("platform_infos is empty"), + }, + _ => bail!("platform_infos is missing"), + } + + Ok(()) } #[test] - fn test_error() { - let expected_run = serde_json::json!({"testRunArtifact": {"error": {"message": "message", "softwareInfoIds": [{"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "software_id", "softwareType": null, "version": null}, {"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "software_id", "softwareType": null, "version": null}], "sourceLocation": {"file": "file.rs", "line": 1}, "symptom": "symptom"}}}); - let expected_step = serde_json::json!({"testStepArtifact":{"error":{"message":"message","softwareInfoIds":[{"computerSystem":null,"name":"name","revision":null,"softwareInfoId":"software_id","softwareType":null,"version":null},{"computerSystem":null,"name":"name","revision":null,"softwareInfoId":"software_id","softwareType":null,"version":null}],"sourceLocation":{"file":"file.rs","line":1},"symptom":"symptom"}}}); + fn test_error() -> Result<()> { + let expected_run = serde_json::json!({ + "testRunArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file": "file.rs", "line": 1}, + "symptom": "symptom" + } + } + }); + let expected_step = serde_json::json!({ + "testStepArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file":"file.rs","line":1}, + "symptom":"symptom" + } + } + }); - let software = super::SoftwareInfo::builder("software_id", "name").build(); - let error = super::ErrorBuilder::new("symptom") + let software = SoftwareInfo::builder("software_id", "name").build(); + let error = ErrorBuilder::new("symptom") .message("message") .source("file.rs", 1) .add_software_info(&software) .add_software_info(&software) .build(); - let spec = error.to_artifact(ArtifactContext::TestRun); - let actual = serde_json::json!(spec); + + let spec_error = error.to_artifact(ArtifactContext::TestRun); + let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_run); - let spec = error.to_artifact(ArtifactContext::TestStep); - let actual = serde_json::json!(spec); + let spec_error = error.to_artifact(ArtifactContext::TestStep); + let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_step); + + Ok(()) } #[test] - fn test_validator() { - let validator = super::Validator::builder(ValidatorType::Equal, Value::from(30)) + fn test_validator() -> Result<()> { + let validator = Validator::builder(ValidatorType::Equal, 30.into()) .name("validator") - .add_metadata("key", Value::from("value")) - .add_metadata("key2", Value::from("value2")) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .build(); - assert_eq!(validator.to_spec().name.unwrap(), "validator"); - assert_eq!(validator.to_spec().value, 30); - assert_eq!(validator.to_spec().validator_type, ValidatorType::Equal); - assert_eq!(validator.to_spec().metadata.unwrap()["key"], "value"); - assert_eq!(validator.to_spec().metadata.unwrap()["key2"], "value2"); + let spec_validator = validator.to_spec(); + + assert_eq!(spec_validator.name, Some("validator".to_owned())); + assert_eq!(spec_validator.value, 30); + assert_eq!(spec_validator.validator_type, ValidatorType::Equal); + + match spec_validator.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is none"), + } + + Ok(()) } #[test] - fn test_hardware_info() { - let info = super::HardwareInfo::builder("hardware_id", "hardware_name") + fn test_hardware_info() -> Result<()> { + let info = HardwareInfo::builder("hardware_id", "hardware_name") .version("version") .revision("revision") .location("location") @@ -1726,66 +1837,82 @@ mod tests { .manager("manager") .build(); - assert_eq!(info.to_spec().id, "hardware_id"); - assert_eq!(info.to_spec().name, "hardware_name"); - assert_eq!(info.to_spec().version.unwrap(), "version"); - assert_eq!(info.to_spec().revision.unwrap(), "revision"); - assert_eq!(info.to_spec().location.unwrap(), "location"); - assert_eq!(info.to_spec().serial_no.unwrap(), "serial_no"); - assert_eq!(info.to_spec().part_no.unwrap(), "part_no"); - assert_eq!(info.to_spec().manufacturer.unwrap(), "manufacturer"); + let spec_hwinfo = info.to_spec(); + + assert_eq!(spec_hwinfo.id, "hardware_id"); + assert_eq!(spec_hwinfo.name, "hardware_name"); + assert_eq!(spec_hwinfo.version, Some("version".to_owned())); + assert_eq!(spec_hwinfo.revision, Some("revision".to_owned())); + assert_eq!(spec_hwinfo.location, Some("location".to_owned())); + assert_eq!(spec_hwinfo.serial_no, Some("serial_no".to_owned())); + assert_eq!(spec_hwinfo.part_no, Some("part_no".to_owned())); + assert_eq!(spec_hwinfo.manufacturer, Some("manufacturer".to_owned())); + assert_eq!( + spec_hwinfo.manufacturer_part_no, + Some("manufacturer_part_no".to_owned()) + ); + assert_eq!(spec_hwinfo.odata_id, Some("odata_id".to_owned())); assert_eq!( - info.to_spec().manufacturer_part_no.unwrap(), - "manufacturer_part_no" + spec_hwinfo.computer_system, + Some("computer_system".to_owned()) ); - assert_eq!(info.to_spec().odata_id.unwrap(), "odata_id"); - assert_eq!(info.to_spec().computer_system.unwrap(), "computer_system"); - assert_eq!(info.to_spec().manager.unwrap(), "manager"); + assert_eq!(spec_hwinfo.manager, Some("manager".to_owned())); + + Ok(()) } #[test] - fn test_subcomponent() { - let sub = super::Subcomponent::builder("sub_name") + fn test_subcomponent() -> Result<()> { + let sub = Subcomponent::builder("sub_name") .subcomponent_type(models::SubcomponentType::Asic) .version("version") .location("location") .revision("revision") .build(); - assert_eq!(sub.to_spec().name, "sub_name"); - assert_eq!(sub.to_spec().version.unwrap(), "version"); - assert_eq!(sub.to_spec().revision.unwrap(), "revision"); - assert_eq!(sub.to_spec().location.unwrap(), "location"); + let spec_subcomponent = sub.to_spec(); + + assert_eq!(spec_subcomponent.name, "sub_name"); + assert_eq!(spec_subcomponent.version, Some("version".to_owned())); + assert_eq!(spec_subcomponent.revision, Some("revision".to_owned())); + assert_eq!(spec_subcomponent.location, Some("location".to_owned())); assert_eq!( - sub.to_spec().subcomponent_type.unwrap(), - models::SubcomponentType::Asic + spec_subcomponent.subcomponent_type, + Some(models::SubcomponentType::Asic) ); + + Ok(()) } #[test] - fn test_platform_info() { - let info = super::PlatformInfo::builder("info").build(); + fn test_platform_info() -> Result<()> { + let info = PlatformInfo::builder("info").build(); assert_eq!(info.to_spec().info, "info"); + Ok(()) } #[test] - fn test_software_info() { - let info = super::SoftwareInfo::builder("software_id", "name") + fn test_software_info() -> Result<()> { + let info = SoftwareInfo::builder("software_id", "name") .version("version") .revision("revision") .software_type(models::SoftwareType::Application) .computer_system("system") .build(); - assert_eq!(info.to_spec().id, "software_id"); - assert_eq!(info.to_spec().name, "name"); - assert_eq!(info.to_spec().version.unwrap(), "version"); - assert_eq!(info.to_spec().revision.unwrap(), "revision"); + let spec_swinfo = info.to_spec(); + + assert_eq!(spec_swinfo.id, "software_id"); + assert_eq!(spec_swinfo.name, "name"); + assert_eq!(spec_swinfo.version, Some("version".to_owned())); + assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); assert_eq!( - info.to_spec().software_type.unwrap(), - models::SoftwareType::Application + spec_swinfo.software_type, + Some(models::SoftwareType::Application) ); - assert_eq!(info.to_spec().computer_system.unwrap(), "system"); + assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); + + Ok(()) } } diff --git a/src/output/runner.rs b/src/output/runner.rs index 8958f87..f33d79c 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -185,11 +185,13 @@ impl TestRun { &self.parameters, &self.dut, ); - if self.metadata.is_some() { - for m in self.metadata.as_ref().unwrap().into_iter() { + + if let Some(m) = &self.metadata { + for m in m { builder = builder.add_metadata(m.0, m.1.clone()) } } + let start = builder.build(); self.state .lock() @@ -982,7 +984,7 @@ impl TestStep { /// /// let measurement = Measurement::builder("name", 5000.into()) /// .hardware_info(&hwinfo) - /// .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); @@ -1192,7 +1194,7 @@ impl MeasurementSeries { /// /// let series = step.measurement_series("name"); /// series.start().await?; - /// series.add_measurement(Value::from(60)).await?; + /// series.add_measurement(60.into()).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -1233,7 +1235,7 @@ impl MeasurementSeries { /// /// let series = step.measurement_series("name"); /// series.start().await?; - /// series.add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]).await?; + /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -1283,9 +1285,9 @@ impl MeasurementSeries { /// let series = step.measurement_series("name"); /// series.start().await?; /// series.scope(|s| async { - /// s.add_measurement(Value::from(60)).await?; - /// s.add_measurement(Value::from(70)).await?; - /// s.add_measurement(Value::from(80)).await?; + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; /// Ok(()) /// }).await?; /// @@ -1318,16 +1320,16 @@ mod tests { use crate::output::objects::*; #[tokio::test] - async fn test_testrun_start_and_end() { + async fn test_testrun_start_and_end() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1335,31 +1337,29 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_log() { + async fn test_testrun_with_log() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1367,27 +1367,27 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); + test_run.start().await?; + test_run .log( LogSeverity::Info, "This is a log message with INFO severity", ) - .await - .unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + .await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_log_with_details() { + async fn test_testrun_with_log_with_details() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), @@ -1397,11 +1397,10 @@ mod tests { }}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1409,7 +1408,8 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); + test_run.start().await?; + test_run .log_with_details( &Log::builder("This is a log message with INFO severity") @@ -1417,32 +1417,30 @@ mod tests { .source("file", 1) .build(), ) - .await - .unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + .await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_error() { + async fn test_testrun_with_error() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":null,"softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1450,32 +1448,32 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); - test_run.error("symptom").await.unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + + test_run.error("symptom").await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_error_with_message() { + async fn test_testrun_with_error_with_message() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1483,24 +1481,22 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); - test_run - .error_with_msg("symptom", "Error message") - .await - .unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + + test_run.error_with_msg("symptom", "Error message").await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_error_with_details() { + async fn test_testrun_with_error_with_details() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), @@ -1519,11 +1515,10 @@ mod tests { },"symptom":"symptom"}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1531,7 +1526,8 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); + test_run.start().await?; + test_run .error_with_details( &Error::builder("symptom") @@ -1540,28 +1536,26 @@ mod tests { .add_software_info(&SoftwareInfo::builder("id", "name").build()) .build(), ) - .await - .unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + .await?; + + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] - async fn test_testrun_with_scope() { + async fn test_testrun_with_scope() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"First message","severity":"INFO","sourceLocation":null}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); @@ -1572,6 +1566,7 @@ mod tests { .build(), ) .build(); + test_run .scope(|r| async { r.log(LogSeverity::Info, "First message").await?; @@ -1580,13 +1575,14 @@ mod tests { result: TestResult::Pass, }) }) - .await - .unwrap(); + .await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } + + Ok(()) } #[tokio::test] @@ -1598,11 +1594,10 @@ mod tests { json!({"sequenceNumber":4,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":5,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1636,11 +1631,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1652,11 +1646,13 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + step.log( LogSeverity::Info, "This is a log message with INFO severity", ) .await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1679,11 +1675,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1695,6 +1690,7 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + step.log_with_details( &Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) @@ -1702,6 +1698,7 @@ mod tests { .build(), ) .await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1724,11 +1721,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1740,7 +1736,9 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + step.error("symptom").await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1763,11 +1761,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1779,7 +1776,9 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + step.error_with_msg("symptom", "Error message").await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1802,11 +1801,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1818,6 +1816,7 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + step.error_with_details( &Error::builder("symptom") .message("Error message") @@ -1826,6 +1825,7 @@ mod tests { .build(), ) .await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1848,11 +1848,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1862,16 +1861,17 @@ mod tests { .build(); test_run.start().await?; - let step = test_run.step("first step")?; - step.scope(|s: &TestStep| async { - s.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) + test_run + .step("first step")? + .scope(|s| async { + s.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + Ok(TestStatus::Complete) + }) .await?; - Ok(TestStatus::Complete) - }) - .await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1893,11 +1893,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1909,7 +1908,9 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; - step.add_measurement("name", Value::from(50)).await?; + + step.add_measurement("name", 50.into()).await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1932,11 +1933,10 @@ mod tests { json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1948,15 +1948,17 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; - let measurement = Measurement::builder("name", Value::from(50)) + + let measurement = Measurement::builder("name", 50.into()) .hardware_info(&objects::HardwareInfo::builder("id", "name").build()) .add_validator( - &objects::Validator::builder(models::ValidatorType::Equal, Value::from(30)).build(), + &objects::Validator::builder(models::ValidatorType::Equal, 30.into()).build(), ) - .add_metadata("key", Value::from("value")) + .add_metadata("key", "value".into()) .subcomponent(&objects::Subcomponent::builder("name").build()) .build(); step.add_measurement_with_details(&measurement).await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1980,11 +1982,10 @@ mod tests { json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -1996,9 +1997,11 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + let series = step.measurement_series("name"); series.start().await?; series.end().await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -2024,11 +2027,10 @@ mod tests { json!({"sequenceNumber":8,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":9,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2040,12 +2042,15 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + let series = step.measurement_series("name"); series.start().await?; series.end().await?; + let series_2 = step.measurement_series("name"); series_2.start().await?; series_2.end().await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -2069,11 +2074,10 @@ mod tests { json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2085,10 +2089,12 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + let series = step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); series.start().await?; series.end().await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -2112,11 +2118,10 @@ mod tests { json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2128,16 +2133,18 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + let series = step.measurement_series_with_details( MeasurementSeriesStart::builder("name", "series_id") - .add_metadata("key", Value::from("value")) - .add_validator(&Validator::builder(ValidatorType::Equal, Value::from(30)).build()) + .add_metadata("key", "value".into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) .hardware_info(&HardwareInfo::builder("id", "name").build()) .subcomponent(&Subcomponent::builder("name").build()) .build(), ); series.start().await?; series.end().await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -2162,11 +2169,10 @@ mod tests { json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2178,10 +2184,12 @@ mod tests { let step = test_run.step("first step")?; step.start().await?; + let series = step.measurement_series("name"); series.start().await?; - series.add_measurement(Value::from(60)).await?; + series.add_measurement(60.into()).await?; series.end().await?; + step.end(TestStatus::Complete).await?; test_run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -2208,11 +2216,10 @@ mod tests { json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2227,9 +2234,9 @@ mod tests { let series = step.measurement_series("name"); series.start().await?; // add more than one element to check the index increments correctly - series.add_measurement(Value::from(60)).await?; - series.add_measurement(Value::from(70)).await?; - series.add_measurement(Value::from(80)).await?; + series.add_measurement(60.into()).await?; + series.add_measurement(70.into()).await?; + series.add_measurement(80.into()).await?; series.end().await?; step.end(TestStatus::Complete).await?; @@ -2255,11 +2262,10 @@ mod tests { json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2274,7 +2280,7 @@ mod tests { let series = step.measurement_series("name"); series.start().await?; series - .add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]) + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) .await?; series.end().await?; step.end(TestStatus::Complete).await?; @@ -2303,11 +2309,10 @@ mod tests { json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2323,13 +2328,13 @@ mod tests { series.start().await?; // add more than one element to check the index increments correctly series - .add_measurement_with_metadata(Value::from(60), vec![("key", Value::from("value"))]) + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) .await?; series - .add_measurement_with_metadata(Value::from(70), vec![("key2", Value::from("value2"))]) + .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) .await?; series - .add_measurement_with_metadata(Value::from(80), vec![("key3", Value::from("value3"))]) + .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) .await?; series.end().await?; step.end(TestStatus::Complete).await?; @@ -2358,11 +2363,10 @@ mod tests { json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2377,9 +2381,10 @@ mod tests { let series = step.measurement_series("name"); series .scope(|s| async { - s.add_measurement(Value::from(60)).await?; - s.add_measurement(Value::from(70)).await?; - s.add_measurement(Value::from(80)).await?; + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; + Ok(()) }) .await?; @@ -2396,18 +2401,17 @@ mod tests { } #[tokio::test] - async fn test_config_builder() -> anyhow::Result<()> { + async fn test_config_builder() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() @@ -2418,18 +2422,12 @@ mod tests { .build(), ) .build(); - test_run.start().await.unwrap(); - test_run - .error_with_msg("symptom", "Error message") - .await - .unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + test_run.error_with_msg("symptom", "Error message").await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } @@ -2437,45 +2435,39 @@ mod tests { } #[tokio::test] - async fn test_testrun_instantiation_with_new() { + async fn test_testrun_instantiation_with_new() -> Result<()> { let test_run = TestRun::new("run_name", "dut_id", "1.0"); - test_run.start().await.unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; assert_eq!(test_run.dut.to_spec().id, "dut_id"); + Ok(()) } #[tokio::test] - async fn test_testrun_metadata() -> anyhow::Result<()> { + async fn test_testrun_metadata() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value"},"name":"run_name","parameters":{},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) .build(), ) - .add_metadata("key", Value::from("value")) + .add_metadata("key", "value".into()) .build(); - test_run.start().await.unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } @@ -2483,36 +2475,32 @@ mod tests { } #[tokio::test] - async fn test_testrun_builder() -> anyhow::Result<()> { + async fn test_testrun_builder() -> Result<()> { let expected = [ json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"commandLine":"cmd_line", "dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value", "key2": "value2"},"name":"run_name","parameters":{"key": "value"},"version":"1.0"}}}), json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") .config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) .build(), ) - .add_metadata("key", Value::from("value")) - .add_metadata("key2", Value::from("value2")) - .add_parameter("key", Value::from("value")) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_parameter("key", "value".into()) .command_line("cmd_line") .build(); - test_run.start().await.unwrap(); - test_run - .end(TestStatus::Complete, TestResult::Pass) - .await - .unwrap(); + test_run.start().await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); + let value = serde_json::from_str::(entry)?; assert_json_include!(actual: value, expected: &expected[idx]); } From 43810b6873da53a6ae7f3aa9a2fd0b8b5d22fb75 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 18:30:52 +0100 Subject: [PATCH 05/96] remove noise from runner tests - extract some common testing methods for ocptv runs and steps - extract common json messages - this results in keeping only the expected output and specific testcase logic, removing all other repeated boilerplate noise Signed-off-by: mimir-d --- Cargo.lock | 99 +++ Cargo.toml | 1 + src/output/runner.rs | 1983 ++++++++++++++++++++++-------------------- 3 files changed, 1130 insertions(+), 953 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d03b988..a1fd678 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -199,12 +199,95 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "gimli" version = "0.31.0" @@ -303,6 +386,7 @@ dependencies = [ "chrono", "chrono-tz", "derive_more", + "futures", "serde", "serde_json", "thiserror", @@ -372,6 +456,12 @@ version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "portable-atomic" version = "1.9.0" @@ -496,6 +586,15 @@ version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + [[package]] name = "syn" version = "2.0.79" diff --git a/Cargo.toml b/Cargo.toml index c69a37e..c7b7ca0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,4 +27,5 @@ tokio = { version = "1.40.0", features = [ [dev-dependencies] anyhow = "1.0.89" assert-json-diff = "2.0.2" +futures = "0.3.30" tokio-test = "0.4.4" diff --git a/src/output/runner.rs b/src/output/runner.rs index f33d79c..978f83f 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -1312,6 +1312,8 @@ mod tests { use anyhow::Result; use assert_json_diff::assert_json_include; + use futures::future::BoxFuture; + use futures::FutureExt; use serde_json::json; use tokio::sync::Mutex; @@ -1319,26 +1321,84 @@ mod tests { use crate::output::models::*; use crate::output::objects::*; - #[tokio::test] - async fn test_testrun_start_and_end() -> Result<()> { - let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), - ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + fn json_schema_version() -> serde_json::Value { + // seqno for schemaVersion is always 1 + json!({ + "schemaVersion": { + "major": models::SPEC_VERSION.0, + "minor": models::SPEC_VERSION.1 + }, + "sequenceNumber": 1 + }) + } + + fn json_run_default_start() -> serde_json::Value { + // seqno for the default test run start is always 2 + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "name": "run_name", + "parameters": {}, + "version": "1.0" + } + }, + "sequenceNumber": 2 + }) + } + + fn json_run_pass(seqno: i32) -> serde_json::Value { + json!({ + "testRunArtifact": { + "testRunEnd": { + "result": "PASS", + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno + }) + } + + fn json_step_default_start() -> serde_json::Value { + // seqno for the default test run start is always 3 + json!({ + "testStepArtifact": { + "testStepStart": { + "name": "first step" + } + }, + "sequenceNumber": 3 + }) + } + + fn json_step_complete(seqno: i32) -> serde_json::Value { + json!({ + "testStepArtifact": { + "testStepEnd": { + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno + }) + } + async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> + where + R: Future>, + F: FnOnce(TestRunBuilder) -> R, + { + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); + let run_builder = TestRun::builder("run_name", &dut, "1.0").config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + // run the main test closure + test_fn(run_builder).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; @@ -1348,227 +1408,226 @@ mod tests { Ok(()) } - #[tokio::test] - async fn test_testrun_with_log() -> Result<()> { - let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), - ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> + where + F: for<'a> FnOnce(&'a TestRun) -> BoxFuture<'a, Result<(), emitters::WriterError>> + Send, + { + check_output(expected, |run_builder| async { + let run = run_builder.build(); - let dut = DutInfo::builder("dut_id").build(); + run.start().await?; + test_fn(&run).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; + Ok(()) + }) + .await + } - test_run - .log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; + async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> + where + F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), emitters::WriterError>>, + { + check_output(expected, |run_builder| async { + let run = run_builder.build(); + run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + let step = run.step("first step")?; + step.start().await?; + test_fn(&step).await?; + step.end(TestStatus::Complete).await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) + Ok(()) + }) + .await } #[tokio::test] - async fn test_testrun_with_log_with_details() -> Result<()> { + async fn test_testrun_start_and_end() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":{ - "file": "file", - "line": 1 - }}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_run_pass(3), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; + check_output_run(&expected, |_| async { Ok(()) }.boxed()).await + } - test_run - .log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; + #[tokio::test] + async fn test_testrun_with_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + check_output_run(&expected, |run| { + async { + run.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await + } + .boxed() + }) + .await + } - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + #[tokio::test] + async fn test_testrun_with_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; - Ok(()) + check_output_run(&expected, |run| { + async { + run.log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_with_error() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":null,"softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - test_run.error("symptom").await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + check_output_run(&expected, |run| { + async { run.error("symptom").await }.boxed() + }) + .await } #[tokio::test] async fn test_testrun_with_error_with_message() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - test_run.error_with_msg("symptom", "Error message").await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + check_output_run(&expected, |run| { + async { run.error_with_msg("symptom", "Error message").await }.boxed() + }) + .await } #[tokio::test] async fn test_testrun_with_error_with_details() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":[ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "id", - "softwareType": null, - "version": null - } - ],"sourceLocation":{ - "file": "file", - "line": 1 - },"symptom":"symptom"}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds":[{ + "name": "name", + "softwareInfoId": "id", + }], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - test_run - .error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .build(), - ) - .await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - Ok(()) + check_output_run(&expected, |run| { + async { + run.error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_with_scope() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"log":{"message":"First message","severity":"INFO","sourceLocation":null}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "First message", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); + check_output(&expected, |run_builder| async { + let run = run_builder.build(); - test_run - .scope(|r| async { + run.scope(|r| async { r.log(LogSeverity::Info, "First message").await?; Ok(TestRunOutcome { status: TestStatus::Complete, @@ -1577,861 +1636,871 @@ mod tests { }) .await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + }) + .await } #[tokio::test] async fn test_testrun_with_step() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":5,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json_step_complete(4), + json_run_pass(5), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - Ok(()) + check_output_step(&expected, |_| async { Ok(()) }.boxed()).await } #[tokio::test] async fn test_testrun_step_log() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 4 + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - step.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - step.end(TestStatus::Complete).await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + check_output_step(&expected, |step| { + async { + step.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_step_log_with_details() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":{"file": "file", "line": 1}}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - let step = test_run.step("first step")?; - step.start().await?; - - step.log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; - - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + check_output_step(&expected, |step| { + async { + step.log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_step_error() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":null,"softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - step.error("symptom").await?; - step.end(TestStatus::Complete).await?; + check_output_step(&expected, |step| { + async { + step.error("symptom").await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_step_error_with_message() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - step.error_with_msg("symptom", "Error message").await?; - - step.end(TestStatus::Complete).await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + check_output_step(&expected, |step| { + async { + step.error_with_msg("symptom", "Error message").await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_step_error_with_details() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"error":{"message":"Error message","softwareInfoIds":[{"computerSystem": null, "name": "name", "revision": null, "softwareInfoId": "id", "softwareType": null, "version": null}],"sourceLocation":{"file": "file", "line": 1},"symptom":"symptom"}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds":[{ + "name": "name", + "softwareInfoId": "id" + }], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - step.error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .build(), - ) - .await?; - - step.end(TestStatus::Complete).await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + check_output_step(&expected, |step| { + async { + step.error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_testrun_step_scope_log() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"log":{"message":"This is a log message with INFO severity","severity":"INFO","sourceLocation":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - test_run - .step("first step")? - .scope(|s| async { - s.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - Ok(TestStatus::Complete) - }) - .await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) - } + check_output_run(&expected, |run| { + async { + run.step("first step")? + .scope(|s| async { + s.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + Ok(TestStatus::Complete) + }) + .await + } + .boxed() + }) + .await + } #[tokio::test] async fn test_step_with_measurement() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurement":{"hardwareInfoId":null,"metadata":null,"name":"name","subcomponent":null,"unit":null,"validators":null,"value":50}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurement": { + "name": "name", "value": 50 + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - step.add_measurement("name", 50.into()).await?; - step.end(TestStatus::Complete).await?; + check_output_step(&expected, |step| { + async { + step.add_measurement("name", 50.into()).await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_builder() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurement":{"hardwareInfoId":"id","metadata":{"key":"value"},"name":"name","subcomponent":{"location":null,"name":"name","revision":null,"type":null,"version":null},"unit":null,"validators":[{"metadata":null,"name":null,"type":"EQUAL","value":30}],"value":50}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":6,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurement": { + "hardwareInfoId": "id", + "metadata": { + "key": "value" + }, + "name": "name", + "subcomponent": { + "name": "name" + }, + "validators":[{ + "type": "EQUAL", + "value": 30 + }], + "value": 50 + } + } + }), + json_step_complete(5), + json_run_pass(6), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - let measurement = Measurement::builder("name", 50.into()) - .hardware_info(&objects::HardwareInfo::builder("id", "name").build()) - .add_validator( - &objects::Validator::builder(models::ValidatorType::Equal, 30.into()).build(), - ) - .add_metadata("key", "value".into()) - .subcomponent(&objects::Subcomponent::builder("name").build()) - .build(); - step.add_measurement_with_details(&measurement).await?; + check_output_step(&expected, |step| { + async { + let measurement = Measurement::builder("name", 50.into()) + .hardware_info(&objects::HardwareInfo::builder("id", "name").build()) + .add_validator( + &objects::Validator::builder(models::ValidatorType::Equal, 30.into()) + .build(), + ) + .add_metadata("key", "value".into()) + .subcomponent(&objects::Subcomponent::builder("name").build()) + .build(); + step.add_measurement_with_details(&measurement).await?; - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":0}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": + "series_1", + "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - let series = step.measurement_series("name"); - series.start().await?; - series.end().await?; - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_multiple_measurement_series() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":0}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_2","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_2","totalCount":0}}}), - json!({"sequenceNumber":8,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":9,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 0 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_2", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_2", + "totalCount": 0 + } + } + }), + json_step_complete(8), + json_run_pass(9), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - let series = step.measurement_series("name"); - series.start().await?; - series.end().await?; - - let series_2 = step.measurement_series("name"); - series_2.start().await?; - series_2.end().await?; - - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } + let series_2 = step.measurement_series("name"); + series_2.start().await?; + series_2.end().await?; - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_with_details() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_id","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_id","totalCount":0}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_id", "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - let series = - step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); - series.start().await?; - series.end().await?; - - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series_with_details(MeasurementSeriesStart::new( + "name", + "series_id", + )); + series.start().await?; + series.end().await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_with_details_and_start_builder() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":{"computerSystem":null,"hardwareInfoId":"id","location":null,"manager":null,"manufacturer":null,"manufacturerPartNumber":null,"name":"name","odataId":null,"partNumber":null,"revision":null,"serialNumber":null,"version":null},"measurementSeriesId":"series_id","metadata":{"key":"value"},"name":"name","subComponent":{"location":null,"name":"name","revision":null,"type":null,"version":null},"unit":null,"validators":[{"metadata":null,"name":null,"type":"EQUAL","value":30}]}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_id","totalCount":0}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":7,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "hardwareInfoId": { + "hardwareInfoId": "id", + "name": "name" + }, + "measurementSeriesId": "series_id", + "metadata": { + "key": "value" + }, + "name": "name", + "subComponent": { + "name": "name" + }, + "validators":[{ + "type": "EQUAL", + "value": 30 + }] + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", + "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series_with_details( + MeasurementSeriesStart::builder("name", "series_id") + .add_metadata("key", "value".into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .subcomponent(&Subcomponent::builder("name").build()) + .build(), + ); + series.start().await?; + series.end().await?; - let step = test_run.step("first step")?; - step.start().await?; - - let series = step.measurement_series_with_details( - MeasurementSeriesStart::builder("name", "series_id") - .add_metadata("key", "value".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(&HardwareInfo::builder("id", "name").build()) - .subcomponent(&Subcomponent::builder("name").build()) - .build(), - ); - series.start().await?; - series.end().await?; - - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_element() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":1}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 1 + } + } + }), + json_step_complete(7), + json_run_pass(8), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.add_measurement(60.into()).await?; + series.end().await?; - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - - let step = test_run.step("first step")?; - step.start().await?; - - let series = step.measurement_series("name"); - series.start().await?; - series.add_measurement(60.into()).await?; - series.end().await?; - - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_element_index_no() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":null,"value":70}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":null,"value":80}}}), - json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), - json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - let step = test_run.step("first step")?; - step.start().await?; - let series = step.measurement_series("name"); - series.start().await?; - // add more than one element to check the index increments correctly - series.add_measurement(60.into()).await?; - series.add_measurement(70.into()).await?; - series.add_measurement(80.into()).await?; - series.end().await?; - step.end(TestStatus::Complete).await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series.add_measurement(60.into()).await?; + series.add_measurement(70.into()).await?; + series.add_measurement(80.into()).await?; + series.end().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":{"key": "value"},"value":60}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":1}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":8,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "metadata": { + "key": "value" + }, + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 1 + } + } + }), + json_step_complete(7), + json_run_pass(8), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - let step = test_run.step("first step")?; - step.start().await?; - let series = step.measurement_series("name"); - series.start().await?; - series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) - .await?; - series.end().await?; - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .await?; + series.end().await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":{"key": "value"},"value":60}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":{"key2": "value2"},"value":70}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":{"key3": "value3"},"value":80}}}), - json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), - json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "metadata": {"key": "value"}, + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "metadata": {"key2": "value2"}, + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "metadata": {"key3": "value3"}, + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; - let step = test_run.step("first step")?; - step.start().await?; - let series = step.measurement_series("name"); - series.start().await?; - // add more than one element to check the index increments correctly - series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) - .await?; - series - .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) - .await?; - series - .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) - .await?; - series.end().await?; - step.end(TestStatus::Complete).await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .await?; + series + .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) + .await?; + series + .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) + .await?; + series.end().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + Ok(()) + } + .boxed() + }) + .await } #[tokio::test] async fn test_step_with_measurement_series_scope() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testStepArtifact":{"testStepStart":{"name":"first step"}}}), - json!({"sequenceNumber":4,"testStepArtifact":{"measurementSeriesStart":{"hardwareInfoId":null,"measurementSeriesId":"series_1","metadata":null,"name":"name","subComponent":null,"unit":null,"validators":null}}}), - json!({"sequenceNumber":5,"testStepArtifact":{"measurementSeriesElement":{"index":0,"measurementSeriesId":"series_1","metadata":null,"value":60}}}), - json!({"sequenceNumber":6,"testStepArtifact":{"measurementSeriesElement":{"index":1,"measurementSeriesId":"series_1","metadata":null,"value":70}}}), - json!({"sequenceNumber":7,"testStepArtifact":{"measurementSeriesElement":{"index":2,"measurementSeriesId":"series_1","metadata":null,"value":80}}}), - json!({"sequenceNumber":8,"testStepArtifact":{"measurementSeriesEnd":{"measurementSeriesId":"series_1","totalCount":3}}}), - json!({"sequenceNumber":9,"testStepArtifact":{"testStepEnd":{"status":"COMPLETE"}}}), - json!({"sequenceNumber":10,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .build(); - test_run.start().await?; + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series + .scope(|s| async { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; - let step = test_run.step("first step")?; - step.start().await?; - let series = step.measurement_series("name"); - series - .scope(|s| async { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; + Ok(()) + }) + .await?; Ok(()) - }) - .await?; - step.end(TestStatus::Complete).await?; - - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) - } - - #[tokio::test] - async fn test_config_builder() -> Result<()> { - let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":null,"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"error":{"message":"Error message","softwareInfoIds":null,"sourceLocation":null,"symptom":"symptom"}}}), - json!({"sequenceNumber":4,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), - ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .timezone(chrono_tz::Europe::Rome) - .with_file_output(std::env::temp_dir().join("file.txt")) - .await? - .build(), - ) - .build(); - test_run.start().await?; - test_run.error_with_msg("symptom", "Error message").await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + } + .boxed() + }) + .await } #[tokio::test] @@ -2447,63 +2516,71 @@ mod tests { #[tokio::test] async fn test_testrun_metadata() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value"},"name":"run_name","parameters":{},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json!({ + "sequenceNumber": 2, + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "metadata": {"key": "value"}, + "name": "run_name", + "parameters": {}, + "version": "1.0" + } + } + }), + json_run_pass(3), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .add_metadata("key", "value".into()) - .build(); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + check_output(&expected, |run_builder| async { + let run = run_builder.add_metadata("key", "value".into()).build(); + run.start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await } #[tokio::test] async fn test_testrun_builder() -> Result<()> { let expected = [ - json!({"schemaVersion":{"major":2,"minor":0},"sequenceNumber":1}), - json!({"sequenceNumber":2,"testRunArtifact":{"testRunStart":{"commandLine":"cmd_line", "dutInfo":{"dutInfoId":"dut_id","hardwareInfos":null,"metadata":null,"name":null,"platformInfos":null,"softwareInfos":null},"metadata":{"key": "value", "key2": "value2"},"name":"run_name","parameters":{"key": "value"},"version":"1.0"}}}), - json!({"sequenceNumber":3,"testRunArtifact":{"testRunEnd":{"result":"PASS","status":"COMPLETE"}}}), + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "commandLine": "cmd_line", + "dutInfo": { + "dutInfoId": "dut_id" + }, + "metadata": { + "key": "value", + "key2": "value2" + }, + "name": "run_name", + "parameters": { + "key": "value" + }, + "version": "1.0" + } + }, + "sequenceNumber": 2 + }), + json_run_pass(3), ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - - let test_run = TestRun::builder("run_name", &dut, "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_parameter("key", "value".into()) - .command_line("cmd_line") - .build(); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) + check_output(&expected, |run_builder| async { + let run = run_builder + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_parameter("key", "value".into()) + .command_line("cmd_line") + .build(); + run.start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await } } From 53308b219246821a9d2d88ef39424b9dbac6ed54 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 2 Oct 2024 18:46:24 +0100 Subject: [PATCH 06/96] change runner tests to integration tests - the runner unittests actually only use public api and look like end-to-end tests, so move them to a separate integration unit Signed-off-by: mimir-d --- src/output/mod.rs | 1 + src/output/runner.rs | 1279 --------------------------------------- tests/output/main.rs | 7 + tests/output/runner.rs | 1291 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1299 insertions(+), 1279 deletions(-) create mode 100644 tests/output/main.rs create mode 100644 tests/output/runner.rs diff --git a/src/output/mod.rs b/src/output/mod.rs index 8c9c5ed..231567f 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -15,6 +15,7 @@ pub use models::LogSeverity; pub use models::TestResult; pub use models::TestStatus; pub use models::ValidatorType; +pub use models::SPEC_VERSION; pub use objects::*; pub use runner::*; pub use serde_json::Value; diff --git a/src/output/runner.rs b/src/output/runner.rs index 978f83f..976b1f4 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -1305,1282 +1305,3 @@ impl MeasurementSeries { Ok(()) } } - -#[cfg(test)] -mod tests { - use std::sync::Arc; - - use anyhow::Result; - use assert_json_diff::assert_json_include; - use futures::future::BoxFuture; - use futures::FutureExt; - use serde_json::json; - use tokio::sync::Mutex; - - use super::*; - use crate::output::models::*; - use crate::output::objects::*; - - fn json_schema_version() -> serde_json::Value { - // seqno for schemaVersion is always 1 - json!({ - "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1 - }, - "sequenceNumber": 1 - }) - } - - fn json_run_default_start() -> serde_json::Value { - // seqno for the default test run start is always 2 - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id" - }, - "name": "run_name", - "parameters": {}, - "version": "1.0" - } - }, - "sequenceNumber": 2 - }) - } - - fn json_run_pass(seqno: i32) -> serde_json::Value { - json!({ - "testRunArtifact": { - "testRunEnd": { - "result": "PASS", - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno - }) - } - - fn json_step_default_start() -> serde_json::Value { - // seqno for the default test run start is always 3 - json!({ - "testStepArtifact": { - "testStepStart": { - "name": "first step" - } - }, - "sequenceNumber": 3 - }) - } - - fn json_step_complete(seqno: i32) -> serde_json::Value { - json!({ - "testStepArtifact": { - "testStepEnd": { - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno - }) - } - - async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> - where - R: Future>, - F: FnOnce(TestRunBuilder) -> R, - { - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - let run_builder = TestRun::builder("run_name", &dut, "1.0").config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .build(), - ); - - // run the main test closure - test_fn(run_builder).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) - } - - async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> - where - F: for<'a> FnOnce(&'a TestRun) -> BoxFuture<'a, Result<(), emitters::WriterError>> + Send, - { - check_output(expected, |run_builder| async { - let run = run_builder.build(); - - run.start().await?; - test_fn(&run).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await - } - - async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> - where - F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), emitters::WriterError>>, - { - check_output(expected, |run_builder| async { - let run = run_builder.build(); - run.start().await?; - - let step = run.step("first step")?; - step.start().await?; - test_fn(&step).await?; - step.end(TestStatus::Complete).await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_testrun_start_and_end() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_run_pass(3), - ]; - - check_output_run(&expected, |_| async { Ok(()) }.boxed()).await - } - - #[tokio::test] - async fn test_testrun_with_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output_run(&expected, |run| { - async { - run.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output_run(&expected, |run| { - async { - run.log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output_run(&expected, |run| { - async { run.error("symptom").await }.boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output_run(&expected, |run| { - async { run.error_with_msg("symptom", "Error message").await }.boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "softwareInfoIds":[{ - "name": "name", - "softwareInfoId": "id", - }], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output_run(&expected, |run| { - async { - run.error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .build(), - ) - .await - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "First message", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output(&expected, |run_builder| async { - let run = run_builder.build(); - - run.scope(|r| async { - r.log(LogSeverity::Info, "First message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - }) - .await?; - - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_testrun_with_step() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |_| async { Ok(()) }.boxed()).await - } - - #[tokio::test] - async fn test_testrun_step_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 4 - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_step_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_step_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "error": { - "symptom": "symptom" - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.error("symptom").await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_step_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.error_with_msg("symptom", "Error message").await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_step_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "error": { - "message": "Error message", - "softwareInfoIds":[{ - "name": "name", - "softwareInfoId": "id" - }], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_step_scope_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_run(&expected, |run| { - async { - run.step("first step")? - .scope(|s| async { - s.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - Ok(TestStatus::Complete) - }) - .await - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurement": { - "name": "name", "value": 50 - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - step.add_measurement("name", 50.into()).await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurement": { - "hardwareInfoId": "id", - "metadata": { - "key": "value" - }, - "name": "name", - "subcomponent": { - "name": "name" - }, - "validators":[{ - "type": "EQUAL", - "value": 30 - }], - "value": 50 - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |step| { - async { - let measurement = Measurement::builder("name", 50.into()) - .hardware_info(&objects::HardwareInfo::builder("id", "name").build()) - .add_validator( - &objects::Validator::builder(models::ValidatorType::Equal, 30.into()) - .build(), - ) - .add_metadata("key", "value".into()) - .subcomponent(&objects::Subcomponent::builder("name").build()) - .build(); - step.add_measurement_with_details(&measurement).await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": - "series_1", - "totalCount": 0 - } - } - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_multiple_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 0 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_2", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 7, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_2", - "totalCount": 0 - } - } - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - series.end().await?; - - let series_2 = step.measurement_series("name"); - series_2.start().await?; - series_2.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_id", "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_id", "totalCount": 0 - } - } - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series_with_details(MeasurementSeriesStart::new( - "name", - "series_id", - )); - series.start().await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_with_details_and_start_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "hardwareInfoId": { - "hardwareInfoId": "id", - "name": "name" - }, - "measurementSeriesId": "series_id", - "metadata": { - "key": "value" - }, - "name": "name", - "subComponent": { - "name": "name" - }, - "validators":[{ - "type": "EQUAL", - "value": 30 - }] - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_id", - "totalCount": 0 - } - } - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series_with_details( - MeasurementSeriesStart::builder("name", "series_id") - .add_metadata("key", "value".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(&HardwareInfo::builder("id", "name").build()) - .subcomponent(&Subcomponent::builder("name").build()) - .build(), - ); - series.start().await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_element() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_1", - "value": 60 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 1 - } - } - }), - json_step_complete(7), - json_run_pass(8), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - series.add_measurement(60.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_element_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_1", - "value": 60 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "series_1", - "value": 70 - } - } - }), - json!({ - "sequenceNumber": 7, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "series_1", - "value": 80 - } - } - }), - json!({ - "sequenceNumber": 8, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 3 - } - } - }), - json_step_complete(9), - json_run_pass(10), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - // add more than one element to check the index increments correctly - series.add_measurement(60.into()).await?; - series.add_measurement(70.into()).await?; - series.add_measurement(80.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_1", - "metadata": { - "key": "value" - }, - "value": 60 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 1 - } - } - }), - json_step_complete(7), - json_run_pass(8), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_1", - "metadata": {"key": "value"}, - "value": 60 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "series_1", - "metadata": {"key2": "value2"}, - "value": 70 - } - } - }), - json!({ - "sequenceNumber": 7, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "series_1", - "metadata": {"key3": "value3"}, - "value": 80 - } - } - }), - json!({ - "sequenceNumber": 8, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 3 - } - } - }), - json_step_complete(9), - json_run_pass(10), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series.start().await?; - // add more than one element to check the index increments correctly - series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) - .await?; - series - .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) - .await?; - series - .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_step_with_measurement_series_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_1", - "name": "name" - } - } - }), - json!({ - "sequenceNumber": 5, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_1", - "value": 60 - } - } - }), - json!({ - "sequenceNumber": 6, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "series_1", - "value": 70 - } - } - }), - json!({ - "sequenceNumber": 7, - "testStepArtifact": { - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "series_1", - "value": 80 - } - } - }), - json!({ - "sequenceNumber": 8, - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_1", - "totalCount": 3 - } - } - }), - json_step_complete(9), - json_run_pass(10), - ]; - - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series - .scope(|s| async { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; - - Ok(()) - }) - .await?; - - Ok(()) - } - .boxed() - }) - .await - } - - #[tokio::test] - async fn test_testrun_instantiation_with_new() -> Result<()> { - let test_run = TestRun::new("run_name", "dut_id", "1.0"); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; - - assert_eq!(test_run.dut.to_spec().id, "dut_id"); - Ok(()) - } - - #[tokio::test] - async fn test_testrun_metadata() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "sequenceNumber": 2, - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id" - }, - "metadata": {"key": "value"}, - "name": "run_name", - "parameters": {}, - "version": "1.0" - } - } - }), - json_run_pass(3), - ]; - - check_output(&expected, |run_builder| async { - let run = run_builder.add_metadata("key", "value".into()).build(); - run.start().await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_testrun_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "commandLine": "cmd_line", - "dutInfo": { - "dutInfoId": "dut_id" - }, - "metadata": { - "key": "value", - "key2": "value2" - }, - "name": "run_name", - "parameters": { - "key": "value" - }, - "version": "1.0" - } - }, - "sequenceNumber": 2 - }), - json_run_pass(3), - ]; - - check_output(&expected, |run_builder| async { - let run = run_builder - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_parameter("key", "value".into()) - .command_line("cmd_line") - .build(); - run.start().await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await - } -} diff --git a/tests/output/main.rs b/tests/output/main.rs new file mode 100644 index 0000000..3949d09 --- /dev/null +++ b/tests/output/main.rs @@ -0,0 +1,7 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +mod runner; diff --git a/tests/output/runner.rs b/tests/output/runner.rs new file mode 100644 index 0000000..72bee77 --- /dev/null +++ b/tests/output/runner.rs @@ -0,0 +1,1291 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use assert_json_diff::assert_json_include; +use futures::future::BoxFuture; +use futures::future::Future; +use futures::FutureExt; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output as tv; +use tv::{ + Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, + SoftwareInfo, Subcomponent, TestResult, TestRun, TestRunBuilder, TestRunOutcome, TestStatus, + TestStep, Validator, ValidatorType, +}; + +fn json_schema_version() -> serde_json::Value { + // seqno for schemaVersion is always 1 + json!({ + "schemaVersion": { + "major": tv::SPEC_VERSION.0, + "minor": tv::SPEC_VERSION.1 + }, + "sequenceNumber": 1 + }) +} + +fn json_run_default_start() -> serde_json::Value { + // seqno for the default test run start is always 2 + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "name": "run_name", + "parameters": {}, + "version": "1.0" + } + }, + "sequenceNumber": 2 + }) +} + +fn json_run_pass(seqno: i32) -> serde_json::Value { + json!({ + "testRunArtifact": { + "testRunEnd": { + "result": "PASS", + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno + }) +} + +fn json_step_default_start() -> serde_json::Value { + // seqno for the default test run start is always 3 + json!({ + "testStepArtifact": { + "testStepStart": { + "name": "first step" + } + }, + "sequenceNumber": 3 + }) +} + +fn json_step_complete(seqno: i32) -> serde_json::Value { + json!({ + "testStepArtifact": { + "testStepEnd": { + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno + }) +} + +async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + R: Future>, + F: FnOnce(TestRunBuilder) -> R, +{ + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let run_builder = TestRun::builder("run_name", &dut, "1.0").config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .build(), + ); + + // run the main test closure + test_fn(run_builder).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} + +async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a TestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, +{ + check_output(expected, |run_builder| async { + let run = run_builder.build(); + + run.start().await?; + test_fn(&run).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} + +async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, +{ + check_output(expected, |run_builder| async { + let run = run_builder.build(); + run.start().await?; + + let step = run.step("first step")?; + step.start().await?; + test_fn(&step).await?; + step.end(TestStatus::Complete).await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_start_and_end() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(3), + ]; + + check_output_run(&expected, |_| async { Ok(()) }.boxed()).await +} + +#[tokio::test] +async fn test_testrun_with_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output_run(&expected, |run| { + async { + run.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output_run(&expected, |run| { + async { + run.log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output_run(&expected, |run| { + async { run.error("symptom").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output_run(&expected, |run| { + async { run.error_with_msg("symptom", "Error message").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds":[{ + "name": "name", + "softwareInfoId": "id", + }], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output_run(&expected, |run| { + async { + run.error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "First message", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + check_output(&expected, |run_builder| async { + let run = run_builder.build(); + + run.scope(|r| async { + r.log(LogSeverity::Info, "First message").await?; + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + }) + .await?; + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_step() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |_| async { Ok(()) }.boxed()).await +} + +#[tokio::test] +async fn test_testrun_step_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 4 + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.error("symptom").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.error_with_msg("symptom", "Error message").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds":[{ + "name": "name", + "softwareInfoId": "id" + }], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_scope_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_run(&expected, |run| { + async { + run.step("first step")? + .scope(|s| async { + s.log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + Ok(TestStatus::Complete) + }) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurement": { + "name": "name", "value": 50 + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + step.add_measurement("name", 50.into()).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurement": { + "hardwareInfoId": "id", + "metadata": { + "key": "value" + }, + "name": "name", + "subcomponent": { + "name": "name" + }, + "validators":[{ + "type": "EQUAL", + "value": 30 + }], + "value": 50 + } + } + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |step| { + async { + let measurement = Measurement::builder("name", 50.into()) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_metadata("key", "value".into()) + .subcomponent(&Subcomponent::builder("name").build()) + .build(); + step.add_measurement_with_details(&measurement).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": + "series_1", + "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_multiple_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 0 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_2", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_2", + "totalCount": 0 + } + } + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.end().await?; + + let series_2 = step.measurement_series("name"); + series_2.start().await?; + series_2.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_id", "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |step| { + async { + let series = step + .measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + series.start().await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_with_details_and_start_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "hardwareInfoId": { + "hardwareInfoId": "id", + "name": "name" + }, + "measurementSeriesId": "series_id", + "metadata": { + "key": "value" + }, + "name": "name", + "subComponent": { + "name": "name" + }, + "validators":[{ + "type": "EQUAL", + "value": 30 + }] + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", + "totalCount": 0 + } + } + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series_with_details( + MeasurementSeriesStart::builder("name", "series_id") + .add_metadata("key", "value".into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .subcomponent(&Subcomponent::builder("name").build()) + .build(), + ); + series.start().await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 1 + } + } + }), + json_step_complete(7), + json_run_pass(8), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series.add_measurement(60.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series.add_measurement(60.into()).await?; + series.add_measurement(70.into()).await?; + series.add_measurement(80.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "metadata": { + "key": "value" + }, + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 1 + } + } + }), + json_step_complete(7), + json_run_pass(8), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + series + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "metadata": {"key": "value"}, + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "metadata": {"key2": "value2"}, + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "metadata": {"key3": "value3"}, + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series.start().await?; + // add more than one element to check the index increments correctly + series + .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .await?; + series + .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) + .await?; + series + .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "sequenceNumber": 4, + "testStepArtifact": { + "measurementSeriesStart": { + "measurementSeriesId": "series_1", + "name": "name" + } + } + }), + json!({ + "sequenceNumber": 5, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_1", + "value": 60 + } + } + }), + json!({ + "sequenceNumber": 6, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_1", + "value": 70 + } + } + }), + json!({ + "sequenceNumber": 7, + "testStepArtifact": { + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_1", + "value": 80 + } + } + }), + json!({ + "sequenceNumber": 8, + "testStepArtifact": { + "measurementSeriesEnd": { + "measurementSeriesId": "series_1", + "totalCount": 3 + } + } + }), + json_step_complete(9), + json_run_pass(10), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.measurement_series("name"); + series + .scope(|s| async { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; + + Ok(()) + }) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_instantiation_with_new() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(3), + ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let test_run = TestRun::new("run_name", "dut_id", "1.0"); + test_run.start().await?; + test_run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} + +#[tokio::test] +async fn test_testrun_metadata() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "sequenceNumber": 2, + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "metadata": {"key": "value"}, + "name": "run_name", + "parameters": {}, + "version": "1.0" + } + } + }), + json_run_pass(3), + ]; + + check_output(&expected, |run_builder| async { + let run = run_builder.add_metadata("key", "value".into()).build(); + run.start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "commandLine": "cmd_line", + "dutInfo": { + "dutInfoId": "dut_id" + }, + "metadata": { + "key": "value", + "key2": "value2" + }, + "name": "run_name", + "parameters": { + "key": "value" + }, + "version": "1.0" + } + }, + "sequenceNumber": 2 + }), + json_run_pass(3), + ]; + + check_output(&expected, |run_builder| async { + let run = run_builder + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_parameter("key", "value".into()) + .command_line("cmd_line") + .build(); + run.start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} From b3c6a856f94c3823d710aa0d73783cda025f6151 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 3 Oct 2024 00:50:54 +0100 Subject: [PATCH 07/96] add gh actions for checking fmt and clippy - fix some clippy warnings Signed-off-by: mimir-d --- .github/workflows/check.yaml | 52 ++++++++++++++++++++++++++++++++++++ src/output/emitters.rs | 1 + src/output/models.rs | 1 + src/output/objects.rs | 2 ++ 4 files changed, 56 insertions(+) create mode 100644 .github/workflows/check.yaml diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml new file mode 100644 index 0000000..9cbfec5 --- /dev/null +++ b/.github/workflows/check.yaml @@ -0,0 +1,52 @@ +name: check + +on: + push: + branches: [main] + pull_request: + +# only read-only for GITHUB_TOKEN +permissions: + contents: read + +# cancel old jobs since their results will be discarded anyway +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + fmt: + runs-on: ubuntu-latest + name: stable / fmt + steps: + - uses: actions/checkout@v4 + - name: Install stable rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt + - name: cargo fmt --check + run: cargo fmt --check + + clippy: + runs-on: ubuntu-latest + name: ${{ matrix.toolchain }} / clippy + permissions: + contents: read + checks: write + strategy: + fail-fast: false + matrix: + # Get early warning of new lints which are regularly introduced in beta channels. + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + - name: Install ${{ matrix.toolchain }} rust + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + components: clippy + - name: cargo clippy + uses: giraffate/clippy-action@v1 + with: + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/src/output/emitters.rs b/src/output/emitters.rs index 733bdc6..f33bb71 100644 --- a/src/output/emitters.rs +++ b/src/output/emitters.rs @@ -71,6 +71,7 @@ impl BufferWriter { #[derive(Debug, Clone)] pub struct StdoutWriter {} +#[allow(clippy::new_without_default)] impl StdoutWriter { pub fn new() -> Self { StdoutWriter {} diff --git a/src/output/models.rs b/src/output/models.rs index fef3088..20343f6 100644 --- a/src/output/models.rs +++ b/src/output/models.rs @@ -60,6 +60,7 @@ pub enum OutputArtifactDescendant { TestStepArtifact(TestStepArtifactSpec), } +#[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] diff --git a/src/output/objects.rs b/src/output/objects.rs index 4b21a61..8aa115c 100644 --- a/src/output/objects.rs +++ b/src/output/objects.rs @@ -20,6 +20,7 @@ pub struct SchemaVersion { minor: i8, } +#[allow(clippy::new_without_default)] impl SchemaVersion { pub fn new() -> SchemaVersion { SchemaVersion { @@ -275,6 +276,7 @@ pub struct TestRunEndBuilder { result: models::TestResult, } +#[allow(clippy::new_without_default)] impl TestRunEndBuilder { pub fn new() -> TestRunEndBuilder { TestRunEndBuilder { From ffc1ae63b3810e522ab214f2552466e4db0b0e45 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 3 Oct 2024 15:54:46 +0100 Subject: [PATCH 08/96] add required test on multiple os Signed-off-by: mimir-d --- .github/workflows/test.yaml | 47 +++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 .github/workflows/test.yaml diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..7fb8bda --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,47 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +# only read-only for GITHUB_TOKEN +permissions: + contents: read + +# cancel old jobs since their results will be discarded anyway +concurrency: + group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} + cancel-in-progress: true + +jobs: + required: + runs-on: ubuntu-latest + name: ubuntu / ${{ matrix.toolchain }} + strategy: + matrix: + # run on stable and beta to ensure that tests won't break on the next version of the rust + # toolchain + toolchain: [stable, beta] + steps: + - uses: actions/checkout@v4 + - name: Install ${{ matrix.toolchain }} + uses: dtolnay/rust-toolchain@master + with: + toolchain: ${{ matrix.toolchain }} + - name: cargo test --locked + run: cargo test --locked --all-features + os-check: + runs-on: ${{ matrix.os }} + name: ${{ matrix.os }} / stable + strategy: + fail-fast: false + matrix: + # ubuntu-latest is covered in `required` + os: [macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo test + run: cargo test --locked --all-features --all-targets \ No newline at end of file From f2f9fffc60003a4f6d3e829cdaca1a530161b31e Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 3 Oct 2024 16:54:31 +0100 Subject: [PATCH 09/96] add llvm-cov test - remove some Deserialize impls because they're not needed right now; this gets the coverage over the 95% threshold Signed-off-by: mimir-d --- .github/codecov.yaml | 15 ++++++++ .github/workflows/test.yaml | 23 ++++++++++++- src/output/models.rs | 68 ++++++++++++++++++------------------- 3 files changed, 71 insertions(+), 35 deletions(-) create mode 100644 .github/codecov.yaml diff --git a/.github/codecov.yaml b/.github/codecov.yaml new file mode 100644 index 0000000..063f879 --- /dev/null +++ b/.github/codecov.yaml @@ -0,0 +1,15 @@ +# ref: https://docs.codecov.com/docs/codecovyml-reference +coverage: + range: 95..100 + round: down + precision: 1 + status: + # ref: https://docs.codecov.com/docs/commit-status + project: + default: + # Avoid false negatives + threshold: 1% + +comment: + layout: "condensed_header, condensed_files" + require_changes: true \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 7fb8bda..f1f5f6b 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -44,4 +44,25 @@ jobs: - name: Install stable uses: dtolnay/rust-toolchain@stable - name: cargo test - run: cargo test --locked --all-features --all-targets \ No newline at end of file + run: cargo test --locked --all-features --all-targets + coverage: + runs-on: ubuntu-latest + name: ubuntu / stable / coverage + steps: + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + - name: cargo install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: cargo llvm-cov + run: cargo llvm-cov --locked --all-features --lcov --output-path lcov.info + - name: Record Rust version + run: echo "RUSTVER=$(rustc --version)" >> "$GITHUB_ENV" + - name: Upload to codecov.io + uses: codecov/codecov-action@v4 + with: + fail_ci_if_error: true + token: ${{ secrets.CODECOV_TOKEN }} + env_vars: OS,RUSTVER \ No newline at end of file diff --git a/src/output/models.rs b/src/output/models.rs index 20343f6..48a2493 100644 --- a/src/output/models.rs +++ b/src/output/models.rs @@ -38,7 +38,7 @@ mod rfc3339_format { } } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestRunArtifactDescendant { #[serde(rename = "testRunStart")] TestRunStart(TestRunStartSpec), @@ -50,7 +50,7 @@ pub enum TestRunArtifactDescendant { Error(ErrorSpec), } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub enum OutputArtifactDescendant { #[serde(rename = "schemaVersion")] SchemaVersion(SchemaVersionSpec), @@ -61,7 +61,7 @@ pub enum OutputArtifactDescendant { } #[allow(clippy::large_enum_variant)] -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] TestStepStart(TestStepStartSpec), @@ -87,7 +87,7 @@ pub enum TestStepArtifactDescendant { Extension(ExtensionSpec), } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub enum ValidatorType { #[serde(rename = "EQUAL")] Equal, @@ -111,7 +111,7 @@ pub enum ValidatorType { NotInSet, } -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub enum SubcomponentType { #[serde(rename = "UNSPECIFIED")] Unspecified, @@ -127,7 +127,7 @@ pub enum SubcomponentType { Connector, } -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub enum ExtensionContentType { #[serde(rename = "float")] Float(f64), @@ -143,7 +143,7 @@ pub enum ExtensionContentType { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis/$defs/type -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub enum DiagnosisType { #[serde(rename = "PASS")] Pass, @@ -157,7 +157,7 @@ pub enum DiagnosisType { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststatus /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_status.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStatus -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testStatus")] pub enum TestStatus { #[serde(rename = "COMPLETE")] @@ -172,7 +172,7 @@ pub enum TestStatus { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testresult /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd/$defs/testResult -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testResult")] pub enum TestResult { #[serde(rename = "PASS")] @@ -186,7 +186,7 @@ pub enum TestResult { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#severity /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log/$defs/severity -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] pub enum LogSeverity { #[serde(rename = "DEBUG")] Debug, @@ -204,7 +204,7 @@ pub enum LogSeverity { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwaretype /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo/properties/softwareType -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareType")] pub enum SoftwareType { #[serde(rename = "UNSPECIFIED")] @@ -217,7 +217,7 @@ pub enum SoftwareType { Application, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Clone)] pub struct OutputArtifactSpec { #[serde(flatten)] pub descendant: OutputArtifactDescendant, @@ -234,7 +234,7 @@ pub struct OutputArtifactSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/root.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "schemaVersion")] pub struct SchemaVersionSpec { #[serde(rename = "major")] @@ -248,7 +248,7 @@ pub struct SchemaVersionSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-run-artifacts /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_artifact.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunArtifact -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifactSpec { #[serde(flatten)] pub descendant: TestRunArtifactDescendant, @@ -259,7 +259,7 @@ pub struct TestRunArtifactSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_start.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunStart")] pub struct TestRunStartSpec { #[serde(rename = "name")] @@ -281,7 +281,7 @@ pub struct TestRunStartSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#dutinfo /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo -#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "dutInfo")] pub struct DutInfoSpec { #[serde(rename = "dutInfoId")] @@ -303,7 +303,7 @@ pub struct DutInfoSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#platforminfo /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo -#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "platformInfo")] pub struct PlatformInfoSpec { #[serde(rename = "info")] @@ -315,7 +315,7 @@ pub struct PlatformInfoSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwareinfo /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareInfo")] pub struct SoftwareInfoSpec { #[serde(rename = "softwareInfoId")] @@ -337,7 +337,7 @@ pub struct SoftwareInfoSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#hardwareinfo /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo -#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "hardwareInfo")] pub struct HardwareInfoSpec { #[serde(rename = "hardwareInfoId")] @@ -371,7 +371,7 @@ pub struct HardwareInfoSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunEnd")] pub struct TestRunEndSpec { #[serde(rename = "status")] @@ -386,7 +386,7 @@ pub struct TestRunEndSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/error.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error -#[derive(Debug, Serialize, Deserialize, Default, Clone, PartialEq)] +#[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] pub struct ErrorSpec { #[serde(rename = "symptom")] @@ -405,7 +405,7 @@ pub struct ErrorSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "log")] pub struct LogSpec { #[serde(rename = "severity")] @@ -421,7 +421,7 @@ pub struct LogSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#sourcelocation /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/source_location.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation -#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] +#[derive(Debug, Serialize, Clone, Default, PartialEq)] #[serde(rename = "sourceLocation")] pub struct SourceLocationSpec { #[serde(rename = "file")] @@ -435,7 +435,7 @@ pub struct SourceLocationSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestStepArtifactSpec { #[serde(flatten)] pub descendant: TestStepArtifactDescendant, @@ -446,7 +446,7 @@ pub struct TestStepArtifactSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_start.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepStart")] pub struct TestStepStartSpec { #[serde(rename = "name")] @@ -458,7 +458,7 @@ pub struct TestStepStartSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_end.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepEnd")] pub struct TestStepEndSpec { #[serde(rename = "status")] @@ -470,7 +470,7 @@ pub struct TestStepEndSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] pub struct MeasurementSpec { #[serde(rename = "name")] @@ -494,7 +494,7 @@ pub struct MeasurementSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#validator /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/validator.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] pub struct ValidatorSpec { #[serde(rename = "name")] @@ -512,7 +512,7 @@ pub struct ValidatorSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#subcomponent /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/subcomponent.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent -#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] +#[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] pub struct SubcomponentSpec { #[serde(rename = "type")] @@ -532,7 +532,7 @@ pub struct SubcomponentSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_start.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] pub struct MeasurementSeriesStartSpec { #[serde(rename = "name")] @@ -556,7 +556,7 @@ pub struct MeasurementSeriesStartSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_end.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesEnd")] pub struct MeasurementSeriesEndSpec { #[serde(rename = "measurementSeriesId")] @@ -590,7 +590,7 @@ pub struct MeasurementSeriesElementSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "diagnosis")] pub struct DiagnosisSpec { #[serde(rename = "verdict")] @@ -612,7 +612,7 @@ pub struct DiagnosisSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/file.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] pub struct FileSpec { #[serde(rename = "name")] @@ -634,7 +634,7 @@ pub struct FileSpec { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "extension")] pub struct ExtensionSpec { #[serde(rename = "name")] From a261f9f6a4f64208d6692aadc41a5d6c4e00f87a Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 3 Oct 2024 16:57:15 +0100 Subject: [PATCH 10/96] add workflow_dispatch to all actions - to enable some debugging from the github UI, add the new on: trigger Signed-off-by: mimir-d --- .github/workflows/check.yaml | 1 + .github/workflows/test.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 9cbfec5..86ee7ba 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -4,6 +4,7 @@ on: push: branches: [main] pull_request: + workflow_dispatch: # only read-only for GITHUB_TOKEN permissions: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f1f5f6b..d677081 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -4,6 +4,7 @@ on: push: branches: [main] pull_request: + workflow_dispatch: # only read-only for GITHUB_TOKEN permissions: From 4de99b2f8b2cfc6d55a0a54309c5123ae834e8b1 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 3 Oct 2024 18:03:39 +0100 Subject: [PATCH 11/96] improve coverage by re-adding the test that uses a filesystem - reduce max coverage 100 -> 98, since it's likely that some statements will be missed in the coverage - add assert_fs to provide a filesystem for testing, but disable that test for anything else other than coverage Signed-off-by: mimir-d --- .github/codecov.yaml | 2 +- Cargo.lock | 261 +++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 7 ++ README.md | 6 +- tests/output/runner.rs | 60 ++++++++++ 5 files changed, 334 insertions(+), 2 deletions(-) diff --git a/.github/codecov.yaml b/.github/codecov.yaml index 063f879..cce915d 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -1,6 +1,6 @@ # ref: https://docs.codecov.com/docs/codecovyml-reference coverage: - range: 95..100 + range: 95..98 round: down precision: 1 status: diff --git a/Cargo.lock b/Cargo.lock index a1fd678..f289b38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -41,6 +41,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + [[package]] name = "anyhow" version = "1.0.89" @@ -57,6 +63,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "assert_fs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" +dependencies = [ + "anstyle", + "doc-comment", + "globwalk", + "predicates", + "predicates-core", + "predicates-tree", + "tempfile", +] + [[package]] name = "async-stream" version = "0.3.6" @@ -100,6 +121,22 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bstr" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "bumpalo" version = "3.16.0" @@ -177,6 +214,31 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + [[package]] name = "derive_more" version = "1.0.0" @@ -199,6 +261,43 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + +[[package]] +name = "float-cmp" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4" +dependencies = [ + "num-traits", +] + [[package]] name = "futures" version = "0.3.30" @@ -294,6 +393,30 @@ version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +[[package]] +name = "globset" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[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 = "iana-time-zone" version = "0.1.61" @@ -317,6 +440,22 @@ dependencies = [ "cc", ] +[[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 = "itoa" version = "1.0.11" @@ -338,6 +477,12 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "log" version = "0.4.22" @@ -359,6 +504,12 @@ dependencies = [ "adler2", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "num-traits" version = "0.2.19" @@ -383,10 +534,12 @@ version = "0.1.0" dependencies = [ "anyhow", "assert-json-diff", + "assert_fs", "chrono", "chrono-tz", "derive_more", "futures", + "predicates", "serde", "serde_json", "thiserror", @@ -468,6 +621,36 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "predicates" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" + +[[package]] +name = "predicates-tree" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.86" @@ -536,12 +719,34 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[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 = "serde" version = "1.0.210" @@ -606,6 +811,25 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" +dependencies = [ + "cfg-if", + "fastrand", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "termtree" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" + [[package]] name = "thiserror" version = "1.0.64" @@ -691,6 +915,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[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 = "wasm-bindgen" version = "0.2.93" @@ -746,6 +980,15 @@ version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +[[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]] name = "windows-core" version = "0.52.0" @@ -755,6 +998,24 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" diff --git a/Cargo.toml b/Cargo.toml index c7b7ca0..23c89ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,5 +27,12 @@ tokio = { version = "1.40.0", features = [ [dev-dependencies] anyhow = "1.0.89" assert-json-diff = "2.0.2" +assert_fs = "1.1.2" futures = "0.3.30" +predicates = "3.1.2" tokio-test = "0.4.4" + +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = [ + 'cfg(coverage,coverage_nightly)', +] } diff --git a/README.md b/README.md index 0a81535..faffc57 100644 --- a/README.md +++ b/README.md @@ -1 +1,5 @@ -# ocp-diag-core-rust \ No newline at end of file +# ocp-diag-core-rust + +[![codecov](https://codecov.io/github/opencomputeproject/ocp-diag-core-rust/graph/badge.svg?token=IJOG0T8XZ3)](https://codecov.io/github/opencomputeproject/ocp-diag-core-rust) + +WIP \ No newline at end of file diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 72bee77..55307e1 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -4,13 +4,16 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::fs; use std::sync::Arc; use anyhow::Result; +use assert_fs::prelude::*; use assert_json_diff::assert_json_include; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; +use predicates::prelude::*; use serde_json::json; use tokio::sync::Mutex; @@ -1198,6 +1201,63 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { .await } +// reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot +// disable the coverage itself, only run this test when in coverage mode because assert_fs +// does ultimately assume there's a real filesystem somewhere +#[cfg(coverage)] +#[tokio::test] +async fn test_config_builder_with_file() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }), + json_run_pass(4), + ]; + + let fs = assert_fs::TempDir::new()?; + let output_file = fs.child("output.jsonl"); + + let dut = DutInfo::builder("dut_id").build(); + + let run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .timezone(chrono_tz::Europe::Rome) + .with_file_output(output_file.path()) + .await? + .build(), + ) + .build(); + + run.scope(|r| async { + r.error_with_msg("symptom", "Error message").await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + }) + .await?; + + output_file.assert(predicate::path::exists()); + let content = fs::read_to_string(output_file.path())?; + + for (idx, entry) in content.lines().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} + #[tokio::test] async fn test_testrun_instantiation_with_new() -> Result<()> { let expected = [ From d3f4bb4fc5c7f39deb46326ec7a3260b90bfa63d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 12:32:27 +0100 Subject: [PATCH 12/96] add StartedTestRun type state - this new type disallows users to misuse the run (eg. by emitting run artifacts when the run hasn't been started yet) - this also shows that there is little value in having a TestRun object around, while not having started, due to the only action possible on it is to start it. It may be beneficial in the future to only allow started objects to exist, instead of having inert objects passed around. This is a slight change in usage, but not very consequential. - also fix macros tests since this new StartedTestRun pattern highlighted that they were emitting invalid messages Signed-off-by: mimir-d --- src/output/macros.rs | 236 ++++++++++--------- src/output/runner.rs | 523 ++++++++++++++++++++--------------------- tests/output/runner.rs | 111 ++++----- 3 files changed, 444 insertions(+), 426 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index 39ab47a..9f49d03 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -27,8 +27,7 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_error!(test_run, "symptom"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -44,8 +43,7 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_error!(test_run, "symptom", "Error message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -54,7 +52,7 @@ /// ``` #[macro_export] macro_rules! ocptv_error { - ($runner:expr , $symptom:expr, $msg:expr) => { + ($runner:expr, $symptom:expr, $msg:expr) => { async { $runner .error_with_details( @@ -99,8 +97,7 @@ macro_rules! ocptv_error { /// /// use ocptv::ocptv_log_debug; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0"); -/// test_run.start().await?; +/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; /// ocptv_log_debug!(test_run, "Log message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -210,23 +207,25 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_error!(test_run, "symptom", "Error message").await?; + ocptv_error!(run, "symptom", "Error message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing error log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -252,23 +251,25 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_error!(test_run, "symptom").await?; + ocptv_error!(run, "symptom").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing error log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -295,23 +296,25 @@ mod tests { "severity": "DEBUG" } }, - "sequenceNumber":1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_debug!(test_run, "log message").await?; + ocptv_log_debug!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -338,23 +341,25 @@ mod tests { "severity": "INFO" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_info!(test_run, "log message").await?; + ocptv_log_info!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -382,23 +387,25 @@ mod tests { "severity": "WARNING" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_warning!(test_run, "log message").await?; + ocptv_log_warning!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -425,23 +432,25 @@ mod tests { "severity": "ERROR" } }, - "sequenceNumber":1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_error!(test_run, "log message").await?; + ocptv_log_error!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -468,23 +477,25 @@ mod tests { "severity": "FATAL" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - ocptv_log_fatal!(test_run, "log message").await?; + ocptv_log_fatal!(run, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -511,25 +522,28 @@ mod tests { "symptom":"symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_error!(step, "symptom", "Error message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -555,25 +569,27 @@ mod tests { "symptom": "symptom" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_error!(step, "symptom").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the error message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -600,24 +616,26 @@ mod tests { "severity": "DEBUG" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_debug!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -644,24 +662,26 @@ mod tests { "severity": "INFO" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_info!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -688,24 +708,26 @@ mod tests { "severity":"WARNING" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_warning!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -732,24 +754,26 @@ mod tests { "severity": "ERROR" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_error!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); @@ -776,24 +800,26 @@ mod tests { "severity": "FATAL" } }, - "sequenceNumber": 1 + "sequenceNumber": 3 }); let dut = DutInfo::builder("dut_id").build(); let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build(); + .build() + .start() + .await?; - let step = test_run.step("step_name")?; + let step = run.step("step_name")?; ocptv_log_fatal!(step, "log message").await?; let actual = serde_json::from_str::( - buffer + &buffer .lock() .await - .first() - .ok_or(anyhow!("Buffer is empty"))?, + .first_chunk::<3>() + .ok_or(anyhow!("Buffer is missing the log message"))?[2], )?; assert_json_include!(actual: actual.clone(), expected: &expected); diff --git a/src/output/runner.rs b/src/output/runner.rs index 976b1f4..547d644 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -109,10 +109,8 @@ impl TestState { } /// The main diag test run. -/// This object describes a single run instance of the diag, and therefore drives the test session. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart - +/// This object describes a single run instance of the diag, and therefore drives the test session. pub struct TestRun { name: String, version: String, @@ -145,7 +143,7 @@ impl TestRun { /// ```rust /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// ``` pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { let dut = objects::DutInfo::new(dut_id); @@ -163,13 +161,13 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// run.start().await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { + pub async fn start(self) -> Result { let version = objects::SchemaVersion::new(); self.state .lock() @@ -199,9 +197,178 @@ impl TestRun { .emitter .emit(&start.to_artifact()) .await?; - Ok(()) + + Ok(StartedTestRun { run: self }) + } + + // disabling this for the moment so we don't publish api that's unusable. + // see: https://github.com/rust-lang/rust/issues/70263 + // + // /// Builds a scope in the [`TestRun`] object, taking care of starting and + // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`TestRun`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + // /// run.scope(|r| async { + // /// r.log(LogSeverity::Info, "First message").await?; + // /// Ok(TestRunOutcome { + // /// status: TestStatus::Complete, + // /// result: TestResult::Pass, + // /// }) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // for<'a> F: Fut2<'a, R>, + // { + // let run = self.start().await?; + // let outcome = func(&run).await?; + // run.end(outcome.status, outcome.result).await?; + + // Ok(()) + // } +} + +/// Builder for the [`TestRun`] object. +pub struct TestRunBuilder { + name: String, + dut: objects::DutInfo, + version: String, + parameters: Map, + command_line: String, + metadata: Option>, + config: Option, +} + +impl TestRunBuilder { + pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + Self { + name: name.to_string(), + dut: dut.clone(), + version: version.to_string(), + parameters: Map::new(), + command_line: env::args().collect::>()[1..].join(" "), + metadata: None, + config: None, + } + } + + /// Adds a user defined parameter to the future [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_parameter("param1", "value1".into()) + /// .build(); + /// ``` + pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { + self.parameters.insert(key.to_string(), value.clone()); + self + } + + /// Adds the command line used to run the test session to the future + /// [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .command_line("my_diag --arg value") + /// .build(); + /// ``` + pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + self.command_line = cmd.to_string(); + self + } + + /// Adds the configuration for the test session to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .config(Config::builder().build()) + /// .build(); + /// ``` + pub fn config(mut self, value: Config) -> TestRunBuilder { + self.config = Some(value); + self + } + + /// Adds user defined metadata to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_metadata("meta1", "value1".into()) + /// .build(); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRun { + let config = self.config.unwrap_or(Config::builder().build()); + let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let state = TestState::new(emitter); + TestRun { + name: self.name, + dut: self.dut, + version: self.version, + parameters: self.parameters, + command_line: self.command_line, + metadata: self.metadata, + state: Arc::new(Mutex::new(state)), + } } +} + +/// A test run that was started. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +pub struct StartedTestRun { + run: TestRun, +} +impl StartedTestRun { /// Ends the test run. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend @@ -212,9 +379,8 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -228,48 +394,10 @@ impl TestRun { .status(status) .result(result) .build(); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - /// Builds a scope in the [`TestRun`] object, taking care of starting and - /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. - /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`TestRun`], since it guarantees - /// all the messages are emitted between the start and end messages, the order - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.scope(|r| async { - /// r.log(LogSeverity::Info, "First message").await?; - /// Ok(TestRunOutcome { - /// status: TestStatus::Complete, - /// result: TestResult::Pass, - /// }) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a TestRun) -> R, - { - self.start().await?; - let outcome = func(self).await?; - self.end(outcome.status, outcome.result).await?; + let emitter = &self.run.state.lock().await.emitter; + + emitter.emit(&end.to_artifact()).await?; Ok(()) } @@ -285,13 +413,12 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.log( + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log( /// LogSeverity::Info, /// "This is a log message with INFO severity", /// ).await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -302,10 +429,10 @@ impl TestRun { msg: &str, ) -> Result<(), emitters::WriterError> { let log = objects::Log::builder(msg).severity(severity).build(); - self.state - .lock() - .await - .emitter + + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -322,24 +449,22 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.log_with_details( + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), /// ).await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -356,20 +481,18 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error("symptom").await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error("symptom").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).build(); - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -387,10 +510,9 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error_with_msg("symptom", "error messasge").await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_msg("symptom", "error messasge").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -401,10 +523,9 @@ impl TestRun { msg: &str, ) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).message(msg).build(); - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) @@ -421,16 +542,15 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// test_run.error_with_details( + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) /// .build(), /// ).await?; - /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); @@ -439,136 +559,16 @@ impl TestRun { &self, error: &objects::Error, ) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter + let emitter = &self.run.state.lock().await.emitter; + + emitter .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) .await?; Ok(()) } pub fn step(&self, name: &str) -> Result { - Ok(TestStep::new(name, self.state.clone())) - } -} - -/// Builder for the [`TestRun`] object. -pub struct TestRunBuilder { - name: String, - dut: objects::DutInfo, - version: String, - parameters: Map, - command_line: String, - metadata: Option>, - config: Option, -} - -impl TestRunBuilder { - pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { - Self { - name: name.to_string(), - dut: dut.clone(), - version: version.to_string(), - parameters: Map::new(), - command_line: env::args().collect::>()[1..].join(" "), - metadata: None, - config: None, - } - } - - /// Adds a user defined parameter to the future [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_parameter("param1", "value1".into()) - /// .build(); - /// ``` - pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { - self.parameters.insert(key.to_string(), value.clone()); - self - } - - /// Adds the command line used to run the test session to the future - /// [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .command_line("my_diag --arg value") - /// .build(); - /// ``` - pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { - self.command_line = cmd.to_string(); - self - } - - /// Adds the configuration for the test session to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .config(Config::builder().build()) - /// .build(); - /// ``` - pub fn config(mut self, value: Config) -> TestRunBuilder { - self.config = Some(value); - self - } - - /// Adds user defined metadata to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let test_run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_metadata("meta1", "value1".into()) - /// .build(); - /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRun { - let config = self.config.unwrap_or(Config::builder().build()); - let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); - let state = TestState::new(emitter); - TestRun { - name: self.name, - dut: self.dut, - version: self.version, - parameters: self.parameters, - command_line: self.command_line, - metadata: self.metadata, - state: Arc::new(Mutex::new(state)), - } + Ok(TestStep::new(name, self.run.state.clone())) } } @@ -600,10 +600,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// # Ok::<(), WriterError>(()) @@ -630,10 +629,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.end(TestStatus::Complete).await?; /// @@ -664,10 +662,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("first step")?; + /// let step = run.step("first step")?; /// step.scope(|s| async { /// s.log( /// LogSeverity::Info, @@ -702,10 +699,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.log( /// LogSeverity::Info, @@ -724,10 +720,9 @@ impl TestStep { /// /// use ocptv::ocptv_log_info; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; /// step.end(TestStatus::Complete).await?; @@ -761,10 +756,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// step.log_with_details( /// &Log::builder("This is a log message with INFO severity") @@ -798,8 +792,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error("symptom").await?; /// step.end(TestStatus::Complete).await?; @@ -816,10 +811,9 @@ impl TestStep { /// /// use ocptv::ocptv_error; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_error!(step, "symptom").await?; /// step.end(TestStatus::Complete).await?; @@ -850,8 +844,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error_with_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; @@ -868,9 +863,9 @@ impl TestStep { /// /// use ocptv::ocptv_error; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// ocptv_error!(step, "symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; @@ -904,8 +899,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.error_with_details( /// &Error::builder("symptom") @@ -942,8 +938,9 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name")?; /// step.start().await?; /// step.add_measurement("name", 50.into()).await?; /// step.end(TestStatus::Complete).await?; @@ -978,9 +975,8 @@ impl TestStep { /// # use ocptv::output::*; /// /// let hwinfo = HardwareInfo::builder("id", "fan").build(); - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; - /// step.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// /// let measurement = Measurement::builder("name", 5000.into()) /// .hardware_info(&hwinfo) @@ -1019,8 +1015,8 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// step.start().await?; /// let series = step.measurement_series("name"); /// @@ -1049,8 +1045,8 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// let step = test_run.step("step_name")?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name")?; /// step.start().await?; /// let series = /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); @@ -1117,10 +1113,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1149,10 +1144,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1186,10 +1180,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1227,10 +1220,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); @@ -1276,10 +1268,9 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let test_run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// test_run.start().await?; + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = test_run.step("step_name")?; + /// let step = run.step("step_name")?; /// step.start().await?; /// /// let series = step.measurement_series("name"); diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 55307e1..242708a 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(unused_imports)] use std::fs; use std::sync::Arc; @@ -20,8 +21,8 @@ use tokio::sync::Mutex; use ocptv::output as tv; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, - SoftwareInfo, Subcomponent, TestResult, TestRun, TestRunBuilder, TestRunOutcome, TestStatus, - TestStep, Validator, ValidatorType, + SoftwareInfo, StartedTestRun, Subcomponent, TestResult, TestRun, TestRunBuilder, + TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, }; fn json_schema_version() -> serde_json::Value { @@ -113,12 +114,12 @@ where async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a TestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, + F: for<'a> FnOnce(&'a StartedTestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, { check_output(expected, |run_builder| async { let run = run_builder.build(); - run.start().await?; + let run = run.start().await?; test_fn(&run).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -132,8 +133,7 @@ where F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, { check_output(expected, |run_builder| async { - let run = run_builder.build(); - run.start().await?; + let run = run_builder.build().start().await?; let step = run.step("first step")?; step.start().await?; @@ -310,39 +310,39 @@ async fn test_testrun_with_error_with_details() -> Result<()> { .await } -#[tokio::test] -async fn test_testrun_with_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "First message", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }), - json_run_pass(4), - ]; - - check_output(&expected, |run_builder| async { - let run = run_builder.build(); - - run.scope(|r| async { - r.log(LogSeverity::Info, "First message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - }) - .await?; - - Ok(()) - }) - .await -} +// #[tokio::test] +// async fn test_testrun_with_scope() -> Result<()> { +// let expected = [ +// json_schema_version(), +// json_run_default_start(), +// json!({ +// "testRunArtifact": { +// "log": { +// "message": "First message", +// "severity": "INFO" +// } +// }, +// "sequenceNumber": 3 +// }), +// json_run_pass(4), +// ]; + +// check_output(&expected, |run_builder| async { +// let run = run_builder.build(); + +// run.scope(|r| async { +// r.log(LogSeverity::Info, "First message").await?; +// Ok(TestRunOutcome { +// status: TestStatus::Complete, +// result: TestResult::Pass, +// }) +// }) +// .await?; + +// Ok(()) +// }) +// .await +// } #[tokio::test] async fn test_testrun_with_step() -> Result<()> { @@ -1235,17 +1235,13 @@ async fn test_config_builder_with_file() -> Result<()> { .await? .build(), ) - .build(); + .build() + .start() + .await?; - run.scope(|r| async { - r.error_with_msg("symptom", "Error message").await?; + run.error_with_msg("symptom", "Error message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - }) - .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; output_file.assert(predicate::path::exists()); let content = fs::read_to_string(output_file.path())?; @@ -1267,9 +1263,8 @@ async fn test_testrun_instantiation_with_new() -> Result<()> { ]; let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let test_run = TestRun::new("run_name", "dut_id", "1.0"); - test_run.start().await?; - test_run.end(TestStatus::Complete, TestResult::Pass).await?; + let run = TestRun::new("run_name", "dut_id", "1.0").start().await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; @@ -1301,8 +1296,12 @@ async fn test_testrun_metadata() -> Result<()> { ]; check_output(&expected, |run_builder| async { - let run = run_builder.add_metadata("key", "value".into()).build(); - run.start().await?; + let run = run_builder + .add_metadata("key", "value".into()) + .build() + .start() + .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) }) @@ -1342,8 +1341,10 @@ async fn test_testrun_builder() -> Result<()> { .add_metadata("key2", "value2".into()) .add_parameter("key", "value".into()) .command_line("cmd_line") - .build(); - run.start().await?; + .build() + .start() + .await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) }) From 5cf3fd572f703f663e66e56e9e2bce29abbd7063 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 13:16:01 +0100 Subject: [PATCH 13/96] reduce the code size for macros - the ocptv_log_* macros were repetitive; make them with another macro Signed-off-by: mimir-d --- src/output/macros.rs | 121 ++++++++++--------------------------------- 1 file changed, 28 insertions(+), 93 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index 9f49d03..983dd7c 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -53,27 +53,20 @@ #[macro_export] macro_rules! ocptv_error { ($runner:expr, $symptom:expr, $msg:expr) => { - async { - $runner - .error_with_details( - &$crate::output::Error::builder($symptom) - .message($msg) - .source(file!(), line!() as i32) - .build(), - ) - .await - } + $runner.error_with_details( + &$crate::output::Error::builder($symptom) + .message($msg) + .source(file!(), line!() as i32) + .build(), + ) }; + ($runner:expr, $symptom:expr) => { - async { - $runner - .error_with_details( - &$crate::output::Error::builder($symptom) - .source(file!(), line!() as i32) - .build(), - ) - .await - } + $runner.error_with_details( + &$crate::output::Error::builder($symptom) + .source(file!(), line!() as i32) + .build(), + ) }; } @@ -97,93 +90,35 @@ macro_rules! ocptv_error { /// /// use ocptv::ocptv_log_debug; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; -/// ocptv_log_debug!(test_run, "Log message"); -/// test_run.end(TestStatus::Complete, TestResult::Pass).await?; +/// let run = TestRun::new("run_name", "my_dut", "1.0").start().await?; +/// ocptv_log_debug!(run, "Log message"); +/// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` -#[macro_export] -macro_rules! ocptv_log_debug { - ($runner:expr, $msg:expr) => { - async { - $runner - .log_with_details( - &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::Debug) - .source(file!(), line!() as i32) - .build(), - ) - .await - } - }; -} - -#[macro_export] -macro_rules! ocptv_log_info { - ($runner:expr, $msg:expr) => { - async { - $runner - .log_with_details( - &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::Info) - .source(file!(), line!() as i32) - .build(), - ) - .await - } - }; -} - -#[macro_export] -macro_rules! ocptv_log_warning { - ($runner:expr, $msg:expr) => { - async { - $runner - .log_with_details( +macro_rules! ocptv_log { + ($name:ident, $severity:ident) => { + #[macro_export] + macro_rules! $name { + ($artifact:expr, $msg:expr) => { + $artifact.log_with_details( &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::Warning) + .severity($crate::output::LogSeverity::$severity) .source(file!(), line!() as i32) .build(), ) - .await + }; } }; } -#[macro_export] -macro_rules! ocptv_log_error { - ($runner:expr, $msg:expr) => { - async { - $runner - .log_with_details( - &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::Error) - .source(file!(), line!() as i32) - .build(), - ) - .await - } - }; -} - -#[macro_export] -macro_rules! ocptv_log_fatal { - ($runner:expr, $msg:expr) => { - async { - $runner - .log_with_details( - &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::Fatal) - .source(file!(), line!() as i32) - .build(), - ) - .await - } - }; -} +ocptv_log!(ocptv_log_debug, Debug); +ocptv_log!(ocptv_log_info, Info); +ocptv_log!(ocptv_log_warning, Warning); +ocptv_log!(ocptv_log_error, Error); +ocptv_log!(ocptv_log_fatal, Fatal); #[cfg(test)] mod tests { From a12011e85a1eb0d8cc954d4adc9079d0528e5a65 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 13:44:02 +0100 Subject: [PATCH 14/96] refactor macros tests - remove a lot of the repetition in these tests - the test cases now only contain the expected output and the exercise, so it makes them a lot easier to read and reason about Signed-off-by: mimir-d --- src/output/macros.rs | 563 +++++++++++-------------------------------- 1 file changed, 135 insertions(+), 428 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index 983dd7c..bd494ab 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -122,6 +122,7 @@ ocptv_log!(ocptv_log_fatal, Fatal); #[cfg(test)] mod tests { + use std::future::Future; use std::sync::Arc; use anyhow::anyhow; @@ -133,19 +134,13 @@ mod tests { use crate::output::objects::*; use crate::output::runner::*; - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { - let expected = json!({ - "testRunArtifact":{ - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }); - + async fn check_output(expected: &serde_json::Value, func: F) -> Result + where + R: Future>, + F: FnOnce(StartedTestRun) -> R, + { let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); let run = TestRun::builder("run_name", &dut, "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) @@ -153,22 +148,34 @@ mod tests { .start() .await?; - ocptv_error!(run, "symptom", "Error message").await?; + func(run).await?; let actual = serde_json::from_str::( &buffer .lock() .await + // first 2 items are schemaVersion, testRunStart .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing error log message"))?[2], + .ok_or(anyhow!("buffer is missing macro output item"))?[2], )?; - assert_json_include!(actual: actual.clone(), expected: &expected); + assert_json_include!(actual: actual.clone(), expected: expected); + + Ok(actual) + } + + async fn check_output_run(expected: &serde_json::Value, key: &str, func: F) -> Result<()> + where + R: Future>, + F: FnOnce(StartedTestRun) -> R, + { + let actual = check_output(expected, func).await?; let source = actual .get("testRunArtifact") .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("error") + .get(key) .ok_or(anyhow!("error key does not exist"))?; + assert_ne!( source.get("sourceLocation"), None, @@ -178,41 +185,26 @@ mod tests { Ok(()) } - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }); - - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_error!(run, "symptom").await?; + async fn check_output_step(expected: &serde_json::Value, key: &str, func: F) -> Result<()> + where + R: Future>, + F: FnOnce(TestStep) -> R, + { + let actual = check_output(expected, |run| async move { + let step = run.step("step_name")?; + // TODO: missing step start here - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing error log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); + func(step).await?; + Ok(()) + }) + .await?; let source = actual - .get("testRunArtifact") + .get("testStepArtifact") .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("error") + .get(key) .ok_or(anyhow!("error key does not exist"))?; + assert_ne!( source.get("sourceLocation"), None, @@ -222,6 +214,43 @@ mod tests { Ok(()) } + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "error", |run| async move { + ocptv_error!(run, "symptom", "Error message").await?; + Ok(()) + }) + .await + } + + #[tokio::test] + async fn test_ocptv_error_macro_with_symptom() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "error", |run| async move { + ocptv_error!(run, "symptom").await?; + Ok(()) + }) + .await + } + #[tokio::test] async fn test_ocptv_log_debug() -> Result<()> { let expected = json!({ @@ -234,37 +263,12 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_log_debug!(run, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); + check_output_run(&expected, "log", |run| async move { + ocptv_log_debug!(run, "log message").await?; - Ok(()) + Ok(()) + }) + .await } #[tokio::test] @@ -279,38 +283,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_log_info!(run, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_run(&expected, "log", |run| async move { + ocptv_log_info!(run, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -325,37 +302,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_log_warning!(run, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_run(&expected, "log", |run| async move { + ocptv_log_warning!(run, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -370,37 +321,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_log_error!(run, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the error message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_run(&expected, "log", |run| async move { + ocptv_log_error!(run, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -415,37 +340,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - ocptv_log_fatal!(run, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the error message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_run(&expected, "log", |run| async move { + ocptv_log_fatal!(run, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -460,40 +359,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - - ocptv_error!(step, "symptom", "Error message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the error message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("error") - .ok_or(anyhow!("error key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "error", |step| async move { + ocptv_error!(step, "symptom", "Error message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -507,39 +377,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - - ocptv_error!(step, "symptom").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the error message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("error") - .ok_or(anyhow!("error key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "error", |step| async move { + ocptv_error!(step, "symptom").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -554,38 +396,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - ocptv_log_debug!(step, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "log", |step| async move { + ocptv_log_debug!(step, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -600,38 +415,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - ocptv_log_info!(step, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "log", |step| async move { + ocptv_log_info!(step, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -646,38 +434,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - ocptv_log_warning!(step, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "log", |step| async move { + ocptv_log_warning!(step, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -692,38 +453,11 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - ocptv_log_error!(step, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "log", |step| async move { + ocptv_log_error!(step, "log message").await?; + Ok(()) + }) + .await } #[tokio::test] @@ -738,37 +472,10 @@ mod tests { "sequenceNumber": 3 }); - let dut = DutInfo::builder("dut_id").build(); - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - let step = run.step("step_name")?; - ocptv_log_fatal!(step, "log message").await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - .first_chunk::<3>() - .ok_or(anyhow!("Buffer is missing the log message"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: &expected); - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testStepArtifact key does not exist"))? - .get("log") - .ok_or(anyhow!("log key does not exist"))?; - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) + check_output_step(&expected, "log", |step| async move { + ocptv_log_fatal!(step, "log message").await?; + Ok(()) + }) + .await } } From 26321ed0ca8b1cfdb27208f6de61c608dc855bb8 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 13:49:04 +0100 Subject: [PATCH 15/96] move macros integration tests - similar to the runner tests before, these behave like integration tests and only use public api - because of this, move them to their own module Signed-off-by: mimir-d --- src/output/macros.rs | 360 ---------------------------------------- tests/output/macros.rs | 364 +++++++++++++++++++++++++++++++++++++++++ tests/output/main.rs | 1 + 3 files changed, 365 insertions(+), 360 deletions(-) create mode 100644 tests/output/macros.rs diff --git a/src/output/macros.rs b/src/output/macros.rs index bd494ab..e09717f 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -119,363 +119,3 @@ ocptv_log!(ocptv_log_info, Info); ocptv_log!(ocptv_log_warning, Warning); ocptv_log!(ocptv_log_error, Error); ocptv_log!(ocptv_log_fatal, Fatal); - -#[cfg(test)] -mod tests { - use std::future::Future; - use std::sync::Arc; - - use anyhow::anyhow; - use anyhow::Result; - use assert_json_diff::assert_json_include; - use serde_json::json; - use tokio::sync::Mutex; - - use crate::output::objects::*; - use crate::output::runner::*; - - async fn check_output(expected: &serde_json::Value, func: F) -> Result - where - R: Future>, - F: FnOnce(StartedTestRun) -> R, - { - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", &dut, "1.0") - .config(Config::builder().with_buffer_output(buffer.clone()).build()) - .build() - .start() - .await?; - - func(run).await?; - - let actual = serde_json::from_str::( - &buffer - .lock() - .await - // first 2 items are schemaVersion, testRunStart - .first_chunk::<3>() - .ok_or(anyhow!("buffer is missing macro output item"))?[2], - )?; - assert_json_include!(actual: actual.clone(), expected: expected); - - Ok(actual) - } - - async fn check_output_run(expected: &serde_json::Value, key: &str, func: F) -> Result<()> - where - R: Future>, - F: FnOnce(StartedTestRun) -> R, - { - let actual = check_output(expected, func).await?; - - let source = actual - .get("testRunArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get(key) - .ok_or(anyhow!("error key does not exist"))?; - - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) - } - - async fn check_output_step(expected: &serde_json::Value, key: &str, func: F) -> Result<()> - where - R: Future>, - F: FnOnce(TestStep) -> R, - { - let actual = check_output(expected, |run| async move { - let step = run.step("step_name")?; - // TODO: missing step start here - - func(step).await?; - Ok(()) - }) - .await?; - - let source = actual - .get("testStepArtifact") - .ok_or(anyhow!("testRunArtifact key does not exist"))? - .get(key) - .ok_or(anyhow!("error key does not exist"))?; - - assert_ne!( - source.get("sourceLocation"), - None, - "sourceLocation is not present in the serialized object" - ); - - Ok(()) - } - - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "error", |run| async move { - ocptv_error!(run, "symptom", "Error message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "error", |run| async move { - ocptv_error!(run, "symptom").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_debug() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "log": { - "message": "log message", - "severity": "DEBUG" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "log", |run| async move { - ocptv_log_debug!(run, "log message").await?; - - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_info() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "log": { - "message": "log message", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "log", |run| async move { - ocptv_log_info!(run, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_warning() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "log": { - "message": "log message", - "severity": "WARNING" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "log", |run| async move { - ocptv_log_warning!(run, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_error() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "log": { - "message": "log message", - "severity": "ERROR" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "log", |run| async move { - ocptv_log_error!(run, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_fatal() -> Result<()> { - let expected = json!({ - "testRunArtifact": { - "log": { - "message": "log message", - "severity": "FATAL" - } - }, - "sequenceNumber": 3 - }); - - check_output_run(&expected, "log", |run| async move { - ocptv_log_fatal!(run, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "error": { - "message": "Error message", - "symptom":"symptom" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "error", |step| async move { - ocptv_error!(step, "symptom", "Error message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "error", |step| async move { - ocptv_error!(step, "symptom").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_debug_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "log": { - "message": "log message", - "severity": "DEBUG" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "log", |step| async move { - ocptv_log_debug!(step, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_info_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "log": { - "message": "log message", - "severity": "INFO" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "log", |step| async move { - ocptv_log_info!(step, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_warning_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "log": { - "message": "log message", - "severity":"WARNING" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "log", |step| async move { - ocptv_log_warning!(step, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_error_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "log": { - "message": "log message", - "severity": "ERROR" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "log", |step| async move { - ocptv_log_error!(step, "log message").await?; - Ok(()) - }) - .await - } - - #[tokio::test] - async fn test_ocptv_log_fatal_in_step() -> Result<()> { - let expected = json!({ - "testStepArtifact": { - "log": { - "message": "log message", - "severity": "FATAL" - } - }, - "sequenceNumber": 3 - }); - - check_output_step(&expected, "log", |step| async move { - ocptv_log_fatal!(step, "log message").await?; - Ok(()) - }) - .await - } -} diff --git a/tests/output/macros.rs b/tests/output/macros.rs new file mode 100644 index 0000000..954f20a --- /dev/null +++ b/tests/output/macros.rs @@ -0,0 +1,364 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::future::Future; +use std::sync::Arc; + +use anyhow::anyhow; +use anyhow::Result; +use assert_json_diff::assert_json_include; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::ocptv_error; +use ocptv::output as tv; +use ocptv::{ocptv_log_debug, ocptv_log_error, ocptv_log_fatal, ocptv_log_info, ocptv_log_warning}; +use tv::{Config, DutInfo, StartedTestRun, TestRun, TestStep}; + +async fn check_output(expected: &serde_json::Value, func: F) -> Result +where + R: Future>, + F: FnOnce(StartedTestRun) -> R, +{ + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::builder("run_name", &dut, "1.0") + .config(Config::builder().with_buffer_output(buffer.clone()).build()) + .build() + .start() + .await?; + + func(run).await?; + + let actual = serde_json::from_str::( + &buffer + .lock() + .await + // first 2 items are schemaVersion, testRunStart + .first_chunk::<3>() + .ok_or(anyhow!("buffer is missing macro output item"))?[2], + )?; + assert_json_include!(actual: actual.clone(), expected: expected); + + Ok(actual) +} + +async fn check_output_run(expected: &serde_json::Value, key: &str, func: F) -> Result<()> +where + R: Future>, + F: FnOnce(StartedTestRun) -> R, +{ + let actual = check_output(expected, func).await?; + + let source = actual + .get("testRunArtifact") + .ok_or(anyhow!("testRunArtifact key does not exist"))? + .get(key) + .ok_or(anyhow!("error key does not exist"))?; + + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + + Ok(()) +} + +async fn check_output_step(expected: &serde_json::Value, key: &str, func: F) -> Result<()> +where + R: Future>, + F: FnOnce(TestStep) -> R, +{ + let actual = check_output(expected, |run| async move { + let step = run.step("step_name")?; + // TODO: missing step start here + + func(step).await?; + Ok(()) + }) + .await?; + + let source = actual + .get("testStepArtifact") + .ok_or(anyhow!("testRunArtifact key does not exist"))? + .get(key) + .ok_or(anyhow!("error key does not exist"))?; + + assert_ne!( + source.get("sourceLocation"), + None, + "sourceLocation is not present in the serialized object" + ); + + Ok(()) +} + +#[tokio::test] +async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "error", |run| async move { + ocptv_error!(run, "symptom", "Error message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_error_macro_with_symptom() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "error", |run| async move { + ocptv_error!(run, "symptom").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_debug() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "DEBUG" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "log", |run| async move { + ocptv_log_debug!(run, "log message").await?; + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_info() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "log", |run| async move { + ocptv_log_info!(run, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_warning() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "WARNING" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "log", |run| async move { + ocptv_log_warning!(run, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_error() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "ERROR" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "log", |run| async move { + ocptv_log_error!(run, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_fatal() -> Result<()> { + let expected = json!({ + "testRunArtifact": { + "log": { + "message": "log message", + "severity": "FATAL" + } + }, + "sequenceNumber": 3 + }); + + check_output_run(&expected, "log", |run| async move { + ocptv_log_fatal!(run, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "error": { + "message": "Error message", + "symptom":"symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "error", |step| async move { + ocptv_error!(step, "symptom", "Error message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "error", |step| async move { + ocptv_error!(step, "symptom").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_debug_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "DEBUG" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "log", |step| async move { + ocptv_log_debug!(step, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_info_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "INFO" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "log", |step| async move { + ocptv_log_info!(step, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_warning_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity":"WARNING" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "log", |step| async move { + ocptv_log_warning!(step, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_error_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "ERROR" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "log", |step| async move { + ocptv_log_error!(step, "log message").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_log_fatal_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "log": { + "message": "log message", + "severity": "FATAL" + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "log", |step| async move { + ocptv_log_fatal!(step, "log message").await?; + Ok(()) + }) + .await +} diff --git a/tests/output/main.rs b/tests/output/main.rs index 3949d09..c07310d 100644 --- a/tests/output/main.rs +++ b/tests/output/main.rs @@ -4,4 +4,5 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod macros; mod runner; From 1772e2dd7e2935d6973b1aa884d373bb68e7a786 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 17:20:51 +0100 Subject: [PATCH 16/96] add StartedTestStep type state - similar to #1, this type pattern disallows steps from doing invalid operations, like logging when the step hasnt started yet - also same reasoning as in #1 around commenting out the `scope` method on TestStep Signed-off-by: mimir-d --- src/output/runner.rs | 187 ++++++++++++++++++++--------------------- tests/output/macros.rs | 34 ++++---- tests/output/runner.rs | 80 +++++++++--------- 3 files changed, 148 insertions(+), 153 deletions(-) diff --git a/src/output/runner.rs b/src/output/runner.rs index 547d644..681e565 100644 --- a/src/output/runner.rs +++ b/src/output/runner.rs @@ -567,8 +567,8 @@ impl StartedTestRun { Ok(()) } - pub fn step(&self, name: &str) -> Result { - Ok(TestStep::new(name, self.run.state.clone())) + pub fn step(&self, name: &str) -> TestStep { + TestStep::new(name, self.run.state.clone()) } } @@ -578,7 +578,6 @@ impl StartedTestRun { pub struct TestStep { name: String, state: Arc>, - measurement_id_no: Arc, } impl TestStep { @@ -586,7 +585,6 @@ impl TestStep { TestStep { name: name.to_string(), state, - measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), } } @@ -601,14 +599,12 @@ impl TestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { + pub async fn start(self) -> Result { let start = objects::TestStepStart::new(&self.name); self.state .lock() @@ -616,9 +612,58 @@ impl TestStep { .emitter .emit(&start.to_artifact()) .await?; - Ok(()) + + Ok(StartedTestStep { + step: self, + measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), + }) } + // /// Builds a scope in the [`TestStep`] object, taking care of starting and + // /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`TestStep`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + // /// + // /// let step = run.step("first step")?; + // /// step.scope(|s| async { + // /// s.log( + // /// LogSeverity::Info, + // /// "This is a log message with INFO severity", + // /// ).await?; + // /// Ok(TestStatus::Complete) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // F: std::ops::FnOnce(&'a TestStep) -> R, + // { + // self.start().await?; + // let status = func(self).await?; + // self.end(status).await?; + // Ok(()) + // } +} + +pub struct StartedTestStep { + step: TestStep, + measurement_id_no: Arc, +} + +impl StartedTestStep { /// Ends the test step. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend @@ -631,8 +676,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), WriterError>(()) @@ -640,7 +684,8 @@ impl TestStep { /// ``` pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { let end = objects::TestStepEnd::new(status); - self.state + self.step + .state .lock() .await .emitter @@ -649,44 +694,6 @@ impl TestStep { Ok(()) } - /// Builds a scope in the [`TestStep`] object, taking care of starting and - /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. - /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`TestStep`], since it guarantees - /// all the messages are emitted between the start and end messages, the order - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("first step")?; - /// step.scope(|s| async { - /// s.log( - /// LogSeverity::Info, - /// "This is a log message with INFO severity", - /// ).await?; - /// Ok(TestStatus::Complete) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a TestStep) -> R, - { - self.start().await?; - let status = func(self).await?; - self.end(status).await?; - Ok(()) - } - /// Eemits Log message. /// This method accepts a [`models::LogSeverity`] to define the severity /// and a [`std::string::String`] for the message. @@ -701,8 +708,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.log( /// LogSeverity::Info, /// "This is a log message with INFO severity", @@ -722,8 +728,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; /// step.end(TestStatus::Complete).await?; /// @@ -736,7 +741,8 @@ impl TestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let log = objects::Log::builder(msg).severity(severity).build(); - self.state + self.step + .state .lock() .await .emitter @@ -758,8 +764,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) @@ -772,7 +777,8 @@ impl TestStep { /// # }); /// ``` pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - self.state + self.step + .state .lock() .await .emitter @@ -794,8 +800,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.error("symptom").await?; /// step.end(TestStatus::Complete).await?; /// @@ -813,8 +818,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// ocptv_error!(step, "symptom").await?; /// step.end(TestStatus::Complete).await?; /// @@ -823,7 +827,8 @@ impl TestStep { /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).build(); - self.state + self.step + .state .lock() .await .emitter @@ -846,8 +851,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.error_with_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// @@ -865,8 +869,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// ocptv_error!(step, "symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// @@ -879,7 +882,8 @@ impl TestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let error = objects::Error::builder(symptom).message(msg).build(); - self.state + self.step + .state .lock() .await .emitter @@ -901,8 +905,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.error_with_details( /// &Error::builder("symptom") /// .message("Error message") @@ -919,7 +922,8 @@ impl TestStep { &self, error: &objects::Error, ) -> Result<(), emitters::WriterError> { - self.state + self.step + .state .lock() .await .emitter @@ -940,8 +944,7 @@ impl TestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// step.add_measurement("name", 50.into()).await?; /// step.end(TestStatus::Complete).await?; /// @@ -954,7 +957,8 @@ impl TestStep { value: Value, ) -> Result<(), emitters::WriterError> { let measurement = objects::Measurement::new(name, value); - self.state + self.step + .state .lock() .await .emitter @@ -976,7 +980,7 @@ impl TestStep { /// /// let hwinfo = HardwareInfo::builder("id", "fan").build(); /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name")?; + /// let step = run.step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) /// .hardware_info(&hwinfo) @@ -994,7 +998,8 @@ impl TestStep { &self, measurement: &objects::Measurement, ) -> Result<(), emitters::WriterError> { - self.state + self.step + .state .lock() .await .emitter @@ -1016,8 +1021,7 @@ impl TestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// let series = step.measurement_series("name"); /// /// # Ok::<(), WriterError>(()) @@ -1031,7 +1035,7 @@ impl TestStep { self.measurement_id_no.load(atomic::Ordering::SeqCst) ); - MeasurementSeries::new(&series_id, name, self.state.clone()) + MeasurementSeries::new(&series_id, name, self.step.state.clone()) } /// Starts a Measurement Series (a time-series list of measurements). @@ -1046,8 +1050,7 @@ impl TestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// let series = /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); /// @@ -1058,7 +1061,7 @@ impl TestStep { &self, start: objects::MeasurementSeriesStart, ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, self.state.clone()) + MeasurementSeries::new_with_details(start, self.step.state.clone()) } } @@ -1114,9 +1117,7 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// let series = step.measurement_series("name"); /// series.start().await?; @@ -1145,9 +1146,7 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// let series = step.measurement_series("name"); /// series.start().await?; @@ -1181,9 +1180,7 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// let series = step.measurement_series("name"); /// series.start().await?; @@ -1221,9 +1218,7 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// let series = step.measurement_series("name"); /// series.start().await?; @@ -1269,9 +1264,7 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name")?; - /// step.start().await?; + /// let step = run.step("step_name").start().await?; /// /// let series = step.measurement_series("name"); /// series.start().await?; diff --git a/tests/output/macros.rs b/tests/output/macros.rs index 954f20a..4eeaf22 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -16,9 +16,12 @@ use tokio::sync::Mutex; use ocptv::ocptv_error; use ocptv::output as tv; use ocptv::{ocptv_log_debug, ocptv_log_error, ocptv_log_fatal, ocptv_log_info, ocptv_log_warning}; -use tv::{Config, DutInfo, StartedTestRun, TestRun, TestStep}; +use tv::{Config, DutInfo, StartedTestRun, StartedTestStep, TestRun}; -async fn check_output(expected: &serde_json::Value, func: F) -> Result +async fn check_output( + expected: &serde_json::Value, + func: F, +) -> Result where R: Future>, F: FnOnce(StartedTestRun) -> R, @@ -39,8 +42,8 @@ where .lock() .await // first 2 items are schemaVersion, testRunStart - .first_chunk::<3>() - .ok_or(anyhow!("buffer is missing macro output item"))?[2], + .first_chunk::() + .ok_or(anyhow!("buffer is missing macro output item"))?[N - 1], )?; assert_json_include!(actual: actual.clone(), expected: expected); @@ -52,7 +55,7 @@ where R: Future>, F: FnOnce(StartedTestRun) -> R, { - let actual = check_output(expected, func).await?; + let actual = check_output::<_, _, 3>(expected, func).await?; let source = actual .get("testRunArtifact") @@ -72,11 +75,10 @@ where async fn check_output_step(expected: &serde_json::Value, key: &str, func: F) -> Result<()> where R: Future>, - F: FnOnce(TestStep) -> R, + F: FnOnce(StartedTestStep) -> R, { - let actual = check_output(expected, |run| async move { - let step = run.step("step_name")?; - // TODO: missing step start here + let actual = check_output::<_, _, 4>(expected, |run| async move { + let step = run.step("step_name").start().await?; func(step).await?; Ok(()) @@ -240,7 +242,7 @@ async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> "symptom":"symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "error", |step| async move { @@ -258,7 +260,7 @@ async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "error", |step| async move { @@ -277,7 +279,7 @@ async fn test_ocptv_log_debug_in_step() -> Result<()> { "severity": "DEBUG" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "log", |step| async move { @@ -296,7 +298,7 @@ async fn test_ocptv_log_info_in_step() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "log", |step| async move { @@ -315,7 +317,7 @@ async fn test_ocptv_log_warning_in_step() -> Result<()> { "severity":"WARNING" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "log", |step| async move { @@ -334,7 +336,7 @@ async fn test_ocptv_log_error_in_step() -> Result<()> { "severity": "ERROR" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "log", |step| async move { @@ -353,7 +355,7 @@ async fn test_ocptv_log_fatal_in_step() -> Result<()> { "severity": "FATAL" } }, - "sequenceNumber": 3 + "sequenceNumber": 4 }); check_output_step(&expected, "log", |step| async move { diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 242708a..82426fc 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -21,8 +21,8 @@ use tokio::sync::Mutex; use ocptv::output as tv; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, - SoftwareInfo, StartedTestRun, Subcomponent, TestResult, TestRun, TestRunBuilder, - TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, + SoftwareInfo, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, + TestRunBuilder, TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, }; fn json_schema_version() -> serde_json::Value { @@ -130,13 +130,12 @@ where async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a TestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, + F: for<'a> FnOnce(&'a StartedTestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, { check_output(expected, |run_builder| async { let run = run_builder.build().start().await?; - let step = run.step("first step")?; - step.start().await?; + let step = run.step("first step").start().await?; test_fn(&step).await?; step.end(TestStatus::Complete).await?; @@ -535,42 +534,43 @@ async fn test_testrun_step_error_with_details() -> Result<()> { .await } -#[tokio::test] -async fn test_testrun_step_scope_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "sequenceNumber": 4, - "testStepArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - } - }), - json_step_complete(5), - json_run_pass(6), - ]; +// #[tokio::test] +// async fn test_testrun_step_scope_log() -> Result<()> { +// let expected = [ +// json_schema_version(), +// json_run_default_start(), +// json_step_default_start(), +// json!({ +// "sequenceNumber": 4, +// "testStepArtifact": { +// "log": { +// "message": "This is a log message with INFO severity", +// "severity": "INFO" +// } +// } +// }), +// json_step_complete(5), +// json_run_pass(6), +// ]; - check_output_run(&expected, |run| { - async { - run.step("first step")? - .scope(|s| async { - s.log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - Ok(TestStatus::Complete) - }) - .await - } - .boxed() - }) - .await -} +// check_output_run(&expected, |run| { +// async { +// run.step("first step") +// .start() +// .scope(|s| async { +// s.log( +// LogSeverity::Info, +// "This is a log message with INFO severity", +// ) +// .await?; +// Ok(TestStatus::Complete) +// }) +// .await +// } +// .boxed() +// }) +// .await +// } #[tokio::test] async fn test_step_with_measurement() -> Result<()> { From 3381b2ff148c226315c22d1fd86dca0cb8874405 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 18:07:39 +0100 Subject: [PATCH 17/96] split runner.rs into smaller files - this refactoring is in preparation for the next steps (commits on this branch) - smaller files makes the scope of the objects easier to reason about, grouping them by ocptv topic Signed-off-by: mimir-d --- src/output/config.rs | 77 ++ src/output/measurement_series.rs | 241 ++++++ src/output/mod.rs | 10 +- src/output/run.rs | 493 ++++++++++++ src/output/runner.rs | 1291 ------------------------------ src/output/state.rs | 18 + src/output/step.rs | 507 ++++++++++++ 7 files changed, 1344 insertions(+), 1293 deletions(-) create mode 100644 src/output/config.rs create mode 100644 src/output/measurement_series.rs create mode 100644 src/output/run.rs delete mode 100644 src/output/runner.rs create mode 100644 src/output/state.rs create mode 100644 src/output/step.rs diff --git a/src/output/config.rs b/src/output/config.rs new file mode 100644 index 0000000..f97baaa --- /dev/null +++ b/src/output/config.rs @@ -0,0 +1,77 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::path::Path; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::output::emitters; + +/// The configuration repository for the TestRun. +pub struct Config { + pub(crate) timezone: chrono_tz::Tz, + pub(crate) writer: emitters::WriterType, +} + +impl Config { + /// Creates a new [`ConfigBuilder`] + /// + /// # Examples + /// ```rust + /// # use ocptv::output::*; + /// + /// let builder = Config::builder(); + /// ``` + pub fn builder() -> ConfigBuilder { + ConfigBuilder::new() + } +} + +/// The builder for the [`Config`] object. +pub struct ConfigBuilder { + timezone: Option, + writer: Option, +} + +impl ConfigBuilder { + fn new() -> Self { + Self { + timezone: None, + writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } + + pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { + self.timezone = Some(timezone); + self + } + + pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { + self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( + buffer, + ))); + self + } + + pub async fn with_file_output>( + mut self, + path: P, + ) -> Result { + self.writer = Some(emitters::WriterType::File( + emitters::FileWriter::new(path).await?, + )); + Ok(self) + } + + pub fn build(self) -> Config { + Config { + timezone: self.timezone.unwrap_or(chrono_tz::UTC), + writer: self + .writer + .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + } + } +} diff --git a/src/output/measurement_series.rs b/src/output/measurement_series.rs new file mode 100644 index 0000000..c7372c2 --- /dev/null +++ b/src/output/measurement_series.rs @@ -0,0 +1,241 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::future::Future; +use std::sync::atomic; +use std::sync::Arc; + +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::{emitters, objects, state}; + +/// The measurement series. +/// A Measurement Series is a time-series list of measurements. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +pub struct MeasurementSeries { + state: Arc>, + seq_no: Arc>, + start: objects::MeasurementSeriesStart, +} + +impl MeasurementSeries { + pub fn new(series_id: &str, name: &str, state: Arc>) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start: objects::MeasurementSeriesStart::new(name, series_id), + } + } + + pub fn new_with_details( + start: objects::MeasurementSeriesStart, + state: Arc>, + ) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start, + } + } + + async fn current_sequence_no(&self) -> u64 { + self.seq_no.lock().await.load(atomic::Ordering::SeqCst) + } + + async fn increment_sequence_no(&self) { + self.seq_no + .lock() + .await + .fetch_add(1, atomic::Ordering::SeqCst); + } + + /// Starts the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&self.start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.end().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self) -> Result<(), emitters::WriterError> { + let end = objects::MeasurementSeriesEnd::new( + self.start.get_series_id(), + self.current_sequence_no().await, + ); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement(60.into()).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + None, + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// This method accepts additional metadata to add to the element. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_metadata( + &self, + value: Value, + metadata: Vec<(&str, Value)>, + ) -> Result<(), emitters::WriterError> { + let element = objects::MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + Some(Map::from_iter( + metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), + )), + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.scope(|s| async { + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; + /// Ok(()) + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + { + self.start().await?; + func(self).await?; + self.end().await?; + Ok(()) + } +} diff --git a/src/output/mod.rs b/src/output/mod.rs index 231567f..c0d0a6e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -4,12 +4,17 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod config; mod emitters; mod macros; +mod measurement_series; mod models; mod objects; -mod runner; +mod run; +mod state; +mod step; +pub use config::*; pub use emitters::*; pub use models::LogSeverity; pub use models::TestResult; @@ -17,5 +22,6 @@ pub use models::TestStatus; pub use models::ValidatorType; pub use models::SPEC_VERSION; pub use objects::*; -pub use runner::*; +pub use run::*; pub use serde_json::Value; +pub use step::*; diff --git a/src/output/run.rs b/src/output/run.rs new file mode 100644 index 0000000..b3d4a9f --- /dev/null +++ b/src/output/run.rs @@ -0,0 +1,493 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::env; +use std::sync::Arc; + +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::config; +use tv::emitters; +use tv::models; +use tv::objects; +use tv::state; +use tv::step::TestStep; + +/// The outcome of a TestRun. +/// It's returned when the scope method of the [`TestRun`] object is used. +pub struct TestRunOutcome { + /// Reports the execution status of the test + pub status: models::TestStatus, + /// Reports the result of the test + pub result: models::TestResult, +} + +/// The main diag test run. +/// +/// This object describes a single run instance of the diag, and therefore drives the test session. +pub struct TestRun { + name: String, + version: String, + parameters: Map, + dut: objects::DutInfo, + command_line: String, + metadata: Option>, + state: Arc>, +} + +impl TestRun { + /// Creates a new [`TestRunBuilder`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("my_dut").build(); + /// let builder = TestRun::builder("run_name", &dut, "1.0"); + /// ``` + pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { + TestRunBuilder::new(name, dut, version) + } + + /// Creates a new [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// ``` + pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { + let dut = objects::DutInfo::new(dut_id); + TestRunBuilder::new(name, &dut, version).build() + } + + /// Starts the test run. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// run.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(self) -> Result { + let version = objects::SchemaVersion::new(); + self.state + .lock() + .await + .emitter + .emit(&version.to_artifact()) + .await?; + + let mut builder = objects::TestRunStart::builder( + &self.name, + &self.version, + &self.command_line, + &self.parameters, + &self.dut, + ); + + if let Some(m) = &self.metadata { + for m in m { + builder = builder.add_metadata(m.0, m.1.clone()) + } + } + + let start = builder.build(); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + + Ok(StartedTestRun { run: self }) + } + + // disabling this for the moment so we don't publish api that's unusable. + // see: https://github.com/rust-lang/rust/issues/70263 + // + // /// Builds a scope in the [`TestRun`] object, taking care of starting and + // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`TestRun`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + // /// run.scope(|r| async { + // /// r.log(LogSeverity::Info, "First message").await?; + // /// Ok(TestRunOutcome { + // /// status: TestStatus::Complete, + // /// result: TestResult::Pass, + // /// }) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // for<'a> F: Fut2<'a, R>, + // { + // let run = self.start().await?; + // let outcome = func(&run).await?; + // run.end(outcome.status, outcome.result).await?; + + // Ok(()) + // } +} + +/// Builder for the [`TestRun`] object. +pub struct TestRunBuilder { + name: String, + dut: objects::DutInfo, + version: String, + parameters: Map, + command_line: String, + metadata: Option>, + config: Option, +} + +impl TestRunBuilder { + pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + Self { + name: name.to_string(), + dut: dut.clone(), + version: version.to_string(), + parameters: Map::new(), + command_line: env::args().collect::>()[1..].join(" "), + metadata: None, + config: None, + } + } + + /// Adds a user defined parameter to the future [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_parameter("param1", "value1".into()) + /// .build(); + /// ``` + pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { + self.parameters.insert(key.to_string(), value.clone()); + self + } + + /// Adds the command line used to run the test session to the future + /// [`TestRun`] object. + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .command_line("my_diag --arg value") + /// .build(); + /// ``` + pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + self.command_line = cmd.to_string(); + self + } + + /// Adds the configuration for the test session to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .config(Config::builder().build()) + /// .build(); + /// ``` + pub fn config(mut self, value: config::Config) -> TestRunBuilder { + self.config = Some(value); + self + } + + /// Adds user defined metadata to the future [`TestRun`] object + /// + /// # Examples + /// + /// ```rust + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::builder("dut_id").build(); + /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// .add_metadata("meta1", "value1".into()) + /// .build(); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRun { + let config = self.config.unwrap_or(config::Config::builder().build()); + let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let state = state::TestState::new(emitter); + TestRun { + name: self.name, + dut: self.dut, + version: self.version, + parameters: self.parameters, + command_line: self.command_line, + metadata: self.metadata, + state: Arc::new(Mutex::new(state)), + } + } +} + +/// A test run that was started. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +pub struct StartedTestRun { + run: TestRun, +} + +impl StartedTestRun { + /// Ends the test run. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end( + &self, + status: models::TestStatus, + result: models::TestResult, + ) -> Result<(), emitters::WriterError> { + let end = objects::TestRunEnd::builder() + .status(status) + .result(result) + .build(); + + let emitter = &self.run.state.lock().await.emitter; + + emitter.emit(&end.to_artifact()).await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error("symptom").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`std::string::String`] to define the symptom and + /// another [`std::string::String`] as error message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_msg("symptom", "error messasge").await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method acceps a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// run.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// ).await?; + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + let emitter = &self.run.state.lock().await.emitter; + + emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .await?; + Ok(()) + } + + pub fn step(&self, name: &str) -> TestStep { + TestStep::new(name, self.run.state.clone()) + } +} diff --git a/src/output/runner.rs b/src/output/runner.rs deleted file mode 100644 index 681e565..0000000 --- a/src/output/runner.rs +++ /dev/null @@ -1,1291 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -//! OCPTV library runner -//! -//! This module contains the main entry point for the test runner. This is the -//! main object the user will interact with. - -use std::env; -use std::future::Future; -use std::path::Path; -use std::sync::atomic; -use std::sync::Arc; - -use serde_json::Map; -use serde_json::Value; -use tokio::sync::Mutex; - -use crate::output::emitters; -use crate::output::models; -use crate::output::objects; - -/// The configuration repository for the TestRun. -pub struct Config { - timezone: chrono_tz::Tz, - writer: emitters::WriterType, -} - -impl Config { - /// Creates a new [`ConfigBuilder`] - /// - /// # Examples - /// ```rust - /// # use ocptv::output::*; - /// - /// let builder = Config::builder(); - /// ``` - pub fn builder() -> ConfigBuilder { - ConfigBuilder::new() - } -} - -/// The builder for the [`Config`] object. -pub struct ConfigBuilder { - timezone: Option, - writer: Option, -} - -impl ConfigBuilder { - fn new() -> Self { - Self { - timezone: None, - writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), - } - } - - pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { - self.timezone = Some(timezone); - self - } - - pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { - self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( - buffer, - ))); - self - } - - pub async fn with_file_output>( - mut self, - path: P, - ) -> Result { - self.writer = Some(emitters::WriterType::File( - emitters::FileWriter::new(path).await?, - )); - Ok(self) - } - - pub fn build(self) -> Config { - Config { - timezone: self.timezone.unwrap_or(chrono_tz::UTC), - writer: self - .writer - .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), - } - } -} - -/// The outcome of a TestRun. -/// It's returned when the scope method of the [`TestRun`] object is used. -pub struct TestRunOutcome { - /// Reports the execution status of the test - pub status: models::TestStatus, - /// Reports the result of the test - pub result: models::TestResult, -} - -struct TestState { - emitter: emitters::JsonEmitter, -} - -impl TestState { - fn new(emitter: emitters::JsonEmitter) -> TestState { - TestState { emitter } - } -} - -/// The main diag test run. -/// -/// This object describes a single run instance of the diag, and therefore drives the test session. -pub struct TestRun { - name: String, - version: String, - parameters: Map, - dut: objects::DutInfo, - command_line: String, - metadata: Option>, - state: Arc>, -} - -impl TestRun { - /// Creates a new [`TestRunBuilder`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("my_dut").build(); - /// let builder = TestRun::builder("run_name", &dut, "1.0"); - /// ``` - pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { - TestRunBuilder::new(name, dut, version) - } - - /// Creates a new [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// ``` - pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { - let dut = objects::DutInfo::new(dut_id); - TestRunBuilder::new(name, &dut, version).build() - } - - /// Starts the test run. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// run.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(self) -> Result { - let version = objects::SchemaVersion::new(); - self.state - .lock() - .await - .emitter - .emit(&version.to_artifact()) - .await?; - - let mut builder = objects::TestRunStart::builder( - &self.name, - &self.version, - &self.command_line, - &self.parameters, - &self.dut, - ); - - if let Some(m) = &self.metadata { - for m in m { - builder = builder.add_metadata(m.0, m.1.clone()) - } - } - - let start = builder.build(); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; - - Ok(StartedTestRun { run: self }) - } - - // disabling this for the moment so we don't publish api that's unusable. - // see: https://github.com/rust-lang/rust/issues/70263 - // - // /// Builds a scope in the [`TestRun`] object, taking care of starting and - // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`TestRun`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - // /// run.scope(|r| async { - // /// r.log(LogSeverity::Info, "First message").await?; - // /// Ok(TestRunOutcome { - // /// status: TestStatus::Complete, - // /// result: TestResult::Pass, - // /// }) - // /// }).await?; - // /// - // /// # Ok::<(), WriterError>(()) - // /// # }); - // /// ``` - // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // for<'a> F: Fut2<'a, R>, - // { - // let run = self.start().await?; - // let outcome = func(&run).await?; - // run.end(outcome.status, outcome.result).await?; - - // Ok(()) - // } -} - -/// Builder for the [`TestRun`] object. -pub struct TestRunBuilder { - name: String, - dut: objects::DutInfo, - version: String, - parameters: Map, - command_line: String, - metadata: Option>, - config: Option, -} - -impl TestRunBuilder { - pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { - Self { - name: name.to_string(), - dut: dut.clone(), - version: version.to_string(), - parameters: Map::new(), - command_line: env::args().collect::>()[1..].join(" "), - metadata: None, - config: None, - } - } - - /// Adds a user defined parameter to the future [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_parameter("param1", "value1".into()) - /// .build(); - /// ``` - pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { - self.parameters.insert(key.to_string(), value.clone()); - self - } - - /// Adds the command line used to run the test session to the future - /// [`TestRun`] object. - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .command_line("my_diag --arg value") - /// .build(); - /// ``` - pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { - self.command_line = cmd.to_string(); - self - } - - /// Adds the configuration for the test session to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .config(Config::builder().build()) - /// .build(); - /// ``` - pub fn config(mut self, value: Config) -> TestRunBuilder { - self.config = Some(value); - self - } - - /// Adds user defined metadata to the future [`TestRun`] object - /// - /// # Examples - /// - /// ```rust - /// # use ocptv::output::*; - /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") - /// .add_metadata("meta1", "value1".into()) - /// .build(); - /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRun { - let config = self.config.unwrap_or(Config::builder().build()); - let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); - let state = TestState::new(emitter); - TestRun { - name: self.name, - dut: self.dut, - version: self.version, - parameters: self.parameters, - command_line: self.command_line, - metadata: self.metadata, - state: Arc::new(Mutex::new(state)), - } - } -} - -/// A test run that was started. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart -pub struct StartedTestRun { - run: TestRun, -} - -impl StartedTestRun { - /// Ends the test run. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end( - &self, - status: models::TestStatus, - result: models::TestResult, - ) -> Result<(), emitters::WriterError> { - let end = objects::TestRunEnd::builder() - .status(status) - .result(result) - .build(); - - let emitter = &self.run.state.lock().await.emitter; - - emitter.emit(&end.to_artifact()).await?; - Ok(()) - } - - /// Emits a Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log( - /// LogSeverity::Info, - /// "This is a log message with INFO severity", - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log( - &self, - severity: models::LogSeverity, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); - - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Log message. - /// This method accepts a [`objects::Log`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log_with_details( - /// &Log::builder("This is a log message with INFO severity") - /// .severity(LogSeverity::Info) - /// .source("file", 1) - /// .build(), - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error("symptom").await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_msg("symptom", "error messasge").await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method acceps a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_details( - /// &Error::builder("symptom") - /// .message("Error message") - /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) - /// .build(), - /// ).await?; - /// run.end(TestStatus::Complete, TestResult::Pass).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_details( - &self, - error: &objects::Error, - ) -> Result<(), emitters::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - - emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) - .await?; - Ok(()) - } - - pub fn step(&self, name: &str) -> TestStep { - TestStep::new(name, self.run.state.clone()) - } -} - -/// A single test step in the scope of a [`TestRun`]. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts -pub struct TestStep { - name: String, - state: Arc>, -} - -impl TestStep { - fn new(name: &str, state: Arc>) -> TestStep { - TestStep { - name: name.to_string(), - state, - } - } - - /// Starts the test step. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(self) -> Result { - let start = objects::TestStepStart::new(&self.name); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; - - Ok(StartedTestStep { - step: self, - measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), - }) - } - - // /// Builds a scope in the [`TestStep`] object, taking care of starting and - // /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`TestStep`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - // /// - // /// let step = run.step("first step")?; - // /// step.scope(|s| async { - // /// s.log( - // /// LogSeverity::Info, - // /// "This is a log message with INFO severity", - // /// ).await?; - // /// Ok(TestStatus::Complete) - // /// }).await?; - // /// - // /// # Ok::<(), WriterError>(()) - // /// # }); - // /// ``` - // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // F: std::ops::FnOnce(&'a TestStep) -> R, - // { - // self.start().await?; - // let status = func(self).await?; - // self.end(status).await?; - // Ok(()) - // } -} - -pub struct StartedTestStep { - step: TestStep, - measurement_id_no: Arc, -} - -impl StartedTestStep { - /// Ends the test step. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { - let end = objects::TestStepEnd::new(status); - self.step - .state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Eemits Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.log( - /// LogSeverity::Info, - /// "This is a log message with INFO severity", - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_log_info; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log( - &self, - severity: models::LogSeverity, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits Log message. - /// This method accepts a [`objects::Log`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.log_with_details( - /// &Log::builder("This is a log message with INFO severity") - /// .severity(LogSeverity::Info) - /// .source("file", 1) - /// .build(), - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits an Error symptom. - /// This method accepts a [`std::string::String`] to define the symptom. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error("symptom").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_error; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_error!(step, "symptom").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits an Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_msg("symptom", "error message").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - /// - /// ## Using macros - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// use ocptv::ocptv_error; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// ocptv_error!(step, "symptom", "error message").await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits a Error message. - /// This method accepts a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_details( - /// &Error::builder("symptom") - /// .message("Error message") - /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) - /// .build(), - /// ).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn error_with_details( - &self, - error: &objects::Error, - ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) - .await?; - Ok(()) - } - - /// Emits a Measurement message. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// - /// let step = run.step("step_name").start().await?; - /// step.add_measurement("name", 50.into()).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement( - &self, - name: &str, - value: Value, - ) -> Result<(), emitters::WriterError> { - let measurement = objects::Measurement::new(name, value); - self.step - .state - .lock() - .await - .emitter - .emit(&measurement.to_artifact()) - .await?; - Ok(()) - } - - /// Emits a Measurement message. - /// This method accepts a [`objects::Error`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let hwinfo = HardwareInfo::builder("id", "fan").build(); - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let measurement = Measurement::builder("name", 5000.into()) - /// .hardware_info(&hwinfo) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) - /// .subcomponent(&Subcomponent::builder("name").build()) - /// .build(); - /// step.add_measurement_with_details(&measurement).await?; - /// step.end(TestStatus::Complete).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_details( - &self, - measurement: &objects::Measurement, - ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&measurement.to_artifact()) - .await?; - Ok(()) - } - - /// Starts a Measurement Series (a time-series list of measurements). - /// This method accepts a [`std::string::String`] as series ID and - /// a [`std::string::String`] as series name. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// let series = step.measurement_series("name"); - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub fn measurement_series(&self, name: &str) -> MeasurementSeries { - self.measurement_id_no - .fetch_add(1, atomic::Ordering::SeqCst); - let series_id: String = format!( - "series_{}", - self.measurement_id_no.load(atomic::Ordering::SeqCst) - ); - - MeasurementSeries::new(&series_id, name, self.step.state.clone()) - } - - /// Starts a Measurement Series (a time-series list of measurements). - /// This method accepts a [`objects::MeasurementSeriesStart`] object. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// let series = - /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub fn measurement_series_with_details( - &self, - start: objects::MeasurementSeriesStart, - ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, self.step.state.clone()) - } -} - -/// The measurement series. -/// A Measurement Series is a time-series list of measurements. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -pub struct MeasurementSeries { - state: Arc>, - seq_no: Arc>, - start: objects::MeasurementSeriesStart, -} - -impl MeasurementSeries { - fn new(series_id: &str, name: &str, state: Arc>) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start: objects::MeasurementSeriesStart::new(name, series_id), - } - } - - fn new_with_details( - start: objects::MeasurementSeriesStart, - state: Arc>, - ) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start, - } - } - - async fn current_sequence_no(&self) -> u64 { - self.seq_no.lock().await.load(atomic::Ordering::SeqCst) - } - - async fn increment_sequence_no(&self) { - self.seq_no - .lock() - .await - .fetch_add(1, atomic::Ordering::SeqCst); - } - - /// Starts the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter - .emit(&self.start.to_artifact()) - .await?; - Ok(()) - } - - /// Ends the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.end().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { - let end = objects::MeasurementSeriesEnd::new( - self.start.get_series_id(), - self.current_sequence_no().await, - ); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement(60.into()).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - None, - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// This method accepts additional metadata to add to the element. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_metadata( - &self, - value: Value, - metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - Some(Map::from_iter( - metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), - )), - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. - /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees - /// all the messages are emitted between the start and end messages, the order - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.scope(|s| async { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; - /// Ok(()) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a MeasurementSeries) -> R, - { - self.start().await?; - func(self).await?; - self.end().await?; - Ok(()) - } -} diff --git a/src/output/state.rs b/src/output/state.rs new file mode 100644 index 0000000..63164f8 --- /dev/null +++ b/src/output/state.rs @@ -0,0 +1,18 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::output::emitters; + +// TODO: will prob need some redesign +pub struct TestState { + pub emitter: emitters::JsonEmitter, +} + +impl TestState { + pub fn new(emitter: emitters::JsonEmitter) -> TestState { + TestState { emitter } + } +} diff --git a/src/output/step.rs b/src/output/step.rs new file mode 100644 index 0000000..59b6ddb --- /dev/null +++ b/src/output/step.rs @@ -0,0 +1,507 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use serde_json::Value; +use std::sync::atomic; +use std::sync::Arc; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::measurement_series::MeasurementSeries; +use tv::{emitters, models, objects, state}; + +/// A single test step in the scope of a [`TestRun`]. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +pub struct TestStep { + name: String, + state: Arc>, +} + +impl TestStep { + pub(crate) fn new(name: &str, state: Arc>) -> TestStep { + TestStep { + name: name.to_string(), + state, + } + } + + /// Starts the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(self) -> Result { + let start = objects::TestStepStart::new(&self.name); + self.state + .lock() + .await + .emitter + .emit(&start.to_artifact()) + .await?; + + Ok(StartedTestStep { + step: self, + measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), + }) + } + + // /// Builds a scope in the [`TestStep`] object, taking care of starting and + // /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`TestStep`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + // /// + // /// let step = run.step("first step")?; + // /// step.scope(|s| async { + // /// s.log( + // /// LogSeverity::Info, + // /// "This is a log message with INFO severity", + // /// ).await?; + // /// Ok(TestStatus::Complete) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + // where + // R: Future>, + // F: std::ops::FnOnce(&'a TestStep) -> R, + // { + // self.start().await?; + // let status = func(self).await?; + // self.end(status).await?; + // Ok(()) + // } +} + +pub struct StartedTestStep { + step: TestStep, + measurement_id_no: Arc, +} + +impl StartedTestStep { + /// Ends the test step. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { + let end = objects::TestStepEnd::new(status); + self.step + .state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Eemits Log message. + /// This method accepts a [`models::LogSeverity`] to define the severity + /// and a [`std::string::String`] for the message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_log_info; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log( + &self, + severity: models::LogSeverity, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let log = objects::Log::builder(msg).severity(severity).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits Log message. + /// This method accepts a [`objects::Log`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.log_with_details( + /// &Log::builder("This is a log message with INFO severity") + /// .severity(LogSeverity::Info) + /// .source("file", 1) + /// .build(), + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits an Error symptom. + /// This method accepts a [`std::string::String`] to define the symptom. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error("symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_error!(step, "symptom").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits an Error message. + /// This method accepts a [`std::string::String`] to define the symptom and + /// another [`std::string::String`] as error message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error_with_msg("symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + /// + /// ## Using macros + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// use ocptv::ocptv_error; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// ocptv_error!(step, "symptom", "error message").await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_msg( + &self, + symptom: &str, + msg: &str, + ) -> Result<(), emitters::WriterError> { + let error = objects::Error::builder(symptom).message(msg).build(); + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Error message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.error_with_details( + /// &Error::builder("symptom") + /// .message("Error message") + /// .source("file", 1) + /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .build(), + /// ).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn error_with_details( + &self, + error: &objects::Error, + ) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.step("step_name").start().await?; + /// step.add_measurement("name", 50.into()).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement( + &self, + name: &str, + value: Value, + ) -> Result<(), emitters::WriterError> { + let measurement = objects::Measurement::new(name, value); + self.step + .state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Emits a Measurement message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let hwinfo = HardwareInfo::builder("id", "fan").build(); + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let measurement = Measurement::builder("name", 5000.into()) + /// .hardware_info(&hwinfo) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_metadata("key", "value".into()) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// step.add_measurement_with_details(&measurement).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_details( + &self, + measurement: &objects::Measurement, + ) -> Result<(), emitters::WriterError> { + self.step + .state + .lock() + .await + .emitter + .emit(&measurement.to_artifact()) + .await?; + Ok(()) + } + + /// Starts a Measurement Series (a time-series list of measurements). + /// This method accepts a [`std::string::String`] as series ID and + /// a [`std::string::String`] as series name. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// let series = step.measurement_series("name"); + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub fn measurement_series(&self, name: &str) -> MeasurementSeries { + self.measurement_id_no + .fetch_add(1, atomic::Ordering::SeqCst); + let series_id: String = format!( + "series_{}", + self.measurement_id_no.load(atomic::Ordering::SeqCst) + ); + + MeasurementSeries::new(&series_id, name, self.step.state.clone()) + } + + /// Starts a Measurement Series (a time-series list of measurements). + /// This method accepts a [`objects::MeasurementSeriesStart`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// let series = + /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub fn measurement_series_with_details( + &self, + start: objects::MeasurementSeriesStart, + ) -> MeasurementSeries { + MeasurementSeries::new_with_details(start, self.step.state.clone()) + } +} From b67d9c2416b3873740b2c6165326d43d39cc0296 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 19:17:55 +0100 Subject: [PATCH 18/96] remove objects.rs - `objects.rs` was a mix of unrelated items, move them to separate modules for easier maintainability Signed-off-by: mimir-d --- src/output/dut.rs | 621 ++++++++++ src/output/emitters.rs | 6 +- src/output/error.rs | 232 ++++ src/output/log.rs | 130 ++ src/output/measurement.rs | 1009 ++++++++++++++++ src/output/measurement_series.rs | 241 ---- src/output/mod.rs | 14 +- src/output/objects.rs | 1920 ------------------------------ src/output/run.rs | 228 +++- src/output/step.rs | 77 +- 10 files changed, 2270 insertions(+), 2208 deletions(-) create mode 100644 src/output/dut.rs create mode 100644 src/output/error.rs create mode 100644 src/output/log.rs create mode 100644 src/output/measurement.rs delete mode 100644 src/output/measurement_series.rs delete mode 100644 src/output/objects.rs diff --git a/src/output/dut.rs b/src/output/dut.rs new file mode 100644 index 0000000..89679f2 --- /dev/null +++ b/src/output/dut.rs @@ -0,0 +1,621 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::output as tv; +use tv::models; + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct DutInfo { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfo { + pub fn builder(id: &str) -> DutInfoBuilder { + DutInfoBuilder::new(id) + } + + pub fn new(id: &str) -> DutInfo { + DutInfoBuilder::new(id).build() + } + + pub(crate) fn to_spec(&self) -> models::DutInfoSpec { + models::DutInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + platform_infos: self + .platform_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + software_infos: self + .software_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + hardware_infos: self + .hardware_infos + .clone() + .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + metadata: self.metadata.clone(), + } + } +} + +pub struct DutInfoBuilder { + id: String, + name: Option, + platform_infos: Option>, + software_infos: Option>, + hardware_infos: Option>, + metadata: Option>, +} + +impl DutInfoBuilder { + pub fn new(id: &str) -> DutInfoBuilder { + DutInfoBuilder { + id: id.to_string(), + name: None, + platform_infos: None, + software_infos: None, + hardware_infos: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> DutInfoBuilder { + self.name = Some(value.to_string()); + self + } + + pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { + self.platform_infos = match self.platform_infos { + Some(mut platform_infos) => { + platform_infos.push(platform_info.clone()); + Some(platform_infos) + } + None => Some(vec![platform_info.clone()]), + }; + self + } + + pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.clone()); + Some(software_infos) + } + None => Some(vec![software_info.clone()]), + }; + self + } + + pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { + self.hardware_infos = match self.hardware_infos { + Some(mut hardware_infos) => { + hardware_infos.push(hardware_info.clone()); + Some(hardware_infos) + } + None => Some(vec![hardware_info.clone()]), + }; + self + } + + pub fn add_metadata(mut self, key: &str, value: serde_json::Value) -> DutInfoBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = serde_json::Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> DutInfo { + DutInfo { + id: self.id, + name: self.name, + platform_infos: self.platform_infos, + software_infos: self.software_infos, + hardware_infos: self.hardware_infos, + metadata: self.metadata, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct HardwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfo { + pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { + HardwareInfoBuilder::new(id, name) + } + + pub fn to_spec(&self) -> models::HardwareInfoSpec { + models::HardwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + location: self.location.clone(), + serial_no: self.serial_no.clone(), + part_no: self.part_no.clone(), + manufacturer: self.manufacturer.clone(), + manufacturer_part_no: self.manufacturer_part_no.clone(), + odata_id: self.odata_id.clone(), + computer_system: self.computer_system.clone(), + manager: self.manager.clone(), + } + } + + pub fn id(&self) -> &str { + &self.id + } +} + +#[derive(Debug)] +pub struct HardwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + HardwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + location: None, + serial_no: None, + part_no: None, + manufacturer: None, + manufacturer_part_no: None, + odata_id: None, + computer_system: None, + manager: None, + } + } + pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + self.location = Some(value.to_string()); + self + } + pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + self.serial_no = Some(value.to_string()); + self + } + pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.part_no = Some(value.to_string()); + self + } + pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer = Some(value.to_string()); + self + } + pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer_part_no = Some(value.to_string()); + self + } + pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + self.odata_id = Some(value.to_string()); + self + } + pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + self.manager = Some(value.to_string()); + self + } + + pub fn build(self) -> HardwareInfo { + HardwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + location: self.location, + serial_no: self.serial_no, + part_no: self.part_no, + manufacturer: self.manufacturer, + manufacturer_part_no: self.manufacturer_part_no, + odata_id: self.odata_id, + computer_system: self.computer_system, + manager: self.manager, + } + } +} + +#[derive(Debug, Clone)] +pub struct Subcomponent { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl Subcomponent { + pub fn builder(name: &str) -> SubcomponentBuilder { + SubcomponentBuilder::new(name) + } + pub fn to_spec(&self) -> models::SubcomponentSpec { + models::SubcomponentSpec { + subcomponent_type: self.subcomponent_type.clone(), + name: self.name.clone(), + location: self.location.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + } + } +} + +#[derive(Debug)] +pub struct SubcomponentBuilder { + subcomponent_type: Option, + name: String, + location: Option, + version: Option, + revision: Option, +} + +impl SubcomponentBuilder { + fn new(name: &str) -> Self { + SubcomponentBuilder { + subcomponent_type: None, + name: name.to_string(), + location: None, + version: None, + revision: None, + } + } + pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { + self.subcomponent_type = Some(value); + self + } + pub fn version(mut self, value: &str) -> SubcomponentBuilder { + self.version = Some(value.to_string()); + self + } + pub fn location(mut self, value: &str) -> SubcomponentBuilder { + self.location = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SubcomponentBuilder { + self.revision = Some(value.to_string()); + self + } + + pub fn build(self) -> Subcomponent { + Subcomponent { + subcomponent_type: self.subcomponent_type, + name: self.name, + location: self.location, + version: self.version, + revision: self.revision, + } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PlatformInfo { + info: String, +} + +impl PlatformInfo { + pub fn builder(info: &str) -> PlatformInfoBuilder { + PlatformInfoBuilder::new(info) + } + + pub fn to_spec(&self) -> models::PlatformInfoSpec { + models::PlatformInfoSpec { + info: self.info.clone(), + } + } +} + +#[derive(Debug)] +pub struct PlatformInfoBuilder { + info: String, +} + +impl PlatformInfoBuilder { + fn new(info: &str) -> Self { + PlatformInfoBuilder { + info: info.to_string(), + } + } + + pub fn build(self) -> PlatformInfo { + PlatformInfo { info: self.info } + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct SoftwareInfo { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfo { + pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { + SoftwareInfoBuilder::new(id, name) + } + + pub fn to_spec(&self) -> models::SoftwareInfoSpec { + models::SoftwareInfoSpec { + id: self.id.clone(), + name: self.name.clone(), + version: self.version.clone(), + revision: self.revision.clone(), + software_type: self.software_type.clone(), + computer_system: self.computer_system.clone(), + } + } +} + +#[derive(Debug)] +pub struct SoftwareInfoBuilder { + id: String, + name: String, + version: Option, + revision: Option, + software_type: Option, + computer_system: Option, +} + +impl SoftwareInfoBuilder { + fn new(id: &str, name: &str) -> Self { + SoftwareInfoBuilder { + id: id.to_string(), + name: name.to_string(), + version: None, + revision: None, + software_type: None, + computer_system: None, + } + } + pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { + self.software_type = Some(value); + self + } + pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + + pub fn build(self) -> SoftwareInfo { + SoftwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + software_type: self.software_type, + computer_system: self.computer_system, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output::models; + use anyhow::{bail, Result}; + + #[test] + fn test_dut_creation_from_builder_with_defaults() -> Result<()> { + let dut = DutInfo::builder("1234").build(); + assert_eq!(dut.id, "1234"); + Ok(()) + } + + #[test] + fn test_dut_builder() -> Result<()> { + let platform = PlatformInfo::builder("platform_info").build(); + let software = SoftwareInfo::builder("software_id", "name").build(); + let hardware = HardwareInfo::builder("hardware_id", "name").build(); + let dut = DutInfo::builder("1234") + .name("DUT") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_hardware_info(&hardware) + .add_hardware_info(&hardware) + .add_platform_info(&platform) + .add_platform_info(&platform) + .add_software_info(&software) + .add_software_info(&software) + .build(); + + let spec_dut = dut.to_spec(); + + assert_eq!(spec_dut.id, "1234"); + assert_eq!(spec_dut.name, Some("DUT".to_owned())); + + match spec_dut.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is empty"), + } + + match spec_dut.hardware_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "hardware_id"); + } + _ => bail!("hardware_infos is empty"), + }, + _ => bail!("hardware_infos is missing"), + } + + match spec_dut.software_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "software_id"); + } + _ => bail!("software_infos is empty"), + }, + _ => bail!("software_infos is missing"), + } + + match spec_dut.platform_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.info, "platform_info"); + } + _ => bail!("platform_infos is empty"), + }, + _ => bail!("platform_infos is missing"), + } + + Ok(()) + } + + #[test] + fn test_hardware_info() -> Result<()> { + let info = HardwareInfo::builder("hardware_id", "hardware_name") + .version("version") + .revision("revision") + .location("location") + .serial_no("serial_no") + .part_no("part_no") + .manufacturer("manufacturer") + .manufacturer_part_no("manufacturer_part_no") + .odata_id("odata_id") + .computer_system("computer_system") + .manager("manager") + .build(); + + let spec_hwinfo = info.to_spec(); + + assert_eq!(spec_hwinfo.id, "hardware_id"); + assert_eq!(spec_hwinfo.name, "hardware_name"); + assert_eq!(spec_hwinfo.version, Some("version".to_owned())); + assert_eq!(spec_hwinfo.revision, Some("revision".to_owned())); + assert_eq!(spec_hwinfo.location, Some("location".to_owned())); + assert_eq!(spec_hwinfo.serial_no, Some("serial_no".to_owned())); + assert_eq!(spec_hwinfo.part_no, Some("part_no".to_owned())); + assert_eq!(spec_hwinfo.manufacturer, Some("manufacturer".to_owned())); + assert_eq!( + spec_hwinfo.manufacturer_part_no, + Some("manufacturer_part_no".to_owned()) + ); + assert_eq!(spec_hwinfo.odata_id, Some("odata_id".to_owned())); + assert_eq!( + spec_hwinfo.computer_system, + Some("computer_system".to_owned()) + ); + assert_eq!(spec_hwinfo.manager, Some("manager".to_owned())); + + Ok(()) + } + + #[test] + fn test_software_info() -> Result<()> { + let info = SoftwareInfo::builder("software_id", "name") + .version("version") + .revision("revision") + .software_type(models::SoftwareType::Application) + .computer_system("system") + .build(); + + let spec_swinfo = info.to_spec(); + + assert_eq!(spec_swinfo.id, "software_id"); + assert_eq!(spec_swinfo.name, "name"); + assert_eq!(spec_swinfo.version, Some("version".to_owned())); + assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); + assert_eq!( + spec_swinfo.software_type, + Some(models::SoftwareType::Application) + ); + assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); + + Ok(()) + } + + #[test] + fn test_platform_info() -> Result<()> { + let info = PlatformInfo::builder("info").build(); + + assert_eq!(info.to_spec().info, "info"); + Ok(()) + } + + #[test] + fn test_subcomponent() -> Result<()> { + let sub = Subcomponent::builder("sub_name") + .subcomponent_type(models::SubcomponentType::Asic) + .version("version") + .location("location") + .revision("revision") + .build(); + + let spec_subcomponent = sub.to_spec(); + + assert_eq!(spec_subcomponent.name, "sub_name"); + assert_eq!(spec_subcomponent.version, Some("version".to_owned())); + assert_eq!(spec_subcomponent.revision, Some("revision".to_owned())); + assert_eq!(spec_subcomponent.location, Some("location".to_owned())); + assert_eq!( + spec_subcomponent.subcomponent_type, + Some(models::SubcomponentType::Asic) + ); + + Ok(()) + } +} diff --git a/src/output/emitters.rs b/src/output/emitters.rs index f33bb71..d818b65 100644 --- a/src/output/emitters.rs +++ b/src/output/emitters.rs @@ -127,13 +127,13 @@ impl JsonEmitter { #[cfg(test)] mod tests { - use anyhow::anyhow; - use anyhow::Result; + use anyhow::{anyhow, Result}; use assert_json_diff::assert_json_include; use serde_json::json; use super::*; - use crate::output::objects::*; + use crate::output as tv; + use tv::run::SchemaVersion; #[tokio::test] async fn test_emit_using_buffer_writer() -> Result<()> { diff --git a/src/output/error.rs b/src/output/error.rs new file mode 100644 index 0000000..d61e9a0 --- /dev/null +++ b/src/output/error.rs @@ -0,0 +1,232 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::output as tv; +use tv::{dut, models, run::ArtifactContext}; + +pub struct Error { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl Error { + pub fn builder(symptom: &str) -> ErrorBuilder { + ErrorBuilder::new(symptom) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct ErrorBuilder { + symptom: String, + message: Option, + software_infos: Option>, + source_location: Option, +} + +impl ErrorBuilder { + fn new(symptom: &str) -> Self { + ErrorBuilder { + symptom: symptom.to_string(), + message: None, + source_location: None, + software_infos: None, + } + } + pub fn message(mut self, value: &str) -> ErrorBuilder { + self.message = Some(value.to_string()); + self + } + pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + pub fn add_software_info(mut self, software_info: &dut::SoftwareInfo) -> ErrorBuilder { + self.software_infos = match self.software_infos { + Some(mut software_infos) => { + software_infos.push(software_info.to_spec()); + Some(software_infos) + } + None => Some(vec![software_info.to_spec()]), + }; + self + } + + pub fn build(self) -> Error { + Error { + symptom: self.symptom, + message: self.message, + source_location: self.source_location, + software_infos: self.software_infos, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use assert_json_diff::assert_json_include; + + use super::*; + use crate::output as tv; + use tv::{dut, models}; + + #[test] + fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") + .message("") + .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + + let artifact = error.to_artifact(ArtifactContext::TestRun); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { + let error = Error::builder("symptom") + .message("") + .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .source("", 1) + .build(); + + let artifact = error.to_artifact(ArtifactContext::TestStep); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_error() -> Result<()> { + let expected_run = serde_json::json!({ + "testRunArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file": "file.rs", "line": 1}, + "symptom": "symptom" + } + } + }); + let expected_step = serde_json::json!({ + "testStepArtifact": { + "error": { + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + } + ], + "sourceLocation": {"file":"file.rs","line":1}, + "symptom":"symptom" + } + } + }); + + let software = dut::SoftwareInfo::builder("software_id", "name").build(); + let error = ErrorBuilder::new("symptom") + .message("message") + .source("file.rs", 1) + .add_software_info(&software) + .add_software_info(&software) + .build(); + + let spec_error = error.to_artifact(ArtifactContext::TestRun); + let actual = serde_json::json!(spec_error); + assert_json_include!(actual: actual, expected: &expected_run); + + let spec_error = error.to_artifact(ArtifactContext::TestStep); + let actual = serde_json::json!(spec_error); + assert_json_include!(actual: actual, expected: &expected_step); + + Ok(()) + } +} diff --git a/src/output/log.rs b/src/output/log.rs new file mode 100644 index 0000000..f76af11 --- /dev/null +++ b/src/output/log.rs @@ -0,0 +1,130 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::output as tv; +use tv::{models, run::ArtifactContext}; + +pub struct Log { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl Log { + pub fn builder(message: &str) -> LogBuilder { + LogBuilder::new(message) + } + + pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { + match context { + ArtifactContext::TestRun => { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + ArtifactContext::TestStep => { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), + }), + }) + } + } + } +} + +#[derive(Debug)] +pub struct LogBuilder { + severity: models::LogSeverity, + message: String, + source_location: Option, +} + +impl LogBuilder { + fn new(message: &str) -> Self { + LogBuilder { + severity: models::LogSeverity::Info, + message: message.to_string(), + source_location: None, + } + } + pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { + self.severity = value; + self + } + pub fn source(mut self, file: &str, line: i32) -> LogBuilder { + self.source_location = Some(models::SourceLocationSpec { + file: file.to_string(), + line, + }); + self + } + + pub fn build(self) -> Log { + Log { + severity: self.severity, + message: self.message, + source_location: self.source_location, + } + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::output as tv; + use tv::models; + + #[test] + fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) + .build(); + + let artifact = log.to_artifact(ArtifactContext::TestRun); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) + ); + + Ok(()) + } + + #[test] + fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { + let log = Log::builder("test") + .severity(models::LogSeverity::Info) + .build(); + + let artifact = log.to_artifact(ArtifactContext::TestStep); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }), + }) + ); + + Ok(()) + } +} diff --git a/src/output/measurement.rs b/src/output/measurement.rs new file mode 100644 index 0000000..c641d8b --- /dev/null +++ b/src/output/measurement.rs @@ -0,0 +1,1009 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::future::Future; +use std::sync::atomic; +use std::sync::Arc; + +use chrono::DateTime; +use serde_json::Map; +use serde_json::Value; +use tokio::sync::Mutex; + +use crate::output as tv; +use tv::{dut, emitters, models, state}; + +/// The measurement series. +/// A Measurement Series is a time-series list of measurements. +/// +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +pub struct MeasurementSeries { + state: Arc>, + seq_no: Arc>, + start: MeasurementSeriesStart, +} + +impl MeasurementSeries { + pub(crate) fn new(series_id: &str, name: &str, state: Arc>) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start: MeasurementSeriesStart::new(name, series_id), + } + } + + pub(crate) fn new_with_details( + start: MeasurementSeriesStart, + state: Arc>, + ) -> Self { + Self { + state, + seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + start, + } + } + + async fn current_sequence_no(&self) -> u64 { + self.seq_no.lock().await.load(atomic::Ordering::SeqCst) + } + + async fn increment_sequence_no(&self) { + self.seq_no + .lock() + .await + .fetch_add(1, atomic::Ordering::SeqCst); + } + + /// Starts the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn start(&self) -> Result<(), emitters::WriterError> { + self.state + .lock() + .await + .emitter + .emit(&self.start.to_artifact()) + .await?; + Ok(()) + } + + /// Ends the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.end().await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn end(&self) -> Result<(), emitters::WriterError> { + let end = + MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); + self.state + .lock() + .await + .emitter + .emit(&end.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement(60.into()).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + let element = MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + None, + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Adds a measurement element to the measurement series. + /// This method accepts additional metadata to add to the element. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn add_measurement_with_metadata( + &self, + value: Value, + metadata: Vec<(&str, Value)>, + ) -> Result<(), emitters::WriterError> { + let element = MeasurementSeriesElement::new( + self.current_sequence_no().await, + value, + &self.start, + Some(Map::from_iter( + metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), + )), + ); + self.increment_sequence_no().await; + self.state + .lock() + .await + .emitter + .emit(&element.to_artifact()) + .await?; + Ok(()) + } + + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.step("step_name").start().await?; + /// + /// let series = step.measurement_series("name"); + /// series.start().await?; + /// series.scope(|s| async { + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; + /// Ok(()) + /// }).await?; + /// + /// # Ok::<(), WriterError>(()) + /// # }); + /// ``` + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + where + R: Future>, + F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + { + self.start().await?; + func(self).await?; + self.end().await?; + Ok(()) + } +} + +#[derive(Clone)] +pub struct Validator { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl Validator { + pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { + ValidatorBuilder::new(validator_type, value) + } + pub fn to_spec(&self) -> models::ValidatorSpec { + models::ValidatorSpec { + name: self.name.clone(), + validator_type: self.validator_type.clone(), + value: self.value.clone(), + metadata: self.metadata.clone(), + } + } +} + +#[derive(Debug)] +pub struct ValidatorBuilder { + name: Option, + validator_type: models::ValidatorType, + value: Value, + metadata: Option>, +} + +impl ValidatorBuilder { + fn new(validator_type: models::ValidatorType, value: Value) -> Self { + ValidatorBuilder { + validator_type, + value: value.clone(), + name: None, + metadata: None, + } + } + pub fn name(mut self, value: &str) -> ValidatorBuilder { + self.name = Some(value.to_string()); + self + } + pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> Validator { + Validator { + name: self.name, + validator_type: self.validator_type, + value: self.value, + metadata: self.metadata, + } + } +} + +/// This structure represents a Measurement message. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// +/// # Examples +/// +/// ## Create a Measurement object with the `new` method +/// +/// ``` +/// use ocptv::output::Measurement; +/// use ocptv::output::Value; +/// +/// let measurement = Measurement::new("name", 50.into()); +/// ``` +/// +/// ## Create a Measurement object with the `builder` method +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; +/// +/// let measurement = Measurement::builder("name", 50.into()) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) +/// .subcomponent(&Subcomponent::builder("name").build()) +/// .build(); +/// ``` +pub struct Measurement { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl Measurement { + /// Builds a new Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::new("name", 50.into()); + /// ``` + pub fn new(name: &str, value: Value) -> Self { + Measurement { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Builds a new Measurement object using [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::Measurement; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::builder("name", 50.into()) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_metadata("key", "value".into()) + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .build(); + /// ``` + pub fn builder(name: &str, value: Value) -> MeasurementBuilder { + MeasurementBuilder::new(name, value) + } + + /// Creates an artifact from a Measurement object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Measurement; + /// use ocptv::output::Value; + /// + /// let measurement = Measurement::new("name", 50.into()); + /// let _ = measurement.to_artifact(); + /// ``` + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { + name: self.name.clone(), + unit: self.unit.clone(), + value: self.value.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info_id: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.id().to_owned()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }), + }) + } +} + +/// This structure builds a [`Measurement`] object. +/// +/// # Examples +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Measurement; +/// use ocptv::output::MeasurementBuilder; +/// use ocptv::output::Subcomponent; +/// use ocptv::output::Validator; +/// use ocptv::output::ValidatorType; +/// use ocptv::output::Value; +/// +/// let builder = MeasurementBuilder::new("name", 50.into()) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_metadata("key", "value".into()) +/// .subcomponent(&Subcomponent::builder("name").build()); +/// let measurement = builder.build(); +/// ``` +pub struct MeasurementBuilder { + name: String, + value: Value, + unit: Option, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementBuilder { + /// Creates a new MeasurementBuilder. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()); + /// ``` + pub fn new(name: &str, value: Value) -> Self { + MeasurementBuilder { + name: name.to_string(), + value: value.clone(), + unit: None, + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + /// Add a [`Validator`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Validator; + /// use ocptv::output::ValidatorType; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()) + /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); + /// ``` + pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { + self.validators = match self.validators { + Some(mut validators) => { + validators.push(validator.clone()); + Some(validators) + } + None => Some(vec![validator.clone()]), + }; + self + } + + /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()); + /// ``` + pub fn hardware_info(mut self, hardware_info: &dut::HardwareInfo) -> MeasurementBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + /// Add a [`Subcomponent`] to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Subcomponent; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()) + /// .subcomponent(&Subcomponent::builder("name").build()); + /// ``` + pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> MeasurementBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + /// Add custom metadata to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = + /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); + /// ``` + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { + match self.metadata { + Some(ref mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + } + None => { + let mut entries = serde_json::Map::new(); + entries.insert(key.to_owned(), value); + self.metadata = Some(entries); + } + }; + self + } + + /// Add measurement unit to a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); + /// ``` + pub fn unit(mut self, unit: &str) -> MeasurementBuilder { + self.unit = Some(unit.to_string()); + self + } + + /// Builds a [`Measurement`] object from a [`MeasurementBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::MeasurementBuilder; + /// use ocptv::output::Value; + /// + /// let builder = MeasurementBuilder::new("name", 50.into()); + /// let measurement = builder.build(); + /// ``` + pub fn build(self) -> Measurement { + Measurement { + name: self.name, + value: self.value, + unit: self.unit, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesStart { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStart { + pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + + pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { + MeasurementSeriesStartBuilder::new(name, series_id) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: self.name.clone(), + unit: self.unit.clone(), + series_id: self.series_id.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.to_spec()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + }, + ), + }) + } + + pub fn get_series_id(&self) -> &str { + &self.series_id + } +} + +pub struct MeasurementSeriesStartBuilder { + name: String, + unit: Option, + series_id: String, + validators: Option>, + hardware_info: Option, + subcomponent: Option, + metadata: Option>, +} + +impl MeasurementSeriesStartBuilder { + pub fn new(name: &str, series_id: &str) -> Self { + MeasurementSeriesStartBuilder { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + } + pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { + self.validators = match self.validators { + Some(mut validators) => { + validators.push(validator.clone()); + Some(validators) + } + None => Some(vec![validator.clone()]), + }; + self + } + + pub fn hardware_info( + mut self, + hardware_info: &dut::HardwareInfo, + ) -> MeasurementSeriesStartBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + pub fn subcomponent( + mut self, + subcomponent: &dut::Subcomponent, + ) -> MeasurementSeriesStartBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { + self.unit = Some(unit.to_string()); + self + } + + pub fn build(self) -> MeasurementSeriesStart { + MeasurementSeriesStart { + name: self.name, + unit: self.unit, + series_id: self.series_id, + validators: self.validators, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + metadata: self.metadata, + } + } +} + +pub struct MeasurementSeriesEnd { + series_id: String, + total_count: u64, +} + +impl MeasurementSeriesEnd { + pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { + MeasurementSeriesEnd { + series_id: series_id.to_string(), + total_count, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: self.series_id.clone(), + total_count: self.total_count, + }, + ), + }) + } +} + +pub struct MeasurementSeriesElement { + index: u64, + value: Value, + timestamp: DateTime, + series_id: String, + metadata: Option>, +} + +impl MeasurementSeriesElement { + pub(crate) fn new( + index: u64, + value: Value, + series: &MeasurementSeriesStart, + metadata: Option>, + ) -> MeasurementSeriesElement { + MeasurementSeriesElement { + index, + value: value.clone(), + timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + series_id: series.series_id.to_string(), + metadata, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( + models::MeasurementSeriesElementSpec { + index: self.index, + value: self.value.clone(), + timestamp: self.timestamp, + series_id: self.series_id.clone(), + metadata: self.metadata.clone(), + }, + ), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output as tv; + use tv::ValidatorType; + use tv::{dut::*, models}; + + use anyhow::{bail, Result}; + + #[test] + fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let value = Value::from(50); + let measurement = Measurement::new(&name, value.clone()); + + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let value = Value::from(50000); + let hardware_info = HardwareInfo::builder("id", "name").build(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + + let meta_key = "key"; + let meta_value = Value::from("value"); + let mut metadata = Map::new(); + metadata.insert(meta_key.to_string(), meta_value.clone()); + metadata.insert(meta_key.to_string(), meta_value.clone()); + + let subcomponent = Subcomponent::builder("name").build(); + + let unit = "RPM"; + let measurement = Measurement::builder(&name, value.clone()) + .hardware_info(&hardware_info) + .add_validator(&validator) + .add_validator(&validator) + .add_metadata(meta_key, meta_value.clone()) + .add_metadata(meta_key, meta_value.clone()) + .subcomponent(&subcomponent) + .unit(unit) + .build(); + + let artifact = measurement.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Measurement( + models::MeasurementSpec { + name, + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_start_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesStart::new(&name, &series_id); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_start_builder_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let series_id = "series_id".to_owned(); + let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); + let hw_info = HardwareInfo::builder("id", "name").build(); + let subcomponent = Subcomponent::builder("name").build(); + let series = MeasurementSeriesStart::builder(&name, &series_id) + .unit("unit") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_validator(&validator) + .add_validator(&validator2) + .hardware_info(&hw_info) + .subcomponent(&subcomponent) + .build(); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( + models::MeasurementSeriesStartSpec { + name, + unit: Some("unit".to_string()), + series_id: series_id.to_string(), + validators: Some(vec![validator.to_spec(), validator2.to_spec()]), + hardware_info: Some(hw_info.to_spec()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(serde_json::Map::from_iter([ + ("key".to_string(), "value".into()), + ("key2".to_string(), "value2".into()) + ])), + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_measurement_series_end_to_artifact() -> Result<()> { + let series_id = "series_id".to_owned(); + let series = MeasurementSeriesEnd::new(&series_id, 1); + + let artifact = series.to_artifact(); + assert_eq!( + artifact, + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( + models::MeasurementSeriesEndSpec { + series_id: series_id.to_string(), + total_count: 1, + } + ), + }) + ); + + Ok(()) + } + + #[test] + fn test_validator() -> Result<()> { + let validator = Validator::builder(ValidatorType::Equal, 30.into()) + .name("validator") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .build(); + + let spec_validator = validator.to_spec(); + + assert_eq!(spec_validator.name, Some("validator".to_owned())); + assert_eq!(spec_validator.value, 30); + assert_eq!(spec_validator.validator_type, ValidatorType::Equal); + + match spec_validator.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is none"), + } + + Ok(()) + } +} diff --git a/src/output/measurement_series.rs b/src/output/measurement_series.rs deleted file mode 100644 index c7372c2..0000000 --- a/src/output/measurement_series.rs +++ /dev/null @@ -1,241 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use std::future::Future; -use std::sync::atomic; -use std::sync::Arc; - -use serde_json::Map; -use serde_json::Value; -use tokio::sync::Mutex; - -use crate::output as tv; -use tv::{emitters, objects, state}; - -/// The measurement series. -/// A Measurement Series is a time-series list of measurements. -/// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -pub struct MeasurementSeries { - state: Arc>, - seq_no: Arc>, - start: objects::MeasurementSeriesStart, -} - -impl MeasurementSeries { - pub fn new(series_id: &str, name: &str, state: Arc>) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start: objects::MeasurementSeriesStart::new(name, series_id), - } - } - - pub fn new_with_details( - start: objects::MeasurementSeriesStart, - state: Arc>, - ) -> Self { - Self { - state, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), - start, - } - } - - async fn current_sequence_no(&self) -> u64 { - self.seq_no.lock().await.load(atomic::Ordering::SeqCst) - } - - async fn increment_sequence_no(&self) { - self.seq_no - .lock() - .await - .fetch_add(1, atomic::Ordering::SeqCst); - } - - /// Starts the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { - self.state - .lock() - .await - .emitter - .emit(&self.start.to_artifact()) - .await?; - Ok(()) - } - - /// Ends the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.end().await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { - let end = objects::MeasurementSeriesEnd::new( - self.start.get_series_id(), - self.current_sequence_no().await, - ); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement(60.into()).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - None, - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Adds a measurement element to the measurement series. - /// This method accepts additional metadata to add to the element. - /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn add_measurement_with_metadata( - &self, - value: Value, - metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { - let element = objects::MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - Some(Map::from_iter( - metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), - )), - ); - self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) - .await?; - Ok(()) - } - - /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. - /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees - /// all the messages are emitted between the start and end messages, the order - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.scope(|s| async { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; - /// Ok(()) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'a MeasurementSeries) -> R, - { - self.start().await?; - func(self).await?; - self.end().await?; - Ok(()) - } -} diff --git a/src/output/mod.rs b/src/output/mod.rs index c0d0a6e..d296c57 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -5,23 +5,29 @@ // https://opensource.org/licenses/MIT. mod config; +mod dut; mod emitters; +mod error; +mod log; mod macros; -mod measurement_series; +mod measurement; mod models; -mod objects; mod run; mod state; mod step; pub use config::*; +pub use dut::*; pub use emitters::*; +pub use error::*; +pub use log::*; +pub use measurement::*; pub use models::LogSeverity; pub use models::TestResult; pub use models::TestStatus; pub use models::ValidatorType; pub use models::SPEC_VERSION; -pub use objects::*; pub use run::*; -pub use serde_json::Value; pub use step::*; + +pub use serde_json::Value; diff --git a/src/output/objects.rs b/src/output/objects.rs deleted file mode 100644 index 8aa115c..0000000 --- a/src/output/objects.rs +++ /dev/null @@ -1,1920 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use chrono::DateTime; -use serde_json::Map; -use serde_json::Value; - -use crate::output::models; - -pub enum ArtifactContext { - TestRun, - TestStep, -} - -pub struct SchemaVersion { - major: i8, - minor: i8, -} - -#[allow(clippy::new_without_default)] -impl SchemaVersion { - pub fn new() -> SchemaVersion { - SchemaVersion { - major: models::SPEC_VERSION.0, - minor: models::SPEC_VERSION.1, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { - major: self.major, - minor: self.minor, - }) - } -} - -#[derive(Default, Debug, Clone, PartialEq)] -pub struct DutInfo { - id: String, - name: Option, - platform_infos: Option>, - software_infos: Option>, - hardware_infos: Option>, - metadata: Option>, -} - -impl DutInfo { - pub fn builder(id: &str) -> DutInfoBuilder { - DutInfoBuilder::new(id) - } - - pub fn new(id: &str) -> DutInfo { - DutInfoBuilder::new(id).build() - } - - pub(crate) fn to_spec(&self) -> models::DutInfoSpec { - models::DutInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - platform_infos: self - .platform_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - software_infos: self - .software_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - hardware_infos: self - .hardware_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - metadata: self.metadata.clone(), - } - } -} - -pub struct DutInfoBuilder { - id: String, - name: Option, - platform_infos: Option>, - software_infos: Option>, - hardware_infos: Option>, - metadata: Option>, -} - -impl DutInfoBuilder { - pub fn new(id: &str) -> DutInfoBuilder { - DutInfoBuilder { - id: id.to_string(), - name: None, - platform_infos: None, - software_infos: None, - hardware_infos: None, - metadata: None, - } - } - pub fn name(mut self, value: &str) -> DutInfoBuilder { - self.name = Some(value.to_string()); - self - } - - pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { - self.platform_infos = match self.platform_infos { - Some(mut platform_infos) => { - platform_infos.push(platform_info.clone()); - Some(platform_infos) - } - None => Some(vec![platform_info.clone()]), - }; - self - } - - pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.clone()); - Some(software_infos) - } - None => Some(vec![software_info.clone()]), - }; - self - } - - pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { - self.hardware_infos = match self.hardware_infos { - Some(mut hardware_infos) => { - hardware_infos.push(hardware_info.clone()); - Some(hardware_infos) - } - None => Some(vec![hardware_info.clone()]), - }; - self - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> DutInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> DutInfo { - DutInfo { - id: self.id, - name: self.name, - platform_infos: self.platform_infos, - software_infos: self.software_infos, - hardware_infos: self.hardware_infos, - metadata: self.metadata, - } - } -} - -pub struct TestRunStart { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: DutInfo, -} - -impl TestRunStart { - pub fn builder( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { - name: self.name.clone(), - version: self.version.clone(), - command_line: self.command_line.clone(), - parameters: self.parameters.clone(), - metadata: self.metadata.clone(), - dut_info: self.dut_info.to_spec(), - }), - }) - } -} - -pub struct TestRunStartBuilder { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: DutInfo, -} - -impl TestRunStartBuilder { - pub fn new( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder { - name: name.to_string(), - version: version.to_string(), - command_line: command_line.to_string(), - parameters: parameters.clone(), - metadata: None, - dut_info: dut_info.clone(), - } - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRunStart { - TestRunStart { - name: self.name, - version: self.version, - command_line: self.command_line, - parameters: self.parameters, - metadata: self.metadata, - dut_info: self.dut_info, - } - } -} - -pub struct TestRunEnd { - status: models::TestStatus, - result: models::TestResult, -} - -impl TestRunEnd { - pub fn builder() -> TestRunEndBuilder { - TestRunEndBuilder::new() - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { - status: self.status.clone(), - result: self.result.clone(), - }), - }) - } -} - -#[derive(Debug)] -pub struct TestRunEndBuilder { - status: models::TestStatus, - result: models::TestResult, -} - -#[allow(clippy::new_without_default)] -impl TestRunEndBuilder { - pub fn new() -> TestRunEndBuilder { - TestRunEndBuilder { - status: models::TestStatus::Complete, - result: models::TestResult::Pass, - } - } - pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { - self.status = value; - self - } - - pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { - self.result = value; - self - } - - pub fn build(self) -> TestRunEnd { - TestRunEnd { - status: self.status, - result: self.result, - } - } -} - -pub struct Log { - severity: models::LogSeverity, - message: String, - source_location: Option, -} - -impl Log { - pub fn builder(message: &str) -> LogBuilder { - LogBuilder::new(message) - } - - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - } - } -} - -#[derive(Debug)] -pub struct LogBuilder { - severity: models::LogSeverity, - message: String, - source_location: Option, -} - -impl LogBuilder { - fn new(message: &str) -> Self { - LogBuilder { - severity: models::LogSeverity::Info, - message: message.to_string(), - source_location: None, - } - } - pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { - self.severity = value; - self - } - pub fn source(mut self, file: &str, line: i32) -> LogBuilder { - self.source_location = Some(models::SourceLocationSpec { - file: file.to_string(), - line, - }); - self - } - - pub fn build(self) -> Log { - Log { - severity: self.severity, - message: self.message, - source_location: self.source_location, - } - } -} - -pub struct Error { - symptom: String, - message: Option, - software_infos: Option>, - source_location: Option, -} - -impl Error { - pub fn builder(symptom: &str) -> ErrorBuilder { - ErrorBuilder::new(symptom) - } - - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - } - } -} - -#[derive(Debug)] -pub struct ErrorBuilder { - symptom: String, - message: Option, - software_infos: Option>, - source_location: Option, -} - -impl ErrorBuilder { - fn new(symptom: &str) -> Self { - ErrorBuilder { - symptom: symptom.to_string(), - message: None, - source_location: None, - software_infos: None, - } - } - pub fn message(mut self, value: &str) -> ErrorBuilder { - self.message = Some(value.to_string()); - self - } - pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { - self.source_location = Some(models::SourceLocationSpec { - file: file.to_string(), - line, - }); - self - } - pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> ErrorBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.to_spec()); - Some(software_infos) - } - None => Some(vec![software_info.to_spec()]), - }; - self - } - - pub fn build(self) -> Error { - Error { - symptom: self.symptom, - message: self.message, - source_location: self.source_location, - software_infos: self.software_infos, - } - } -} - -pub struct TestStepStart { - name: String, -} - -impl TestStepStart { - pub fn new(name: &str) -> TestStepStart { - TestStepStart { - name: name.to_string(), - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepStart( - models::TestStepStartSpec { - name: self.name.clone(), - }, - ), - }) - } -} - -pub struct TestStepEnd { - status: models::TestStatus, -} - -impl TestStepEnd { - pub fn new(status: models::TestStatus) -> TestStepEnd { - TestStepEnd { status } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { - status: self.status.clone(), - }), - }) - } -} - -#[derive(Clone)] -pub struct Validator { - name: Option, - validator_type: models::ValidatorType, - value: Value, - metadata: Option>, -} - -impl Validator { - pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { - ValidatorBuilder::new(validator_type, value) - } - pub fn to_spec(&self) -> models::ValidatorSpec { - models::ValidatorSpec { - name: self.name.clone(), - validator_type: self.validator_type.clone(), - value: self.value.clone(), - metadata: self.metadata.clone(), - } - } -} - -#[derive(Debug)] -pub struct ValidatorBuilder { - name: Option, - validator_type: models::ValidatorType, - value: Value, - metadata: Option>, -} - -impl ValidatorBuilder { - fn new(validator_type: models::ValidatorType, value: Value) -> Self { - ValidatorBuilder { - validator_type, - value: value.clone(), - name: None, - metadata: None, - } - } - pub fn name(mut self, value: &str) -> ValidatorBuilder { - self.name = Some(value.to_string()); - self - } - pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> Validator { - Validator { - name: self.name, - validator_type: self.validator_type, - value: self.value, - metadata: self.metadata, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct HardwareInfo { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfo { - pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { - HardwareInfoBuilder::new(id, name) - } - pub fn to_spec(&self) -> models::HardwareInfoSpec { - models::HardwareInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - location: self.location.clone(), - serial_no: self.serial_no.clone(), - part_no: self.part_no.clone(), - manufacturer: self.manufacturer.clone(), - manufacturer_part_no: self.manufacturer_part_no.clone(), - odata_id: self.odata_id.clone(), - computer_system: self.computer_system.clone(), - manager: self.manager.clone(), - } - } -} - -#[derive(Debug)] -pub struct HardwareInfoBuilder { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { - HardwareInfoBuilder { - id: id.to_string(), - name: name.to_string(), - version: None, - revision: None, - location: None, - serial_no: None, - part_no: None, - manufacturer: None, - manufacturer_part_no: None, - odata_id: None, - computer_system: None, - manager: None, - } - } - pub fn version(mut self, value: &str) -> HardwareInfoBuilder { - self.version = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { - self.revision = Some(value.to_string()); - self - } - pub fn location(mut self, value: &str) -> HardwareInfoBuilder { - self.location = Some(value.to_string()); - self - } - pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { - self.serial_no = Some(value.to_string()); - self - } - pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.part_no = Some(value.to_string()); - self - } - pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer = Some(value.to_string()); - self - } - pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer_part_no = Some(value.to_string()); - self - } - pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { - self.odata_id = Some(value.to_string()); - self - } - pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { - self.computer_system = Some(value.to_string()); - self - } - pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { - self.manager = Some(value.to_string()); - self - } - - pub fn build(self) -> HardwareInfo { - HardwareInfo { - id: self.id, - name: self.name, - version: self.version, - revision: self.revision, - location: self.location, - serial_no: self.serial_no, - part_no: self.part_no, - manufacturer: self.manufacturer, - manufacturer_part_no: self.manufacturer_part_no, - odata_id: self.odata_id, - computer_system: self.computer_system, - manager: self.manager, - } - } -} - -#[derive(Debug, Clone)] -pub struct Subcomponent { - subcomponent_type: Option, - name: String, - location: Option, - version: Option, - revision: Option, -} - -impl Subcomponent { - pub fn builder(name: &str) -> SubcomponentBuilder { - SubcomponentBuilder::new(name) - } - pub fn to_spec(&self) -> models::SubcomponentSpec { - models::SubcomponentSpec { - subcomponent_type: self.subcomponent_type.clone(), - name: self.name.clone(), - location: self.location.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - } - } -} - -#[derive(Debug)] -pub struct SubcomponentBuilder { - subcomponent_type: Option, - name: String, - location: Option, - version: Option, - revision: Option, -} - -impl SubcomponentBuilder { - fn new(name: &str) -> Self { - SubcomponentBuilder { - subcomponent_type: None, - name: name.to_string(), - location: None, - version: None, - revision: None, - } - } - pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { - self.subcomponent_type = Some(value); - self - } - pub fn version(mut self, value: &str) -> SubcomponentBuilder { - self.version = Some(value.to_string()); - self - } - pub fn location(mut self, value: &str) -> SubcomponentBuilder { - self.location = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> SubcomponentBuilder { - self.revision = Some(value.to_string()); - self - } - - pub fn build(self) -> Subcomponent { - Subcomponent { - subcomponent_type: self.subcomponent_type, - name: self.name, - location: self.location, - version: self.version, - revision: self.revision, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct PlatformInfo { - info: String, -} - -impl PlatformInfo { - pub fn builder(info: &str) -> PlatformInfoBuilder { - PlatformInfoBuilder::new(info) - } - - pub fn to_spec(&self) -> models::PlatformInfoSpec { - models::PlatformInfoSpec { - info: self.info.clone(), - } - } -} - -#[derive(Debug)] -pub struct PlatformInfoBuilder { - info: String, -} - -impl PlatformInfoBuilder { - fn new(info: &str) -> Self { - PlatformInfoBuilder { - info: info.to_string(), - } - } - - pub fn build(self) -> PlatformInfo { - PlatformInfo { info: self.info } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct SoftwareInfo { - id: String, - name: String, - version: Option, - revision: Option, - software_type: Option, - computer_system: Option, -} - -impl SoftwareInfo { - pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { - SoftwareInfoBuilder::new(id, name) - } - - pub fn to_spec(&self) -> models::SoftwareInfoSpec { - models::SoftwareInfoSpec { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - software_type: self.software_type.clone(), - computer_system: self.computer_system.clone(), - } - } -} - -#[derive(Debug)] -pub struct SoftwareInfoBuilder { - id: String, - name: String, - version: Option, - revision: Option, - software_type: Option, - computer_system: Option, -} - -impl SoftwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { - SoftwareInfoBuilder { - id: id.to_string(), - name: name.to_string(), - version: None, - revision: None, - software_type: None, - computer_system: None, - } - } - pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { - self.version = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { - self.revision = Some(value.to_string()); - self - } - pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { - self.software_type = Some(value); - self - } - pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { - self.computer_system = Some(value.to_string()); - self - } - - pub fn build(self) -> SoftwareInfo { - SoftwareInfo { - id: self.id, - name: self.name, - version: self.version, - revision: self.revision, - software_type: self.software_type, - computer_system: self.computer_system, - } - } -} - -/// This structure represents a Measurement message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement -/// -/// # Examples -/// -/// ## Create a Measurement object with the `new` method -/// -/// ``` -/// use ocptv::output::Measurement; -/// use ocptv::output::Value; -/// -/// let measurement = Measurement::new("name", 50.into()); -/// ``` -/// -/// ## Create a Measurement object with the `builder` method -/// -/// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; -/// -/// let measurement = Measurement::builder("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) -/// .subcomponent(&Subcomponent::builder("name").build()) -/// .build(); -/// ``` -pub struct Measurement { - name: String, - value: Value, - unit: Option, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl Measurement { - /// Builds a new Measurement object. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::new("name", 50.into()); - /// ``` - pub fn new(name: &str, value: Value) -> Self { - Measurement { - name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - /// Builds a new Measurement object using [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::Measurement; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::builder("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) - /// .subcomponent(&Subcomponent::builder("name").build()) - /// .build(); - /// ``` - pub fn builder(name: &str, value: Value) -> MeasurementBuilder { - MeasurementBuilder::new(name, value) - } - - /// Creates an artifact from a Measurement object. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// - /// let measurement = Measurement::new("name", 50.into()); - /// let _ = measurement.to_artifact(); - /// ``` - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { - name: self.name.clone(), - unit: self.unit.clone(), - value: self.value.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info_id: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.id.clone()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }), - }) - } -} - -/// This structure builds a [`Measurement`] object. -/// -/// # Examples -/// -/// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::MeasurementBuilder; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; -/// -/// let builder = MeasurementBuilder::new("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) -/// .subcomponent(&Subcomponent::builder("name").build()); -/// let measurement = builder.build(); -/// ``` -pub struct MeasurementBuilder { - name: String, - value: Value, - unit: Option, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl MeasurementBuilder { - /// Creates a new MeasurementBuilder. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); - /// ``` - pub fn new(name: &str, value: Value) -> Self { - MeasurementBuilder { - name: name.to_string(), - value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - /// Add a [`Validator`] to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); - /// ``` - pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; - self - } - - /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()); - /// ``` - pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementBuilder { - self.hardware_info = Some(hardware_info.clone()); - self - } - - /// Add a [`Subcomponent`] to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .subcomponent(&Subcomponent::builder("name").build()); - /// ``` - pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementBuilder { - self.subcomponent = Some(subcomponent.clone()); - self - } - - /// Add custom metadata to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = - /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); - /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { - match self.metadata { - Some(ref mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - } - None => { - let mut entries = serde_json::Map::new(); - entries.insert(key.to_owned(), value); - self.metadata = Some(entries); - } - }; - self - } - - /// Add measurement unit to a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); - /// ``` - pub fn unit(mut self, unit: &str) -> MeasurementBuilder { - self.unit = Some(unit.to_string()); - self - } - - /// Builds a [`Measurement`] object from a [`MeasurementBuilder`]. - /// - /// # Examples - /// - /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); - /// let measurement = builder.build(); - /// ``` - pub fn build(self) -> Measurement { - Measurement { - name: self.name, - value: self.value, - unit: self.unit, - validators: self.validators, - hardware_info: self.hardware_info, - subcomponent: self.subcomponent, - metadata: self.metadata, - } - } -} - -pub struct MeasurementSeriesStart { - name: String, - unit: Option, - series_id: String, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl MeasurementSeriesStart { - pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { - MeasurementSeriesStart { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - - pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { - MeasurementSeriesStartBuilder::new(name, series_id) - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name: self.name.clone(), - unit: self.unit.clone(), - series_id: self.series_id.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.to_spec()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }, - ), - }) - } - - pub fn get_series_id(&self) -> &str { - &self.series_id - } -} - -pub struct MeasurementSeriesStartBuilder { - name: String, - unit: Option, - series_id: String, - validators: Option>, - hardware_info: Option, - subcomponent: Option, - metadata: Option>, -} - -impl MeasurementSeriesStartBuilder { - pub fn new(name: &str, series_id: &str) -> Self { - MeasurementSeriesStartBuilder { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - } - pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; - self - } - - pub fn hardware_info(mut self, hardware_info: &HardwareInfo) -> MeasurementSeriesStartBuilder { - self.hardware_info = Some(hardware_info.clone()); - self - } - - pub fn subcomponent(mut self, subcomponent: &Subcomponent) -> MeasurementSeriesStartBuilder { - self.subcomponent = Some(subcomponent.clone()); - self - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { - self.unit = Some(unit.to_string()); - self - } - - pub fn build(self) -> MeasurementSeriesStart { - MeasurementSeriesStart { - name: self.name, - unit: self.unit, - series_id: self.series_id, - validators: self.validators, - hardware_info: self.hardware_info, - subcomponent: self.subcomponent, - metadata: self.metadata, - } - } -} - -pub struct MeasurementSeriesEnd { - series_id: String, - total_count: u64, -} - -impl MeasurementSeriesEnd { - pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { - MeasurementSeriesEnd { - series_id: series_id.to_string(), - total_count, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { - series_id: self.series_id.clone(), - total_count: self.total_count, - }, - ), - }) - } -} - -pub struct MeasurementSeriesElement { - index: u64, - value: Value, - timestamp: DateTime, - series_id: String, - metadata: Option>, -} - -impl MeasurementSeriesElement { - pub(crate) fn new( - index: u64, - value: Value, - series: &MeasurementSeriesStart, - metadata: Option>, - ) -> MeasurementSeriesElement { - MeasurementSeriesElement { - index, - value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), - series_id: series.series_id.to_string(), - metadata, - } - } - - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( - models::MeasurementSeriesElementSpec { - index: self.index, - value: self.value.clone(), - timestamp: self.timestamp, - series_id: self.series_id.clone(), - metadata: self.metadata.clone(), - }, - ), - }) - } -} - -#[cfg(test)] -mod tests { - use anyhow::bail; - use anyhow::Result; - - use assert_json_diff::assert_json_include; - use serde_json::Map; - use serde_json::Value; - - use super::*; - use crate::output::models; - use crate::output::models::ValidatorType; - - #[test] - fn test_schema_creation_from_builder() -> Result<()> { - let version = SchemaVersion::new(); - assert_eq!(version.major, models::SPEC_VERSION.0); - assert_eq!(version.minor, models::SPEC_VERSION.1); - Ok(()) - } - - #[test] - fn test_dut_creation_from_builder_with_defaults() -> Result<()> { - let dut = DutInfo::builder("1234").build(); - assert_eq!(dut.id, "1234"); - Ok(()) - } - - #[test] - fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { - let log = Log::builder("test") - .severity(models::LogSeverity::Info) - .build(); - - let artifact = log.to_artifact(ArtifactContext::TestRun); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { - let log = Log::builder("test") - .severity(models::LogSeverity::Info) - .build(); - - let artifact = log.to_artifact(ArtifactContext::TestStep); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { - let error = Error::builder("symptom") - .message("") - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .source("", 1) - .build(); - - let artifact = error.to_artifact(ArtifactContext::TestRun); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { - let error = Error::builder("symptom") - .message("") - .add_software_info(&SoftwareInfo::builder("id", "name").build()) - .source("", 1) - .build(); - - let artifact = error.to_artifact(ArtifactContext::TestStep); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let value = Value::from(50); - let measurement = Measurement::new(&name, value.clone()); - - let artifact = measurement.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name: name.to_string(), - unit: None, - value, - validators: None, - hardware_info_id: None, - subcomponent: None, - metadata: None, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let value = Value::from(50000); - let hardware_info = HardwareInfo::builder("id", "name").build(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - - let meta_key = "key"; - let meta_value = Value::from("value"); - let mut metadata = Map::new(); - metadata.insert(meta_key.to_string(), meta_value.clone()); - metadata.insert(meta_key.to_string(), meta_value.clone()); - - let subcomponent = Subcomponent::builder("name").build(); - - let unit = "RPM"; - let measurement = Measurement::builder(&name, value.clone()) - .hardware_info(&hardware_info) - .add_validator(&validator) - .add_validator(&validator) - .add_metadata(meta_key, meta_value.clone()) - .add_metadata(meta_key, meta_value.clone()) - .subcomponent(&subcomponent) - .unit(unit) - .build(); - - let artifact = measurement.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name, - unit: Some(unit.to_string()), - value, - validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(metadata), - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_start_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesStart::new(&name, &series_id); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_start_builder_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); - let hw_info = HardwareInfo::builder("id", "name").build(); - let subcomponent = Subcomponent::builder("name").build(); - let series = MeasurementSeriesStart::builder(&name, &series_id) - .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_validator(&validator) - .add_validator(&validator2) - .hardware_info(&hw_info) - .subcomponent(&subcomponent) - .build(); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { - name, - unit: Some("unit".to_string()), - series_id: series_id.to_string(), - validators: Some(vec![validator.to_spec(), validator2.to_spec()]), - hardware_info: Some(hw_info.to_spec()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(serde_json::Map::from_iter([ - ("key".to_string(), "value".into()), - ("key2".to_string(), "value2".into()) - ])), - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_end_to_artifact() -> Result<()> { - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesEnd::new(&series_id, 1); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { - series_id: series_id.to_string(), - total_count: 1, - } - ), - }) - ); - - Ok(()) - } - - #[test] - fn test_dut_builder() -> Result<()> { - let platform = PlatformInfo::builder("platform_info").build(); - let software = SoftwareInfo::builder("software_id", "name").build(); - let hardware = HardwareInfo::builder("hardware_id", "name").build(); - let dut = DutInfo::builder("1234") - .name("DUT") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_hardware_info(&hardware) - .add_hardware_info(&hardware) - .add_platform_info(&platform) - .add_platform_info(&platform) - .add_software_info(&software) - .add_software_info(&software) - .build(); - - let spec_dut = dut.to_spec(); - - assert_eq!(spec_dut.id, "1234"); - assert_eq!(spec_dut.name, Some("DUT".to_owned())); - - match spec_dut.metadata { - Some(m) => { - assert_eq!(m["key"], "value"); - assert_eq!(m["key2"], "value2"); - } - _ => bail!("metadata is empty"), - } - - match spec_dut.hardware_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "hardware_id"); - } - _ => bail!("hardware_infos is empty"), - }, - _ => bail!("hardware_infos is missing"), - } - - match spec_dut.software_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "software_id"); - } - _ => bail!("software_infos is empty"), - }, - _ => bail!("software_infos is missing"), - } - - match spec_dut.platform_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.info, "platform_info"); - } - _ => bail!("platform_infos is empty"), - }, - _ => bail!("platform_infos is missing"), - } - - Ok(()) - } - - #[test] - fn test_error() -> Result<()> { - let expected_run = serde_json::json!({ - "testRunArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file": "file.rs", "line": 1}, - "symptom": "symptom" - } - } - }); - let expected_step = serde_json::json!({ - "testStepArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file":"file.rs","line":1}, - "symptom":"symptom" - } - } - }); - - let software = SoftwareInfo::builder("software_id", "name").build(); - let error = ErrorBuilder::new("symptom") - .message("message") - .source("file.rs", 1) - .add_software_info(&software) - .add_software_info(&software) - .build(); - - let spec_error = error.to_artifact(ArtifactContext::TestRun); - let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_run); - - let spec_error = error.to_artifact(ArtifactContext::TestStep); - let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_step); - - Ok(()) - } - - #[test] - fn test_validator() -> Result<()> { - let validator = Validator::builder(ValidatorType::Equal, 30.into()) - .name("validator") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .build(); - - let spec_validator = validator.to_spec(); - - assert_eq!(spec_validator.name, Some("validator".to_owned())); - assert_eq!(spec_validator.value, 30); - assert_eq!(spec_validator.validator_type, ValidatorType::Equal); - - match spec_validator.metadata { - Some(m) => { - assert_eq!(m["key"], "value"); - assert_eq!(m["key2"], "value2"); - } - _ => bail!("metadata is none"), - } - - Ok(()) - } - - #[test] - fn test_hardware_info() -> Result<()> { - let info = HardwareInfo::builder("hardware_id", "hardware_name") - .version("version") - .revision("revision") - .location("location") - .serial_no("serial_no") - .part_no("part_no") - .manufacturer("manufacturer") - .manufacturer_part_no("manufacturer_part_no") - .odata_id("odata_id") - .computer_system("computer_system") - .manager("manager") - .build(); - - let spec_hwinfo = info.to_spec(); - - assert_eq!(spec_hwinfo.id, "hardware_id"); - assert_eq!(spec_hwinfo.name, "hardware_name"); - assert_eq!(spec_hwinfo.version, Some("version".to_owned())); - assert_eq!(spec_hwinfo.revision, Some("revision".to_owned())); - assert_eq!(spec_hwinfo.location, Some("location".to_owned())); - assert_eq!(spec_hwinfo.serial_no, Some("serial_no".to_owned())); - assert_eq!(spec_hwinfo.part_no, Some("part_no".to_owned())); - assert_eq!(spec_hwinfo.manufacturer, Some("manufacturer".to_owned())); - assert_eq!( - spec_hwinfo.manufacturer_part_no, - Some("manufacturer_part_no".to_owned()) - ); - assert_eq!(spec_hwinfo.odata_id, Some("odata_id".to_owned())); - assert_eq!( - spec_hwinfo.computer_system, - Some("computer_system".to_owned()) - ); - assert_eq!(spec_hwinfo.manager, Some("manager".to_owned())); - - Ok(()) - } - - #[test] - fn test_subcomponent() -> Result<()> { - let sub = Subcomponent::builder("sub_name") - .subcomponent_type(models::SubcomponentType::Asic) - .version("version") - .location("location") - .revision("revision") - .build(); - - let spec_subcomponent = sub.to_spec(); - - assert_eq!(spec_subcomponent.name, "sub_name"); - assert_eq!(spec_subcomponent.version, Some("version".to_owned())); - assert_eq!(spec_subcomponent.revision, Some("revision".to_owned())); - assert_eq!(spec_subcomponent.location, Some("location".to_owned())); - assert_eq!( - spec_subcomponent.subcomponent_type, - Some(models::SubcomponentType::Asic) - ); - - Ok(()) - } - - #[test] - fn test_platform_info() -> Result<()> { - let info = PlatformInfo::builder("info").build(); - - assert_eq!(info.to_spec().info, "info"); - Ok(()) - } - - #[test] - fn test_software_info() -> Result<()> { - let info = SoftwareInfo::builder("software_id", "name") - .version("version") - .revision("revision") - .software_type(models::SoftwareType::Application) - .computer_system("system") - .build(); - - let spec_swinfo = info.to_spec(); - - assert_eq!(spec_swinfo.id, "software_id"); - assert_eq!(spec_swinfo.name, "name"); - assert_eq!(spec_swinfo.version, Some("version".to_owned())); - assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); - assert_eq!( - spec_swinfo.software_type, - Some(models::SoftwareType::Application) - ); - assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); - - Ok(()) - } -} diff --git a/src/output/run.rs b/src/output/run.rs index b3d4a9f..15203df 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -12,12 +12,8 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::config; -use tv::emitters; -use tv::models; -use tv::objects; -use tv::state; use tv::step::TestStep; +use tv::{config, dut, emitters, error, log, models, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -35,7 +31,7 @@ pub struct TestRun { name: String, version: String, parameters: Map, - dut: objects::DutInfo, + dut: dut::DutInfo, command_line: String, metadata: Option>, state: Arc>, @@ -52,7 +48,7 @@ impl TestRun { /// let dut = DutInfo::builder("my_dut").build(); /// let builder = TestRun::builder("run_name", &dut, "1.0"); /// ``` - pub fn builder(name: &str, dut: &objects::DutInfo, version: &str) -> TestRunBuilder { + pub fn builder(name: &str, dut: &dut::DutInfo, version: &str) -> TestRunBuilder { TestRunBuilder::new(name, dut, version) } @@ -66,7 +62,7 @@ impl TestRun { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// ``` pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { - let dut = objects::DutInfo::new(dut_id); + let dut = dut::DutInfo::new(dut_id); TestRunBuilder::new(name, &dut, version).build() } @@ -88,7 +84,7 @@ impl TestRun { /// # }); /// ``` pub async fn start(self) -> Result { - let version = objects::SchemaVersion::new(); + let version = SchemaVersion::new(); self.state .lock() .await @@ -96,7 +92,7 @@ impl TestRun { .emit(&version.to_artifact()) .await?; - let mut builder = objects::TestRunStart::builder( + let mut builder = run::TestRunStart::builder( &self.name, &self.version, &self.command_line, @@ -165,7 +161,7 @@ impl TestRun { /// Builder for the [`TestRun`] object. pub struct TestRunBuilder { name: String, - dut: objects::DutInfo, + dut: dut::DutInfo, version: String, parameters: Map, command_line: String, @@ -174,7 +170,7 @@ pub struct TestRunBuilder { } impl TestRunBuilder { - pub fn new(name: &str, dut: &objects::DutInfo, version: &str) -> Self { + pub fn new(name: &str, dut: &dut::DutInfo, version: &str) -> Self { Self { name: name.to_string(), dut: dut.clone(), @@ -310,7 +306,7 @@ impl StartedTestRun { status: models::TestStatus, result: models::TestResult, ) -> Result<(), emitters::WriterError> { - let end = objects::TestRunEnd::builder() + let end = run::TestRunEnd::builder() .status(status) .result(result) .build(); @@ -348,12 +344,12 @@ impl StartedTestRun { severity: models::LogSeverity, msg: &str, ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); + let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&log.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -381,11 +377,11 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&log.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -409,11 +405,11 @@ impl StartedTestRun { /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); + let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -442,11 +438,11 @@ impl StartedTestRun { symptom: &str, msg: &str, ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); + let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -477,12 +473,12 @@ impl StartedTestRun { /// ``` pub async fn error_with_details( &self, - error: &objects::Error, + error: &error::Error, ) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestRun)) + .emit(&error.to_artifact(ArtifactContext::TestRun)) .await?; Ok(()) } @@ -491,3 +487,189 @@ impl StartedTestRun { TestStep::new(name, self.run.state.clone()) } } + +pub struct TestRunStart { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: dut::DutInfo, +} + +impl TestRunStart { + pub fn builder( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &dut::DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + name: self.name.clone(), + version: self.version.clone(), + command_line: self.command_line.clone(), + parameters: self.parameters.clone(), + metadata: self.metadata.clone(), + dut_info: self.dut_info.to_spec(), + }), + }) + } +} + +pub struct TestRunStartBuilder { + name: String, + version: String, + command_line: String, + parameters: Map, + metadata: Option>, + dut_info: dut::DutInfo, +} + +impl TestRunStartBuilder { + pub fn new( + name: &str, + version: &str, + command_line: &str, + parameters: &Map, + dut_info: &dut::DutInfo, + ) -> TestRunStartBuilder { + TestRunStartBuilder { + name: name.to_string(), + version: version.to_string(), + command_line: command_line.to_string(), + parameters: parameters.clone(), + metadata: None, + dut_info: dut_info.clone(), + } + } + + pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + None => { + let mut metadata = Map::new(); + metadata.insert(key.to_string(), value.clone()); + Some(metadata) + } + }; + self + } + + pub fn build(self) -> TestRunStart { + TestRunStart { + name: self.name, + version: self.version, + command_line: self.command_line, + parameters: self.parameters, + metadata: self.metadata, + dut_info: self.dut_info, + } + } +} + +pub struct TestRunEnd { + status: models::TestStatus, + result: models::TestResult, +} + +impl TestRunEnd { + pub fn builder() -> TestRunEndBuilder { + TestRunEndBuilder::new() + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + status: self.status.clone(), + result: self.result.clone(), + }), + }) + } +} + +#[derive(Debug)] +pub struct TestRunEndBuilder { + status: models::TestStatus, + result: models::TestResult, +} + +#[allow(clippy::new_without_default)] +impl TestRunEndBuilder { + pub fn new() -> TestRunEndBuilder { + TestRunEndBuilder { + status: models::TestStatus::Complete, + result: models::TestResult::Pass, + } + } + pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { + self.status = value; + self + } + + pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { + self.result = value; + self + } + + pub fn build(self) -> TestRunEnd { + TestRunEnd { + status: self.status, + result: self.result, + } + } +} + +// TODO: move this away from here +pub enum ArtifactContext { + TestRun, + TestStep, +} + +// TODO: this likely will go into the emitter since it's not the run's job to emit the schema version +pub struct SchemaVersion { + major: i8, + minor: i8, +} + +#[allow(clippy::new_without_default)] +impl SchemaVersion { + pub fn new() -> SchemaVersion { + SchemaVersion { + major: models::SPEC_VERSION.0, + minor: models::SPEC_VERSION.1, + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { + major: self.major, + minor: self.minor, + }) + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + + use super::*; + use crate::output as tv; + use tv::models; + + #[test] + fn test_schema_creation_from_builder() -> Result<()> { + let version = SchemaVersion::new(); + assert_eq!(version.major, models::SPEC_VERSION.0); + assert_eq!(version.minor, models::SPEC_VERSION.1); + Ok(()) + } +} diff --git a/src/output/step.rs b/src/output/step.rs index 59b6ddb..86659a4 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -10,8 +10,8 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; -use tv::measurement_series::MeasurementSeries; -use tv::{emitters, models, objects, state}; +use tv::measurement::MeasurementSeries; +use tv::{emitters, error, log, measurement, models, run, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -46,7 +46,7 @@ impl TestStep { /// # }); /// ``` pub async fn start(self) -> Result { - let start = objects::TestStepStart::new(&self.name); + let start = step::TestStepStart::new(&self.name); self.state .lock() .await @@ -124,7 +124,7 @@ impl StartedTestStep { /// # }); /// ``` pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { - let end = objects::TestStepEnd::new(status); + let end = step::TestStepEnd::new(status); self.step .state .lock() @@ -181,13 +181,13 @@ impl StartedTestStep { severity: models::LogSeverity, msg: &str, ) -> Result<(), emitters::WriterError> { - let log = objects::Log::builder(msg).severity(severity).build(); + let log = log::Log::builder(msg).severity(severity).build(); self.step .state .lock() .await .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&log.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -217,13 +217,13 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &objects::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { self.step .state .lock() .await .emitter - .emit(&log.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&log.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -267,13 +267,13 @@ impl StartedTestStep { /// # }); /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).build(); + let error = error::Error::builder(symptom).build(); self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -322,13 +322,13 @@ impl StartedTestStep { symptom: &str, msg: &str, ) -> Result<(), emitters::WriterError> { - let error = objects::Error::builder(symptom).message(msg).build(); + let error = error::Error::builder(symptom).message(msg).build(); self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -361,14 +361,14 @@ impl StartedTestStep { /// ``` pub async fn error_with_details( &self, - error: &objects::Error, + error: &error::Error, ) -> Result<(), emitters::WriterError> { self.step .state .lock() .await .emitter - .emit(&error.to_artifact(objects::ArtifactContext::TestStep)) + .emit(&error.to_artifact(run::ArtifactContext::TestStep)) .await?; Ok(()) } @@ -397,7 +397,7 @@ impl StartedTestStep { name: &str, value: Value, ) -> Result<(), emitters::WriterError> { - let measurement = objects::Measurement::new(name, value); + let measurement = measurement::Measurement::new(name, value); self.step .state .lock() @@ -437,7 +437,7 @@ impl StartedTestStep { /// ``` pub async fn add_measurement_with_details( &self, - measurement: &objects::Measurement, + measurement: &measurement::Measurement, ) -> Result<(), emitters::WriterError> { self.step .state @@ -500,8 +500,51 @@ impl StartedTestStep { /// ``` pub fn measurement_series_with_details( &self, - start: objects::MeasurementSeriesStart, + start: measurement::MeasurementSeriesStart, ) -> MeasurementSeries { MeasurementSeries::new_with_details(start, self.step.state.clone()) } } + +pub struct TestStepStart { + name: String, +} + +impl TestStepStart { + pub fn new(name: &str) -> TestStepStart { + TestStepStart { + name: name.to_string(), + } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepStart( + models::TestStepStartSpec { + name: self.name.clone(), + }, + ), + }) + } +} + +pub struct TestStepEnd { + status: models::TestStatus, +} + +impl TestStepEnd { + pub fn new(status: models::TestStatus) -> TestStepEnd { + TestStepEnd { status } + } + + pub fn to_artifact(&self) -> models::OutputArtifactDescendant { + models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { + status: self.status.clone(), + }), + }) + } +} + +#[cfg(test)] +mod tests {} From 9a2cc6ff0db2d0843e8edfc7430cf491ecba6f49 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 19:33:50 +0100 Subject: [PATCH 19/96] remove the leaky ArtifactContext - previously all log and error outputs needed to know where theyre being called from; this is an antipattern and leaks impl detail - move this context to the proper spaces (in StartedTestRun, StartedTestStep) - this commit changes semantics of the `to_artifact` a bit, but this will be cleared in the next commits which refactor the emitter Signed-off-by: mimir-d --- src/output/error.rs | 158 ++++++++++++++++++-------------------------- src/output/log.rs | 55 +++++---------- src/output/run.rs | 36 +++++++--- src/output/step.rs | 82 ++++++++++++++--------- 4 files changed, 159 insertions(+), 172 deletions(-) diff --git a/src/output/error.rs b/src/output/error.rs index d61e9a0..a940c10 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -5,7 +5,7 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{dut, models, run::ArtifactContext}; +use tv::{dut, models}; pub struct Error { symptom: String, @@ -19,28 +19,12 @@ impl Error { ErrorBuilder::new(symptom) } - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: self.symptom.clone(), - message: self.message.clone(), - software_infos: self.software_infos.clone(), - source_location: self.source_location.clone(), - }), - }) - } + pub fn to_artifact(&self) -> models::ErrorSpec { + models::ErrorSpec { + symptom: self.symptom.clone(), + message: self.message.clone(), + software_infos: self.software_infos.clone(), + source_location: self.source_location.clone(), } } } @@ -112,17 +96,15 @@ mod tests { .source("", 1) .build(); - let artifact = error.to_artifact(ArtifactContext::TestRun); + let artifact = error.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) + models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } ); Ok(()) @@ -136,17 +118,15 @@ mod tests { .source("", 1) .build(); - let artifact = error.to_artifact(ArtifactContext::TestStep); + let artifact = error.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(models::ErrorSpec { - symptom: error.symptom.clone(), - message: error.message.clone(), - software_infos: error.software_infos.clone(), - source_location: error.source_location.clone(), - }), - }) + models::ErrorSpec { + symptom: error.symptom.clone(), + message: error.message.clone(), + software_infos: error.software_infos.clone(), + source_location: error.source_location.clone(), + } ); Ok(()) @@ -155,60 +135,52 @@ mod tests { #[test] fn test_error() -> Result<()> { let expected_run = serde_json::json!({ - "testRunArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file": "file.rs", "line": 1}, - "symptom": "symptom" + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": + "software_id", + "softwareType": null, + "version": null } - } + ], + "sourceLocation": {"file": "file.rs", "line": 1}, + "symptom": "symptom" }); let expected_step = serde_json::json!({ - "testStepArtifact": { - "error": { - "message": "message", - "softwareInfoIds": [ - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - }, - { - "computerSystem": null, - "name": "name", - "revision": null, - "softwareInfoId": "software_id", - "softwareType": null, - "version": null - } - ], - "sourceLocation": {"file":"file.rs","line":1}, - "symptom":"symptom" + "message": "message", + "softwareInfoIds": [ + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null + }, + { + "computerSystem": null, + "name": "name", + "revision": null, + "softwareInfoId": "software_id", + "softwareType": null, + "version": null } - } + ], + "sourceLocation": {"file":"file.rs","line":1}, + "symptom":"symptom" }); let software = dut::SoftwareInfo::builder("software_id", "name").build(); @@ -219,11 +191,11 @@ mod tests { .add_software_info(&software) .build(); - let spec_error = error.to_artifact(ArtifactContext::TestRun); + let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_run); - let spec_error = error.to_artifact(ArtifactContext::TestStep); + let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); assert_json_include!(actual: actual, expected: &expected_step); diff --git a/src/output/log.rs b/src/output/log.rs index f76af11..8228b58 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -5,7 +5,7 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{models, run::ArtifactContext}; +use tv::models; pub struct Log { severity: models::LogSeverity, @@ -18,26 +18,11 @@ impl Log { LogBuilder::new(message) } - pub fn to_artifact(&self, context: ArtifactContext) -> models::OutputArtifactDescendant { - match context { - ArtifactContext::TestRun => { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } - ArtifactContext::TestStep => { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: self.severity.clone(), - message: self.message.clone(), - source_location: self.source_location.clone(), - }), - }) - } + pub fn to_artifact(&self) -> models::LogSpec { + models::LogSpec { + severity: self.severity.clone(), + message: self.message.clone(), + source_location: self.source_location.clone(), } } } @@ -92,16 +77,14 @@ mod tests { .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(ArtifactContext::TestRun); + let artifact = log.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) + models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + }, ); Ok(()) @@ -113,16 +96,14 @@ mod tests { .severity(models::LogSeverity::Info) .build(); - let artifact = log.to_artifact(ArtifactContext::TestStep); + let artifact = log.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(models::LogSpec { - severity: log.severity.clone(), - message: log.message.clone(), - source_location: log.source_location.clone(), - }), - }) + models::LogSpec { + severity: log.severity.clone(), + message: log.message.clone(), + source_location: log.source_location.clone(), + } ); Ok(()) diff --git a/src/output/run.rs b/src/output/run.rs index 15203df..2e72bda 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -348,9 +348,13 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + }; emitter - .emit(&log.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -380,9 +384,13 @@ impl StartedTestRun { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + }; emitter - .emit(&log.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -408,9 +416,13 @@ impl StartedTestRun { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -441,9 +453,13 @@ impl StartedTestRun { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -477,9 +493,13 @@ impl StartedTestRun { ) -> Result<(), emitters::WriterError> { let emitter = &self.run.state.lock().await.emitter; + let artifact = models::TestRunArtifactSpec { + descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + }; emitter - .emit(&error.to_artifact(ArtifactContext::TestRun)) + .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) .await?; + Ok(()) } @@ -628,12 +648,6 @@ impl TestRunEndBuilder { } } -// TODO: move this away from here -pub enum ArtifactContext { - TestRun, - TestStep, -} - // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version pub struct SchemaVersion { major: i8, diff --git a/src/output/step.rs b/src/output/step.rs index 86659a4..749a0a8 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -11,7 +11,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::measurement::MeasurementSeries; -use tv::{emitters, error, log, measurement, models, run, state, step}; +use tv::{emitters, error, log, measurement, models, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -182,13 +182,17 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -218,13 +222,17 @@ impl StartedTestStep { /// # }); /// ``` pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&log.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -268,13 +276,17 @@ impl StartedTestStep { /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { let error = error::Error::builder(symptom).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -323,13 +335,17 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitters::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } @@ -363,13 +379,17 @@ impl StartedTestStep { &self, error: &error::Error, ) -> Result<(), emitters::WriterError> { - self.step - .state - .lock() - .await - .emitter - .emit(&error.to_artifact(run::ArtifactContext::TestStep)) + let emitter = &self.step.state.lock().await.emitter; + + let artifact = models::TestStepArtifactSpec { + descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + }; + emitter + .emit(&models::OutputArtifactDescendant::TestStepArtifact( + artifact, + )) .await?; + Ok(()) } From 49594846fbfb1005c327b9894655c991744ab194 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 19:56:32 +0100 Subject: [PATCH 20/96] some renames; better formatting in spec models Signed-off-by: mimir-d --- src/output/config.rs | 18 ++--- src/output/{emitters.rs => emitter.rs} | 12 ++-- src/output/measurement.rs | 40 +++++------ src/output/mod.rs | 4 +- src/output/models.rs | 99 ++++++++++++++++++++++++-- src/output/run.rs | 54 +++++++------- src/output/state.rs | 6 +- src/output/step.rs | 48 +++++-------- 8 files changed, 179 insertions(+), 102 deletions(-) rename src/output/{emitters.rs => emitter.rs} (93%) diff --git a/src/output/config.rs b/src/output/config.rs index f97baaa..d5c27b8 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -8,12 +8,12 @@ use std::path::Path; use std::sync::Arc; use tokio::sync::Mutex; -use crate::output::emitters; +use crate::output::emitter; /// The configuration repository for the TestRun. pub struct Config { pub(crate) timezone: chrono_tz::Tz, - pub(crate) writer: emitters::WriterType, + pub(crate) writer: emitter::WriterType, } impl Config { @@ -33,14 +33,14 @@ impl Config { /// The builder for the [`Config`] object. pub struct ConfigBuilder { timezone: Option, - writer: Option, + writer: Option, } impl ConfigBuilder { fn new() -> Self { Self { timezone: None, - writer: Some(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + writer: Some(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } @@ -50,7 +50,7 @@ impl ConfigBuilder { } pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { - self.writer = Some(emitters::WriterType::Buffer(emitters::BufferWriter::new( + self.writer = Some(emitter::WriterType::Buffer(emitter::BufferWriter::new( buffer, ))); self @@ -59,9 +59,9 @@ impl ConfigBuilder { pub async fn with_file_output>( mut self, path: P, - ) -> Result { - self.writer = Some(emitters::WriterType::File( - emitters::FileWriter::new(path).await?, + ) -> Result { + self.writer = Some(emitter::WriterType::File( + emitter::FileWriter::new(path).await?, )); Ok(self) } @@ -71,7 +71,7 @@ impl ConfigBuilder { timezone: self.timezone.unwrap_or(chrono_tz::UTC), writer: self .writer - .unwrap_or(emitters::WriterType::Stdout(emitters::StdoutWriter::new())), + .unwrap_or(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } } diff --git a/src/output/emitters.rs b/src/output/emitter.rs similarity index 93% rename from src/output/emitters.rs rename to src/output/emitter.rs index d818b65..1a96bad 100644 --- a/src/output/emitters.rs +++ b/src/output/emitter.rs @@ -98,13 +98,13 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &models::OutputArtifactDescendant) -> serde_json::Value { + fn serialize_artifact(&self, object: &models::RootArtifactSpec) -> serde_json::Value { let now = chrono::Local::now(); let now_tz = now.with_timezone(&self.timezone); - let out_artifact = models::OutputArtifactSpec { - descendant: object.clone(), - now: now_tz, - sequence_number: self.next_sequence_no(), + let out_artifact = models::RootSpec { + artifact: object.clone(), + timestamp: now_tz, + seqno: self.next_sequence_no(), }; serde_json::json!(out_artifact) } @@ -114,7 +114,7 @@ impl JsonEmitter { self.sequence_no.load(atomic::Ordering::SeqCst) } - pub async fn emit(&self, object: &models::OutputArtifactDescendant) -> Result<(), WriterError> { + pub async fn emit(&self, object: &models::RootArtifactSpec) -> Result<(), WriterError> { let serialized = self.serialize_artifact(object); match self.writer { WriterType::File(ref file) => file.write(&serialized.to_string()).await?, diff --git a/src/output/measurement.rs b/src/output/measurement.rs index c641d8b..5badd7f 100644 --- a/src/output/measurement.rs +++ b/src/output/measurement.rs @@ -14,7 +14,7 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::{dut, emitters, models, state}; +use tv::{dut, emitter, models, state}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -76,7 +76,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitters::WriterError> { + pub async fn start(&self) -> Result<(), emitter::WriterError> { self.state .lock() .await @@ -106,7 +106,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self) -> Result<(), emitters::WriterError> { + pub async fn end(&self) -> Result<(), emitter::WriterError> { let end = MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); self.state @@ -138,7 +138,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitters::WriterError> { + pub async fn add_measurement(&self, value: Value) -> Result<(), emitter::WriterError> { let element = MeasurementSeriesElement::new( self.current_sequence_no().await, value, @@ -180,7 +180,7 @@ impl MeasurementSeries { &self, value: Value, metadata: Vec<(&str, Value)>, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let element = MeasurementSeriesElement::new( self.current_sequence_no().await, value, @@ -227,9 +227,9 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> + pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitter::WriterError> where - R: Future>, + R: Future>, F: std::ops::FnOnce(&'a MeasurementSeries) -> R, { self.start().await?; @@ -405,8 +405,8 @@ impl Measurement { /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { name: self.name.clone(), unit: self.unit.clone(), @@ -633,8 +633,8 @@ impl MeasurementSeriesStart { MeasurementSeriesStartBuilder::new(name, series_id) } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name: self.name.clone(), @@ -758,8 +758,8 @@ impl MeasurementSeriesEnd { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( models::MeasurementSeriesEndSpec { series_id: self.series_id.clone(), @@ -794,8 +794,8 @@ impl MeasurementSeriesElement { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( models::MeasurementSeriesElementSpec { index: self.index, @@ -827,7 +827,7 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement( models::MeasurementSpec { name: name.to_string(), @@ -874,7 +874,7 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Measurement( models::MeasurementSpec { name, @@ -901,7 +901,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name: name.to_string(), @@ -940,7 +940,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( models::MeasurementSeriesStartSpec { name, @@ -969,7 +969,7 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( models::MeasurementSeriesEndSpec { series_id: series_id.to_string(), diff --git a/src/output/mod.rs b/src/output/mod.rs index d296c57..6326631 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -6,7 +6,7 @@ mod config; mod dut; -mod emitters; +mod emitter; mod error; mod log; mod macros; @@ -18,7 +18,7 @@ mod step; pub use config::*; pub use dut::*; -pub use emitters::*; +pub use emitter::*; pub use error::*; pub use log::*; pub use measurement::*; diff --git a/src/output/models.rs b/src/output/models.rs index 48a2493..db187d1 100644 --- a/src/output/models.rs +++ b/src/output/models.rs @@ -42,20 +42,25 @@ mod rfc3339_format { pub enum TestRunArtifactDescendant { #[serde(rename = "testRunStart")] TestRunStart(TestRunStartSpec), + #[serde(rename = "testRunEnd")] TestRunEnd(TestRunEndSpec), + #[serde(rename = "log")] Log(LogSpec), + #[serde(rename = "error")] Error(ErrorSpec), } #[derive(Debug, Serialize, PartialEq, Clone)] -pub enum OutputArtifactDescendant { +pub enum RootArtifactSpec { #[serde(rename = "schemaVersion")] SchemaVersion(SchemaVersionSpec), + #[serde(rename = "testRunArtifact")] TestRunArtifact(TestRunArtifactSpec), + #[serde(rename = "testStepArtifact")] TestStepArtifact(TestStepArtifactSpec), } @@ -65,24 +70,34 @@ pub enum OutputArtifactDescendant { pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] TestStepStart(TestStepStartSpec), + #[serde(rename = "testStepEnd")] TestStepEnd(TestStepEndSpec), + #[serde(rename = "measurement")] Measurement(MeasurementSpec), + #[serde(rename = "measurementSeriesStart")] MeasurementSeriesStart(MeasurementSeriesStartSpec), + #[serde(rename = "measurementSeriesEnd")] MeasurementSeriesEnd(MeasurementSeriesEndSpec), + #[serde(rename = "measurementSeriesElement")] MeasurementSeriesElement(MeasurementSeriesElementSpec), + #[serde(rename = "diagnosis")] Diagnosis(DiagnosisSpec), + #[serde(rename = "log")] Log(LogSpec), + #[serde(rename = "error")] Error(ErrorSpec), + #[serde(rename = "file")] File(FileSpec), + #[serde(rename = "extension")] Extension(ExtensionSpec), } @@ -127,6 +142,7 @@ pub enum SubcomponentType { Connector, } +// TODO: this should be better typed #[derive(Debug, Serialize, PartialEq, Clone)] pub enum ExtensionContentType { #[serde(rename = "float")] @@ -218,15 +234,17 @@ pub enum SoftwareType { } #[derive(Debug, Serialize, Clone)] -pub struct OutputArtifactSpec { +pub struct RootSpec { #[serde(flatten)] - pub descendant: OutputArtifactDescendant, + pub artifact: RootArtifactSpec, + // TODO : manage different timezones #[serde(rename = "timestamp")] #[serde(with = "rfc3339_format")] - pub now: DateTime, + pub timestamp: DateTime, + #[serde(rename = "sequenceNumber")] - pub sequence_number: u64, + pub seqno: u64, } /// Low-level model for the `schemaVersion` spec object. @@ -239,6 +257,7 @@ pub struct OutputArtifactSpec { pub struct SchemaVersionSpec { #[serde(rename = "major")] pub major: i8, + #[serde(rename = "minor")] pub minor: i8, } @@ -251,7 +270,7 @@ pub struct SchemaVersionSpec { #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifactSpec { #[serde(flatten)] - pub descendant: TestRunArtifactDescendant, + pub artifact: TestRunArtifactDescendant, } /// Low-level model for the `testRunStart` spec object. @@ -264,14 +283,19 @@ pub struct TestRunArtifactSpec { pub struct TestRunStartSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: String, + #[serde(rename = "commandLine")] pub command_line: String, + #[serde(rename = "parameters")] pub parameters: Map, + #[serde(rename = "dutInfo")] pub dut_info: DutInfoSpec, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -286,14 +310,19 @@ pub struct TestRunStartSpec { pub struct DutInfoSpec { #[serde(rename = "dutInfoId")] pub id: String, + #[serde(rename = "name")] pub name: Option, + #[serde(rename = "platformInfos")] pub platform_infos: Option>, + #[serde(rename = "softwareInfos")] pub software_infos: Option>, + #[serde(rename = "hardwareInfos")] pub hardware_infos: Option>, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -320,14 +349,19 @@ pub struct PlatformInfoSpec { pub struct SoftwareInfoSpec { #[serde(rename = "softwareInfoId")] pub id: String, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, + #[serde(rename = "softwareType")] pub software_type: Option, + #[serde(rename = "computerSystem")] pub computer_system: Option, } @@ -342,26 +376,37 @@ pub struct SoftwareInfoSpec { pub struct HardwareInfoSpec { #[serde(rename = "hardwareInfoId")] pub id: String, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, + #[serde(rename = "location")] pub location: Option, + #[serde(rename = "serialNumber")] pub serial_no: Option, + #[serde(rename = "partNumber")] pub part_no: Option, + #[serde(rename = "manufacturer")] pub manufacturer: Option, + #[serde(rename = "manufacturerPartNumber")] pub manufacturer_part_no: Option, + #[serde(rename = "odataId")] pub odata_id: Option, + #[serde(rename = "computerSystem")] pub computer_system: Option, + #[serde(rename = "manager")] pub manager: Option, } @@ -376,6 +421,7 @@ pub struct HardwareInfoSpec { pub struct TestRunEndSpec { #[serde(rename = "status")] pub status: TestStatus, + #[serde(rename = "result")] pub result: TestResult, } @@ -391,11 +437,14 @@ pub struct TestRunEndSpec { pub struct ErrorSpec { #[serde(rename = "symptom")] pub symptom: String, + #[serde(rename = "message")] pub message: Option, + // TODO: support this field during serialization to print only the id of SoftwareInfo struct #[serde(rename = "softwareInfoIds")] pub software_infos: Option>, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -410,8 +459,10 @@ pub struct ErrorSpec { pub struct LogSpec { #[serde(rename = "severity")] pub severity: LogSeverity, + #[serde(rename = "message")] pub message: String, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -426,6 +477,7 @@ pub struct LogSpec { pub struct SourceLocationSpec { #[serde(rename = "file")] pub file: String, + #[serde(rename = "line")] pub line: i32, } @@ -475,16 +527,22 @@ pub struct TestStepEndSpec { pub struct MeasurementSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "value")] pub value: Value, + #[serde(rename = "unit")] pub unit: Option, + #[serde(rename = "validators")] pub validators: Option>, + #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, + #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -499,10 +557,13 @@ pub struct MeasurementSpec { pub struct ValidatorSpec { #[serde(rename = "name")] pub name: Option, + #[serde(rename = "type")] pub validator_type: ValidatorType, + #[serde(rename = "value")] pub value: Value, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -517,12 +578,16 @@ pub struct ValidatorSpec { pub struct SubcomponentSpec { #[serde(rename = "type")] pub subcomponent_type: Option, + #[serde(rename = "name")] pub name: String, + #[serde(rename = "location")] pub location: Option, + #[serde(rename = "version")] pub version: Option, + #[serde(rename = "revision")] pub revision: Option, } @@ -537,16 +602,22 @@ pub struct SubcomponentSpec { pub struct MeasurementSeriesStartSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "unit")] pub unit: Option, + #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "validators")] pub validators: Option>, + #[serde(rename = "hardwareInfoId")] pub hardware_info: Option, + #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -561,6 +632,7 @@ pub struct MeasurementSeriesStartSpec { pub struct MeasurementSeriesEndSpec { #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "totalCount")] pub total_count: u64, } @@ -575,12 +647,16 @@ pub struct MeasurementSeriesEndSpec { pub struct MeasurementSeriesElementSpec { #[serde(rename = "index")] pub index: u64, + #[serde(rename = "value")] pub value: Value, + #[serde(with = "rfc3339_format")] pub timestamp: DateTime, + #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -595,14 +671,19 @@ pub struct MeasurementSeriesElementSpec { pub struct DiagnosisSpec { #[serde(rename = "verdict")] pub verdict: String, + #[serde(rename = "type")] pub diagnosis_type: DiagnosisType, + #[serde(rename = "message")] pub message: Option, + #[serde(rename = "validators")] pub hardware_info: Option, + #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -617,14 +698,19 @@ pub struct DiagnosisSpec { pub struct FileSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "uri")] pub uri: String, + #[serde(rename = "isSnapshot")] pub is_snapshot: bool, + #[serde(rename = "description")] pub description: Option, + #[serde(rename = "contentType")] pub content_type: Option, + #[serde(rename = "metadata")] pub metadata: Option>, } @@ -639,6 +725,7 @@ pub struct FileSpec { pub struct ExtensionSpec { #[serde(rename = "name")] pub name: String, + #[serde(rename = "content")] pub content: ExtensionContentType, } diff --git a/src/output/run.rs b/src/output/run.rs index 2e72bda..dce57f5 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -13,7 +13,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::step::TestStep; -use tv::{config, dut, emitters, error, log, models, run, state}; +use tv::{config, dut, emitter, error, log, models, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -83,7 +83,7 @@ impl TestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let version = SchemaVersion::new(); self.state .lock() @@ -263,7 +263,7 @@ impl TestRunBuilder { pub fn build(self) -> TestRun { let config = self.config.unwrap_or(config::Config::builder().build()); - let emitter = emitters::JsonEmitter::new(config.timezone, config.writer); + let emitter = emitter::JsonEmitter::new(config.timezone, config.writer); let state = state::TestState::new(emitter); TestRun { name: self.name, @@ -305,7 +305,7 @@ impl StartedTestRun { &self, status: models::TestStatus, result: models::TestResult, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let end = run::TestRunEnd::builder() .status(status) .result(result) @@ -343,16 +343,16 @@ impl StartedTestRun { &self, severity: models::LogSeverity, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -381,14 +381,14 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -412,15 +412,15 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -449,15 +449,15 @@ impl StartedTestRun { &self, symptom: &str, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -490,14 +490,14 @@ impl StartedTestRun { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; let artifact = models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestRunArtifact(artifact)) + .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) .await?; Ok(()) @@ -528,9 +528,9 @@ impl TestRunStart { TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { + artifact: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { name: self.name.clone(), version: self.version.clone(), command_line: self.command_line.clone(), @@ -606,9 +606,9 @@ impl TestRunEnd { TestRunEndBuilder::new() } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestRunArtifact(models::TestRunArtifactSpec { - descendant: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { + artifact: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { status: self.status.clone(), result: self.result.clone(), }), @@ -663,8 +663,8 @@ impl SchemaVersion { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::SchemaVersion(models::SchemaVersionSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::SchemaVersion(models::SchemaVersionSpec { major: self.major, minor: self.minor, }) diff --git a/src/output/state.rs b/src/output/state.rs index 63164f8..df1fe96 100644 --- a/src/output/state.rs +++ b/src/output/state.rs @@ -4,15 +4,15 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output::emitters; +use crate::output::emitter; // TODO: will prob need some redesign pub struct TestState { - pub emitter: emitters::JsonEmitter, + pub emitter: emitter::JsonEmitter, } impl TestState { - pub fn new(emitter: emitters::JsonEmitter) -> TestState { + pub fn new(emitter: emitter::JsonEmitter) -> TestState { TestState { emitter } } } diff --git a/src/output/step.rs b/src/output/step.rs index 749a0a8..de8da4d 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -11,7 +11,7 @@ use tokio::sync::Mutex; use crate::output as tv; use tv::measurement::MeasurementSeries; -use tv::{emitters, error, log, measurement, models, state, step}; +use tv::{emitter, error, log, measurement, models, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -45,7 +45,7 @@ impl TestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { let start = step::TestStepStart::new(&self.name); self.state .lock() @@ -123,7 +123,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitters::WriterError> { + pub async fn end(&self, status: models::TestStatus) -> Result<(), emitter::WriterError> { let end = step::TestStepEnd::new(status); self.step .state @@ -180,7 +180,7 @@ impl StartedTestStep { &self, severity: models::LogSeverity, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.step.state.lock().await.emitter; @@ -188,9 +188,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -221,16 +219,14 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitters::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; let artifact = models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -274,7 +270,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitters::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); let emitter = &self.step.state.lock().await.emitter; @@ -282,9 +278,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -333,7 +327,7 @@ impl StartedTestStep { &self, symptom: &str, msg: &str, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.step.state.lock().await.emitter; @@ -341,9 +335,7 @@ impl StartedTestStep { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -378,16 +370,14 @@ impl StartedTestStep { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; let artifact = models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::OutputArtifactDescendant::TestStepArtifact( - artifact, - )) + .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) .await?; Ok(()) @@ -416,7 +406,7 @@ impl StartedTestStep { &self, name: &str, value: Value, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { let measurement = measurement::Measurement::new(name, value); self.step .state @@ -458,7 +448,7 @@ impl StartedTestStep { pub async fn add_measurement_with_details( &self, measurement: &measurement::Measurement, - ) -> Result<(), emitters::WriterError> { + ) -> Result<(), emitter::WriterError> { self.step .state .lock() @@ -537,8 +527,8 @@ impl TestStepStart { } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::TestStepStart( models::TestStepStartSpec { name: self.name.clone(), @@ -557,8 +547,8 @@ impl TestStepEnd { TestStepEnd { status } } - pub fn to_artifact(&self) -> models::OutputArtifactDescendant { - models::OutputArtifactDescendant::TestStepArtifact(models::TestStepArtifactSpec { + pub fn to_artifact(&self) -> models::RootArtifactSpec { + models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { status: self.status.clone(), }), From 4987393fa0a8c91ef06064ee99bddb7cdedf7490 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 20:09:24 +0100 Subject: [PATCH 21/96] move spec to a different module path - reasoning: this spec file will be used in multiple modules (serialized in ::output but deserialize in possibly ::parse) - remove the `Spec` suffix from the objects now, since that's clear from the module name Signed-off-by: mimir-d --- src/lib.rs | 1 + src/output/dut.rs | 45 ++++++----- src/output/emitter.rs | 20 ++--- src/output/error.rs | 24 +++--- src/output/log.rs | 32 ++++---- src/output/measurement.rs | 116 ++++++++++++++-------------- src/output/mod.rs | 11 ++- src/output/run.rs | 86 ++++++++++----------- src/output/step.rs | 59 +++++++-------- src/{output/models.rs => spec.rs} | 122 +++++++++++++++--------------- 10 files changed, 256 insertions(+), 260 deletions(-) rename src/{output/models.rs => spec.rs} (91%) diff --git a/src/lib.rs b/src/lib.rs index e01580c..ee95e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,3 +5,4 @@ // https://opensource.org/licenses/MIT. pub mod output; +mod spec; diff --git a/src/output/dut.rs b/src/output/dut.rs index 89679f2..b4d6c20 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -4,8 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output as tv; -use tv::models; +use crate::spec; #[derive(Default, Debug, Clone, PartialEq)] pub struct DutInfo { @@ -26,8 +25,8 @@ impl DutInfo { DutInfoBuilder::new(id).build() } - pub(crate) fn to_spec(&self) -> models::DutInfoSpec { - models::DutInfoSpec { + pub(crate) fn to_spec(&self) -> spec::DutInfo { + spec::DutInfo { id: self.id.clone(), name: self.name.clone(), platform_infos: self @@ -153,8 +152,8 @@ impl HardwareInfo { HardwareInfoBuilder::new(id, name) } - pub fn to_spec(&self) -> models::HardwareInfoSpec { - models::HardwareInfoSpec { + pub fn to_spec(&self) -> spec::HardwareInfo { + spec::HardwareInfo { id: self.id.clone(), name: self.name.clone(), version: self.version.clone(), @@ -269,7 +268,7 @@ impl HardwareInfoBuilder { #[derive(Debug, Clone)] pub struct Subcomponent { - subcomponent_type: Option, + subcomponent_type: Option, name: String, location: Option, version: Option, @@ -280,8 +279,8 @@ impl Subcomponent { pub fn builder(name: &str) -> SubcomponentBuilder { SubcomponentBuilder::new(name) } - pub fn to_spec(&self) -> models::SubcomponentSpec { - models::SubcomponentSpec { + pub fn to_spec(&self) -> spec::Subcomponent { + spec::Subcomponent { subcomponent_type: self.subcomponent_type.clone(), name: self.name.clone(), location: self.location.clone(), @@ -293,7 +292,7 @@ impl Subcomponent { #[derive(Debug)] pub struct SubcomponentBuilder { - subcomponent_type: Option, + subcomponent_type: Option, name: String, location: Option, version: Option, @@ -310,7 +309,7 @@ impl SubcomponentBuilder { revision: None, } } - pub fn subcomponent_type(mut self, value: models::SubcomponentType) -> SubcomponentBuilder { + pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> SubcomponentBuilder { self.subcomponent_type = Some(value); self } @@ -348,8 +347,8 @@ impl PlatformInfo { PlatformInfoBuilder::new(info) } - pub fn to_spec(&self) -> models::PlatformInfoSpec { - models::PlatformInfoSpec { + pub fn to_spec(&self) -> spec::PlatformInfo { + spec::PlatformInfo { info: self.info.clone(), } } @@ -378,7 +377,7 @@ pub struct SoftwareInfo { name: String, version: Option, revision: Option, - software_type: Option, + software_type: Option, computer_system: Option, } @@ -387,8 +386,8 @@ impl SoftwareInfo { SoftwareInfoBuilder::new(id, name) } - pub fn to_spec(&self) -> models::SoftwareInfoSpec { - models::SoftwareInfoSpec { + pub fn to_spec(&self) -> spec::SoftwareInfo { + spec::SoftwareInfo { id: self.id.clone(), name: self.name.clone(), version: self.version.clone(), @@ -405,7 +404,7 @@ pub struct SoftwareInfoBuilder { name: String, version: Option, revision: Option, - software_type: Option, + software_type: Option, computer_system: Option, } @@ -428,7 +427,7 @@ impl SoftwareInfoBuilder { self.revision = Some(value.to_string()); self } - pub fn software_type(mut self, value: models::SoftwareType) -> SoftwareInfoBuilder { + pub fn software_type(mut self, value: spec::SoftwareType) -> SoftwareInfoBuilder { self.software_type = Some(value); self } @@ -452,7 +451,7 @@ impl SoftwareInfoBuilder { #[cfg(test)] mod tests { use super::*; - use crate::output::models; + use crate::spec; use anyhow::{bail, Result}; #[test] @@ -569,7 +568,7 @@ mod tests { let info = SoftwareInfo::builder("software_id", "name") .version("version") .revision("revision") - .software_type(models::SoftwareType::Application) + .software_type(spec::SoftwareType::Application) .computer_system("system") .build(); @@ -581,7 +580,7 @@ mod tests { assert_eq!(spec_swinfo.revision, Some("revision".to_owned())); assert_eq!( spec_swinfo.software_type, - Some(models::SoftwareType::Application) + Some(spec::SoftwareType::Application) ); assert_eq!(spec_swinfo.computer_system, Some("system".to_owned())); @@ -599,7 +598,7 @@ mod tests { #[test] fn test_subcomponent() -> Result<()> { let sub = Subcomponent::builder("sub_name") - .subcomponent_type(models::SubcomponentType::Asic) + .subcomponent_type(spec::SubcomponentType::Asic) .version("version") .location("location") .revision("revision") @@ -613,7 +612,7 @@ mod tests { assert_eq!(spec_subcomponent.location, Some("location".to_owned())); assert_eq!( spec_subcomponent.subcomponent_type, - Some(models::SubcomponentType::Asic) + Some(spec::SubcomponentType::Asic) ); Ok(()) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 1a96bad..e26acd2 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -16,7 +16,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; -use crate::output::models; +use crate::spec; #[derive(Debug, thiserror::Error, derive_more::Display)] #[non_exhaustive] @@ -98,10 +98,10 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &models::RootArtifactSpec) -> serde_json::Value { + fn serialize_artifact(&self, object: &spec::RootArtifact) -> serde_json::Value { let now = chrono::Local::now(); let now_tz = now.with_timezone(&self.timezone); - let out_artifact = models::RootSpec { + let out_artifact = spec::Root { artifact: object.clone(), timestamp: now_tz, seqno: self.next_sequence_no(), @@ -114,7 +114,7 @@ impl JsonEmitter { self.sequence_no.load(atomic::Ordering::SeqCst) } - pub async fn emit(&self, object: &models::RootArtifactSpec) -> Result<(), WriterError> { + pub async fn emit(&self, object: &spec::RootArtifact) -> Result<(), WriterError> { let serialized = self.serialize_artifact(object); match self.writer { WriterType::File(ref file) => file.write(&serialized.to_string()).await?, @@ -139,8 +139,8 @@ mod tests { async fn test_emit_using_buffer_writer() -> Result<()> { let expected = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 1 }); @@ -164,15 +164,15 @@ mod tests { async fn test_sequence_number_increments_at_each_call() -> Result<()> { let expected_1 = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 1 }); let expected_2 = json!({ "schemaVersion": { - "major": models::SPEC_VERSION.0, - "minor": models::SPEC_VERSION.1, + "major": spec::SPEC_VERSION.0, + "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 2 }); diff --git a/src/output/error.rs b/src/output/error.rs index a940c10..22b1d17 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -5,13 +5,14 @@ // https://opensource.org/licenses/MIT. use crate::output as tv; -use tv::{dut, models}; +use crate::spec; +use tv::dut; pub struct Error { symptom: String, message: Option, - software_infos: Option>, - source_location: Option, + software_infos: Option>, + source_location: Option, } impl Error { @@ -19,8 +20,8 @@ impl Error { ErrorBuilder::new(symptom) } - pub fn to_artifact(&self) -> models::ErrorSpec { - models::ErrorSpec { + pub fn to_artifact(&self) -> spec::Error { + spec::Error { symptom: self.symptom.clone(), message: self.message.clone(), software_infos: self.software_infos.clone(), @@ -33,8 +34,8 @@ impl Error { pub struct ErrorBuilder { symptom: String, message: Option, - software_infos: Option>, - source_location: Option, + software_infos: Option>, + source_location: Option, } impl ErrorBuilder { @@ -51,7 +52,7 @@ impl ErrorBuilder { self } pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { - self.source_location = Some(models::SourceLocationSpec { + self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, }); @@ -86,7 +87,8 @@ mod tests { use super::*; use crate::output as tv; - use tv::{dut, models}; + use crate::spec; + use tv::dut; #[test] fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { @@ -99,7 +101,7 @@ mod tests { let artifact = error.to_artifact(); assert_eq!( artifact, - models::ErrorSpec { + spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), software_infos: error.software_infos.clone(), @@ -121,7 +123,7 @@ mod tests { let artifact = error.to_artifact(); assert_eq!( artifact, - models::ErrorSpec { + spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), software_infos: error.software_infos.clone(), diff --git a/src/output/log.rs b/src/output/log.rs index 8228b58..baf8635 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -4,13 +4,12 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use crate::output as tv; -use tv::models; +use crate::spec; pub struct Log { - severity: models::LogSeverity, + severity: spec::LogSeverity, message: String, - source_location: Option, + source_location: Option, } impl Log { @@ -18,8 +17,8 @@ impl Log { LogBuilder::new(message) } - pub fn to_artifact(&self) -> models::LogSpec { - models::LogSpec { + pub fn to_artifact(&self) -> spec::Log { + spec::Log { severity: self.severity.clone(), message: self.message.clone(), source_location: self.source_location.clone(), @@ -29,25 +28,25 @@ impl Log { #[derive(Debug)] pub struct LogBuilder { - severity: models::LogSeverity, + severity: spec::LogSeverity, message: String, - source_location: Option, + source_location: Option, } impl LogBuilder { fn new(message: &str) -> Self { LogBuilder { - severity: models::LogSeverity::Info, + severity: spec::LogSeverity::Info, message: message.to_string(), source_location: None, } } - pub fn severity(mut self, value: models::LogSeverity) -> LogBuilder { + pub fn severity(mut self, value: spec::LogSeverity) -> LogBuilder { self.severity = value; self } pub fn source(mut self, file: &str, line: i32) -> LogBuilder { - self.source_location = Some(models::SourceLocationSpec { + self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, }); @@ -68,19 +67,18 @@ mod tests { use anyhow::Result; use super::*; - use crate::output as tv; - use tv::models; + use crate::spec; #[test] fn test_log_output_as_test_run_descendant_to_artifact() -> Result<()> { let log = Log::builder("test") - .severity(models::LogSeverity::Info) + .severity(spec::LogSeverity::Info) .build(); let artifact = log.to_artifact(); assert_eq!( artifact, - models::LogSpec { + spec::Log { severity: log.severity.clone(), message: log.message.clone(), source_location: log.source_location.clone(), @@ -93,13 +91,13 @@ mod tests { #[test] fn test_log_output_as_test_step_descendant_to_artifact() -> Result<()> { let log = Log::builder("test") - .severity(models::LogSeverity::Info) + .severity(spec::LogSeverity::Info) .build(); let artifact = log.to_artifact(); assert_eq!( artifact, - models::LogSpec { + spec::Log { severity: log.severity.clone(), message: log.message.clone(), source_location: log.source_location.clone(), diff --git a/src/output/measurement.rs b/src/output/measurement.rs index 5badd7f..249df84 100644 --- a/src/output/measurement.rs +++ b/src/output/measurement.rs @@ -14,7 +14,8 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; -use tv::{dut, emitter, models, state}; +use crate::spec; +use tv::{dut, emitter, state}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -242,17 +243,17 @@ impl MeasurementSeries { #[derive(Clone)] pub struct Validator { name: Option, - validator_type: models::ValidatorType, + validator_type: spec::ValidatorType, value: Value, metadata: Option>, } impl Validator { - pub fn builder(validator_type: models::ValidatorType, value: Value) -> ValidatorBuilder { + pub fn builder(validator_type: spec::ValidatorType, value: Value) -> ValidatorBuilder { ValidatorBuilder::new(validator_type, value) } - pub fn to_spec(&self) -> models::ValidatorSpec { - models::ValidatorSpec { + pub fn to_spec(&self) -> spec::Validator { + spec::Validator { name: self.name.clone(), validator_type: self.validator_type.clone(), value: self.value.clone(), @@ -264,13 +265,13 @@ impl Validator { #[derive(Debug)] pub struct ValidatorBuilder { name: Option, - validator_type: models::ValidatorType, + validator_type: spec::ValidatorType, value: Value, metadata: Option>, } impl ValidatorBuilder { - fn new(validator_type: models::ValidatorType, value: Value) -> Self { + fn new(validator_type: spec::ValidatorType, value: Value) -> Self { ValidatorBuilder { validator_type, value: value.clone(), @@ -405,9 +406,9 @@ impl Measurement { /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement(models::MeasurementSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { name: self.name.clone(), unit: self.unit.clone(), value: self.value.clone(), @@ -633,10 +634,10 @@ impl MeasurementSeriesStart { MeasurementSeriesStartBuilder::new(name, series_id) } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name: self.name.clone(), unit: self.unit.clone(), series_id: self.series_id.clone(), @@ -758,10 +759,10 @@ impl MeasurementSeriesEnd { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( + spec::MeasurementSeriesEnd { series_id: self.series_id.clone(), total_count: self.total_count, }, @@ -794,10 +795,10 @@ impl MeasurementSeriesElement { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesElement( - models::MeasurementSeriesElementSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesElement( + spec::MeasurementSeriesElement { index: self.index, value: self.value.clone(), timestamp: self.timestamp, @@ -813,8 +814,9 @@ impl MeasurementSeriesElement { mod tests { use super::*; use crate::output as tv; + use crate::spec; + use tv::dut::*; use tv::ValidatorType; - use tv::{dut::*, models}; use anyhow::{bail, Result}; @@ -827,18 +829,16 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name: name.to_string(), - unit: None, - value, - validators: None, - hardware_info_id: None, - subcomponent: None, - metadata: None, - } - ), + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + }), }) ); @@ -850,7 +850,7 @@ mod tests { let name = "name".to_owned(); let value = Value::from(50000); let hardware_info = HardwareInfo::builder("id", "name").build(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); + let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); let meta_key = "key"; let meta_value = Value::from("value"); @@ -874,18 +874,16 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Measurement( - models::MeasurementSpec { - name, - unit: Some(unit.to_string()), - value, - validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(metadata), - } - ), + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { + name, + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + }), }) ); @@ -901,9 +899,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name: name.to_string(), unit: None, series_id: series_id.to_string(), @@ -923,8 +921,8 @@ mod tests { fn test_measurement_series_start_builder_to_artifact() -> Result<()> { let name = "name".to_owned(); let series_id = "series_id".to_owned(); - let validator = Validator::builder(models::ValidatorType::Equal, 30.into()).build(); - let validator2 = Validator::builder(models::ValidatorType::GreaterThen, 10.into()).build(); + let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); + let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); let hw_info = HardwareInfo::builder("id", "name").build(); let subcomponent = Subcomponent::builder("name").build(); let series = MeasurementSeriesStart::builder(&name, &series_id) @@ -940,9 +938,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesStart( - models::MeasurementSeriesStartSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( + spec::MeasurementSeriesStart { name, unit: Some("unit".to_string()), series_id: series_id.to_string(), @@ -969,9 +967,9 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::MeasurementSeriesEnd( - models::MeasurementSeriesEndSpec { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( + spec::MeasurementSeriesEnd { series_id: series_id.to_string(), total_count: 1, } diff --git a/src/output/mod.rs b/src/output/mod.rs index 6326631..17cd5f4 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -11,22 +11,21 @@ mod error; mod log; mod macros; mod measurement; -mod models; mod run; mod state; mod step; +pub use crate::spec::LogSeverity; +pub use crate::spec::TestResult; +pub use crate::spec::TestStatus; +pub use crate::spec::ValidatorType; +pub use crate::spec::SPEC_VERSION; pub use config::*; pub use dut::*; pub use emitter::*; pub use error::*; pub use log::*; pub use measurement::*; -pub use models::LogSeverity; -pub use models::TestResult; -pub use models::TestStatus; -pub use models::ValidatorType; -pub use models::SPEC_VERSION; pub use run::*; pub use step::*; diff --git a/src/output/run.rs b/src/output/run.rs index dce57f5..c3cc844 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -12,16 +12,17 @@ use serde_json::Value; use tokio::sync::Mutex; use crate::output as tv; +use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log, models, run, state}; +use tv::{config, dut, emitter, error, log, run, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. pub struct TestRunOutcome { /// Reports the execution status of the test - pub status: models::TestStatus, + pub status: spec::TestStatus, /// Reports the result of the test - pub result: models::TestResult, + pub result: spec::TestResult, } /// The main diag test run. @@ -303,8 +304,8 @@ impl StartedTestRun { /// ``` pub async fn end( &self, - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, ) -> Result<(), emitter::WriterError> { let end = run::TestRunEnd::builder() .status(status) @@ -341,18 +342,18 @@ impl StartedTestRun { /// ``` pub async fn log( &self, - severity: models::LogSeverity, + severity: spec::LogSeverity, msg: &str, ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -384,11 +385,11 @@ impl StartedTestRun { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -416,11 +417,11 @@ impl StartedTestRun { let error = error::Error::builder(symptom).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -453,11 +454,11 @@ impl StartedTestRun { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -493,11 +494,11 @@ impl StartedTestRun { ) -> Result<(), emitter::WriterError> { let emitter = &self.run.state.lock().await.emitter; - let artifact = models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestRunArtifact(artifact)) + .emit(&spec::RootArtifact::TestRunArtifact(artifact)) .await?; Ok(()) @@ -528,9 +529,9 @@ impl TestRunStart { TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::TestRunStart(models::TestRunStartSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::TestRunStart(spec::TestRunStart { name: self.name.clone(), version: self.version.clone(), command_line: self.command_line.clone(), @@ -597,8 +598,8 @@ impl TestRunStartBuilder { } pub struct TestRunEnd { - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, } impl TestRunEnd { @@ -606,9 +607,9 @@ impl TestRunEnd { TestRunEndBuilder::new() } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestRunArtifact(models::TestRunArtifactSpec { - artifact: models::TestRunArtifactDescendant::TestRunEnd(models::TestRunEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactDescendant::TestRunEnd(spec::TestRunEnd { status: self.status.clone(), result: self.result.clone(), }), @@ -618,24 +619,24 @@ impl TestRunEnd { #[derive(Debug)] pub struct TestRunEndBuilder { - status: models::TestStatus, - result: models::TestResult, + status: spec::TestStatus, + result: spec::TestResult, } #[allow(clippy::new_without_default)] impl TestRunEndBuilder { pub fn new() -> TestRunEndBuilder { TestRunEndBuilder { - status: models::TestStatus::Complete, - result: models::TestResult::Pass, + status: spec::TestStatus::Complete, + result: spec::TestResult::Pass, } } - pub fn status(mut self, value: models::TestStatus) -> TestRunEndBuilder { + pub fn status(mut self, value: spec::TestStatus) -> TestRunEndBuilder { self.status = value; self } - pub fn result(mut self, value: models::TestResult) -> TestRunEndBuilder { + pub fn result(mut self, value: spec::TestResult) -> TestRunEndBuilder { self.result = value; self } @@ -658,13 +659,13 @@ pub struct SchemaVersion { impl SchemaVersion { pub fn new() -> SchemaVersion { SchemaVersion { - major: models::SPEC_VERSION.0, - minor: models::SPEC_VERSION.1, + major: spec::SPEC_VERSION.0, + minor: spec::SPEC_VERSION.1, } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::SchemaVersion(models::SchemaVersionSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::SchemaVersion(spec::SchemaVersion { major: self.major, minor: self.minor, }) @@ -676,14 +677,13 @@ mod tests { use anyhow::Result; use super::*; - use crate::output as tv; - use tv::models; + use crate::spec; #[test] fn test_schema_creation_from_builder() -> Result<()> { let version = SchemaVersion::new(); - assert_eq!(version.major, models::SPEC_VERSION.0); - assert_eq!(version.minor, models::SPEC_VERSION.1); + assert_eq!(version.major, spec::SPEC_VERSION.0); + assert_eq!(version.minor, spec::SPEC_VERSION.1); Ok(()) } } diff --git a/src/output/step.rs b/src/output/step.rs index de8da4d..20fa755 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -10,8 +10,9 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; +use crate::spec; use tv::measurement::MeasurementSeries; -use tv::{emitter, error, log, measurement, models, state, step}; +use tv::{emitter, error, log, measurement, state, step}; /// A single test step in the scope of a [`TestRun`]. /// @@ -123,7 +124,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self, status: models::TestStatus) -> Result<(), emitter::WriterError> { + pub async fn end(&self, status: spec::TestStatus) -> Result<(), emitter::WriterError> { let end = step::TestStepEnd::new(status); self.step .state @@ -178,17 +179,17 @@ impl StartedTestStep { /// ``` pub async fn log( &self, - severity: models::LogSeverity, + severity: spec::LogSeverity, msg: &str, ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -222,11 +223,11 @@ impl StartedTestStep { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Log(log.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -274,11 +275,11 @@ impl StartedTestStep { let error = error::Error::builder(symptom).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -331,11 +332,11 @@ impl StartedTestStep { let error = error::Error::builder(symptom).message(msg).build(); let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -373,11 +374,11 @@ impl StartedTestStep { ) -> Result<(), emitter::WriterError> { let emitter = &self.step.state.lock().await.emitter; - let artifact = models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::Error(error.to_artifact()), + let artifact = spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), }; emitter - .emit(&models::RootArtifactSpec::TestStepArtifact(artifact)) + .emit(&spec::RootArtifact::TestStepArtifact(artifact)) .await?; Ok(()) @@ -527,29 +528,27 @@ impl TestStepStart { } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepStart( - models::TestStepStartSpec { - name: self.name.clone(), - }, - ), + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::TestStepStart(spec::TestStepStart { + name: self.name.clone(), + }), }) } } pub struct TestStepEnd { - status: models::TestStatus, + status: spec::TestStatus, } impl TestStepEnd { - pub fn new(status: models::TestStatus) -> TestStepEnd { + pub fn new(status: spec::TestStatus) -> TestStepEnd { TestStepEnd { status } } - pub fn to_artifact(&self) -> models::RootArtifactSpec { - models::RootArtifactSpec::TestStepArtifact(models::TestStepArtifactSpec { - descendant: models::TestStepArtifactDescendant::TestStepEnd(models::TestStepEndSpec { + pub fn to_artifact(&self) -> spec::RootArtifact { + spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + descendant: spec::TestStepArtifactDescendant::TestStepEnd(spec::TestStepEnd { status: self.status.clone(), }), }) diff --git a/src/output/models.rs b/src/spec.rs similarity index 91% rename from src/output/models.rs rename to src/spec.rs index db187d1..d60baae 100644 --- a/src/output/models.rs +++ b/src/spec.rs @@ -41,65 +41,65 @@ mod rfc3339_format { #[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestRunArtifactDescendant { #[serde(rename = "testRunStart")] - TestRunStart(TestRunStartSpec), + TestRunStart(TestRunStart), #[serde(rename = "testRunEnd")] - TestRunEnd(TestRunEndSpec), + TestRunEnd(TestRunEnd), #[serde(rename = "log")] - Log(LogSpec), + Log(Log), #[serde(rename = "error")] - Error(ErrorSpec), + Error(Error), } #[derive(Debug, Serialize, PartialEq, Clone)] -pub enum RootArtifactSpec { +pub enum RootArtifact { #[serde(rename = "schemaVersion")] - SchemaVersion(SchemaVersionSpec), + SchemaVersion(SchemaVersion), #[serde(rename = "testRunArtifact")] - TestRunArtifact(TestRunArtifactSpec), + TestRunArtifact(TestRunArtifact), #[serde(rename = "testStepArtifact")] - TestStepArtifact(TestStepArtifactSpec), + TestStepArtifact(TestStepArtifact), } #[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize, PartialEq, Clone)] pub enum TestStepArtifactDescendant { #[serde(rename = "testStepStart")] - TestStepStart(TestStepStartSpec), + TestStepStart(TestStepStart), #[serde(rename = "testStepEnd")] - TestStepEnd(TestStepEndSpec), + TestStepEnd(TestStepEnd), #[serde(rename = "measurement")] - Measurement(MeasurementSpec), + Measurement(Measurement), #[serde(rename = "measurementSeriesStart")] - MeasurementSeriesStart(MeasurementSeriesStartSpec), + MeasurementSeriesStart(MeasurementSeriesStart), #[serde(rename = "measurementSeriesEnd")] - MeasurementSeriesEnd(MeasurementSeriesEndSpec), + MeasurementSeriesEnd(MeasurementSeriesEnd), #[serde(rename = "measurementSeriesElement")] - MeasurementSeriesElement(MeasurementSeriesElementSpec), + MeasurementSeriesElement(MeasurementSeriesElement), #[serde(rename = "diagnosis")] - Diagnosis(DiagnosisSpec), + Diagnosis(Diagnosis), #[serde(rename = "log")] - Log(LogSpec), + Log(Log), #[serde(rename = "error")] - Error(ErrorSpec), + Error(Error), #[serde(rename = "file")] - File(FileSpec), + File(File), #[serde(rename = "extension")] - Extension(ExtensionSpec), + Extension(Extension), } #[derive(Debug, Serialize, Clone, PartialEq)] @@ -234,9 +234,9 @@ pub enum SoftwareType { } #[derive(Debug, Serialize, Clone)] -pub struct RootSpec { +pub struct Root { #[serde(flatten)] - pub artifact: RootArtifactSpec, + pub artifact: RootArtifact, // TODO : manage different timezones #[serde(rename = "timestamp")] @@ -254,7 +254,7 @@ pub struct RootSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "schemaVersion")] -pub struct SchemaVersionSpec { +pub struct SchemaVersion { #[serde(rename = "major")] pub major: i8, @@ -268,7 +268,7 @@ pub struct SchemaVersionSpec { /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_artifact.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunArtifact #[derive(Debug, Serialize, PartialEq, Clone)] -pub struct TestRunArtifactSpec { +pub struct TestRunArtifact { #[serde(flatten)] pub artifact: TestRunArtifactDescendant, } @@ -280,7 +280,7 @@ pub struct TestRunArtifactSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunStart")] -pub struct TestRunStartSpec { +pub struct TestRunStart { #[serde(rename = "name")] pub name: String, @@ -294,7 +294,7 @@ pub struct TestRunStartSpec { pub parameters: Map, #[serde(rename = "dutInfo")] - pub dut_info: DutInfoSpec, + pub dut_info: DutInfo, #[serde(rename = "metadata")] pub metadata: Option>, @@ -307,7 +307,7 @@ pub struct TestRunStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "dutInfo")] -pub struct DutInfoSpec { +pub struct DutInfo { #[serde(rename = "dutInfoId")] pub id: String, @@ -315,13 +315,13 @@ pub struct DutInfoSpec { pub name: Option, #[serde(rename = "platformInfos")] - pub platform_infos: Option>, + pub platform_infos: Option>, #[serde(rename = "softwareInfos")] - pub software_infos: Option>, + pub software_infos: Option>, #[serde(rename = "hardwareInfos")] - pub hardware_infos: Option>, + pub hardware_infos: Option>, #[serde(rename = "metadata")] pub metadata: Option>, @@ -334,7 +334,7 @@ pub struct DutInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "platformInfo")] -pub struct PlatformInfoSpec { +pub struct PlatformInfo { #[serde(rename = "info")] pub info: String, } @@ -346,7 +346,7 @@ pub struct PlatformInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareInfo")] -pub struct SoftwareInfoSpec { +pub struct SoftwareInfo { #[serde(rename = "softwareInfoId")] pub id: String, @@ -373,7 +373,7 @@ pub struct SoftwareInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "hardwareInfo")] -pub struct HardwareInfoSpec { +pub struct HardwareInfo { #[serde(rename = "hardwareInfoId")] pub id: String, @@ -418,7 +418,7 @@ pub struct HardwareInfoSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunEnd")] -pub struct TestRunEndSpec { +pub struct TestRunEnd { #[serde(rename = "status")] pub status: TestStatus, @@ -434,7 +434,7 @@ pub struct TestRunEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] -pub struct ErrorSpec { +pub struct Error { #[serde(rename = "symptom")] pub symptom: String, @@ -443,10 +443,10 @@ pub struct ErrorSpec { // TODO: support this field during serialization to print only the id of SoftwareInfo struct #[serde(rename = "softwareInfoIds")] - pub software_infos: Option>, + pub software_infos: Option>, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Low-level model for `log` spec object. @@ -456,7 +456,7 @@ pub struct ErrorSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "log")] -pub struct LogSpec { +pub struct Log { #[serde(rename = "severity")] pub severity: LogSeverity, @@ -464,7 +464,7 @@ pub struct LogSpec { pub message: String, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Provides information about which file/line of the source code in @@ -474,7 +474,7 @@ pub struct LogSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation #[derive(Debug, Serialize, Clone, Default, PartialEq)] #[serde(rename = "sourceLocation")] -pub struct SourceLocationSpec { +pub struct SourceLocation { #[serde(rename = "file")] pub file: String, @@ -488,7 +488,7 @@ pub struct SourceLocationSpec { /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact #[derive(Debug, Serialize, PartialEq, Clone)] -pub struct TestStepArtifactSpec { +pub struct TestStepArtifact { #[serde(flatten)] pub descendant: TestStepArtifactDescendant, } @@ -500,7 +500,7 @@ pub struct TestStepArtifactSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepStart")] -pub struct TestStepStartSpec { +pub struct TestStepStart { #[serde(rename = "name")] pub name: String, } @@ -512,7 +512,7 @@ pub struct TestStepStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepEnd")] -pub struct TestStepEndSpec { +pub struct TestStepEnd { #[serde(rename = "status")] pub status: TestStatus, } @@ -524,7 +524,7 @@ pub struct TestStepEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] -pub struct MeasurementSpec { +pub struct Measurement { #[serde(rename = "name")] pub name: String, @@ -535,13 +535,13 @@ pub struct MeasurementSpec { pub unit: Option, #[serde(rename = "validators")] - pub validators: Option>, + pub validators: Option>, #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, #[serde(rename = "subcomponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "metadata")] pub metadata: Option>, @@ -554,7 +554,7 @@ pub struct MeasurementSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] -pub struct ValidatorSpec { +pub struct Validator { #[serde(rename = "name")] pub name: Option, @@ -575,7 +575,7 @@ pub struct ValidatorSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] -pub struct SubcomponentSpec { +pub struct Subcomponent { #[serde(rename = "type")] pub subcomponent_type: Option, @@ -599,7 +599,7 @@ pub struct SubcomponentSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] -pub struct MeasurementSeriesStartSpec { +pub struct MeasurementSeriesStart { #[serde(rename = "name")] pub name: String, @@ -610,13 +610,13 @@ pub struct MeasurementSeriesStartSpec { pub series_id: String, #[serde(rename = "validators")] - pub validators: Option>, + pub validators: Option>, #[serde(rename = "hardwareInfoId")] - pub hardware_info: Option, + pub hardware_info: Option, #[serde(rename = "subComponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "metadata")] pub metadata: Option>, @@ -629,7 +629,7 @@ pub struct MeasurementSeriesStartSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesEnd")] -pub struct MeasurementSeriesEndSpec { +pub struct MeasurementSeriesEnd { #[serde(rename = "measurementSeriesId")] pub series_id: String, @@ -644,7 +644,7 @@ pub struct MeasurementSeriesEndSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesElement #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesElement")] -pub struct MeasurementSeriesElementSpec { +pub struct MeasurementSeriesElement { #[serde(rename = "index")] pub index: u64, @@ -668,7 +668,7 @@ pub struct MeasurementSeriesElementSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "diagnosis")] -pub struct DiagnosisSpec { +pub struct Diagnosis { #[serde(rename = "verdict")] pub verdict: String, @@ -679,13 +679,13 @@ pub struct DiagnosisSpec { pub message: Option, #[serde(rename = "validators")] - pub hardware_info: Option, + pub hardware_info: Option, #[serde(rename = "subComponent")] - pub subcomponent: Option, + pub subcomponent: Option, #[serde(rename = "sourceLocation")] - pub source_location: Option, + pub source_location: Option, } /// Low-level model for the `file` spec object. @@ -695,7 +695,7 @@ pub struct DiagnosisSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] -pub struct FileSpec { +pub struct File { #[serde(rename = "name")] pub name: String, @@ -722,7 +722,7 @@ pub struct FileSpec { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "extension")] -pub struct ExtensionSpec { +pub struct Extension { #[serde(rename = "name")] pub name: String, @@ -742,7 +742,7 @@ mod tests { #[test] fn test_rfc3339_format_serialize() -> Result<()> { let test_date = "2022-01-01T00:00:00.000Z"; - let msr = MeasurementSeriesElementSpec { + let msr = MeasurementSeriesElement { index: 0, value: 1.0.into(), timestamp: DateTime::parse_from_rfc3339(test_date)?.with_timezone(&chrono_tz::UTC), @@ -762,7 +762,7 @@ mod tests { let test_date = "2022-01-01T00:00:00.000Z"; let json = json!({"index":0,"measurementSeriesId":"test","metadata":null,"timestamp":"2022-01-01T00:00:00.000Z","value":1.0}); - let msr = serde_json::from_value::(json)?; + let msr = serde_json::from_value::(json)?; assert_eq!( msr.timestamp.to_rfc3339_opts(SecondsFormat::Millis, true), test_date From 2e12ab177a7cf2a290de900167d01f2cdd003aa5 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 20:18:40 +0100 Subject: [PATCH 22/96] add non_exhaustive to public enums - this is needed in case the spec adds new variants; otherwise we break consumers of these enums that may have matched exhaustively Signed-off-by: mimir-d --- src/spec.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/spec.rs b/src/spec.rs index d60baae..dc91774 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -103,6 +103,7 @@ pub enum TestStepArtifactDescendant { } #[derive(Debug, Serialize, Clone, PartialEq)] +#[non_exhaustive] pub enum ValidatorType { #[serde(rename = "EQUAL")] Equal, @@ -175,6 +176,7 @@ pub enum DiagnosisType { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStatus #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testStatus")] +#[non_exhaustive] pub enum TestStatus { #[serde(rename = "COMPLETE")] Complete, @@ -190,6 +192,7 @@ pub enum TestStatus { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd/$defs/testResult #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testResult")] +#[non_exhaustive] pub enum TestResult { #[serde(rename = "PASS")] Pass, @@ -198,11 +201,13 @@ pub enum TestResult { #[serde(rename = "NOT_APPLICABLE")] NotApplicable, } + /// Known log severity variants. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#severity /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log/$defs/severity #[derive(Debug, Serialize, Clone, PartialEq)] +#[non_exhaustive] pub enum LogSeverity { #[serde(rename = "DEBUG")] Debug, From f7a5696e12fbd54f639d5b2330998e28665d4e80 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 22:04:04 +0100 Subject: [PATCH 23/96] current impl was missing testStepId; add it - note that the tests are not catching the fact that this new field is missing from the expected output, because all the tests are doing an "included" test, rather than the correct equality; fixing this in a later commit Signed-off-by: mimir-d --- src/output/emitter.rs | 8 +- src/output/measurement.rs | 282 +++++++++++++++++--------------------- src/output/run.rs | 15 +- src/output/step.rs | 146 ++++++++++---------- src/spec.rs | 3 + tests/output/runner.rs | 11 +- 6 files changed, 230 insertions(+), 235 deletions(-) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index e26acd2..9bfd8bd 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -84,9 +84,9 @@ impl StdoutWriter { } pub struct JsonEmitter { - sequence_no: Arc, timezone: chrono_tz::Tz, writer: WriterType, + seqno: Arc, } impl JsonEmitter { @@ -94,7 +94,7 @@ impl JsonEmitter { JsonEmitter { timezone, writer, - sequence_no: Arc::new(atomic::AtomicU64::new(0)), + seqno: Arc::new(atomic::AtomicU64::new(0)), } } @@ -110,8 +110,8 @@ impl JsonEmitter { } fn next_sequence_no(&self) -> u64 { - self.sequence_no.fetch_add(1, atomic::Ordering::SeqCst); - self.sequence_no.load(atomic::Ordering::SeqCst) + self.seqno.fetch_add(1, atomic::Ordering::SeqCst); + self.seqno.load(atomic::Ordering::SeqCst) } pub async fn emit(&self, object: &spec::RootArtifact) -> Result<(), WriterError> { diff --git a/src/output/measurement.rs b/src/output/measurement.rs index 249df84..a874bd9 100644 --- a/src/output/measurement.rs +++ b/src/output/measurement.rs @@ -15,22 +15,25 @@ use tokio::sync::Mutex; use crate::output as tv; use crate::spec; -use tv::{dut, emitter, state}; +use tv::{dut, emitter, step}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -pub struct MeasurementSeries { - state: Arc>, +pub struct MeasurementSeries<'a> { + // note: intentional design to only allow 1 thread to output; may need + // revisiting in the future, if there's a case for multithreaded writers + emitter: &'a step::StepEmitter, + seq_no: Arc>, start: MeasurementSeriesStart, } -impl MeasurementSeries { - pub(crate) fn new(series_id: &str, name: &str, state: Arc>) -> Self { +impl<'a> MeasurementSeries<'a> { + pub(crate) fn new(series_id: &str, name: &str, emitter: &'a step::StepEmitter) -> Self { Self { - state, + emitter, seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), start: MeasurementSeriesStart::new(name, series_id), } @@ -38,10 +41,10 @@ impl MeasurementSeries { pub(crate) fn new_with_details( start: MeasurementSeriesStart, - state: Arc>, + emitter: &'a step::StepEmitter, ) -> Self { Self { - state, + emitter, seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), start, } @@ -78,11 +81,10 @@ impl MeasurementSeries { /// # }); /// ``` pub async fn start(&self) -> Result<(), emitter::WriterError> { - self.state - .lock() - .await - .emitter - .emit(&self.start.to_artifact()) + self.emitter + .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesStart( + self.start.to_artifact(), + )) .await?; Ok(()) } @@ -110,12 +112,12 @@ impl MeasurementSeries { pub async fn end(&self) -> Result<(), emitter::WriterError> { let end = MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); - self.state - .lock() - .await - .emitter - .emit(&end.to_artifact()) + self.emitter + .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesEnd( + end.to_artifact(), + )) .await?; + Ok(()) } @@ -147,12 +149,13 @@ impl MeasurementSeries { None, ); self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) + + self.emitter + .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesElement( + element.to_artifact(), + )) .await?; + Ok(()) } @@ -191,12 +194,13 @@ impl MeasurementSeries { )), ); self.increment_sequence_no().await; - self.state - .lock() - .await - .emitter - .emit(&element.to_artifact()) + + self.emitter + .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesElement( + element.to_artifact(), + )) .await?; + Ok(()) } @@ -228,10 +232,10 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitter::WriterError> + pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), emitter::WriterError> where R: Future>, - F: std::ops::FnOnce(&'a MeasurementSeries) -> R, + F: std::ops::FnOnce(&'s MeasurementSeries) -> R, { self.start().await?; func(self).await?; @@ -406,27 +410,25 @@ impl Measurement { /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { - name: self.name.clone(), - unit: self.unit.clone(), - value: self.value.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info_id: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.id().to_owned()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }), - }) + pub fn to_artifact(&self) -> spec::Measurement { + spec::Measurement { + name: self.name.clone(), + unit: self.unit.clone(), + value: self.value.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info_id: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.id().to_owned()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + } } } @@ -634,29 +636,25 @@ impl MeasurementSeriesStart { MeasurementSeriesStartBuilder::new(name, series_id) } - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( - spec::MeasurementSeriesStart { - name: self.name.clone(), - unit: self.unit.clone(), - series_id: self.series_id.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info: self - .hardware_info - .as_ref() - .map(|hardware_info| hardware_info.to_spec()), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - }, - ), - }) + pub fn to_artifact(&self) -> spec::MeasurementSeriesStart { + spec::MeasurementSeriesStart { + name: self.name.clone(), + unit: self.unit.clone(), + series_id: self.series_id.clone(), + validators: self + .validators + .clone() + .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + hardware_info: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.to_spec()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + metadata: self.metadata.clone(), + } } pub fn get_series_id(&self) -> &str { @@ -759,15 +757,11 @@ impl MeasurementSeriesEnd { } } - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( - spec::MeasurementSeriesEnd { - series_id: self.series_id.clone(), - total_count: self.total_count, - }, - ), - }) + pub fn to_artifact(&self) -> spec::MeasurementSeriesEnd { + spec::MeasurementSeriesEnd { + series_id: self.series_id.clone(), + total_count: self.total_count, + } } } @@ -795,18 +789,14 @@ impl MeasurementSeriesElement { } } - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesElement( - spec::MeasurementSeriesElement { - index: self.index, - value: self.value.clone(), - timestamp: self.timestamp, - series_id: self.series_id.clone(), - metadata: self.metadata.clone(), - }, - ), - }) + pub fn to_artifact(&self) -> spec::MeasurementSeriesElement { + spec::MeasurementSeriesElement { + index: self.index, + value: self.value.clone(), + timestamp: self.timestamp, + series_id: self.series_id.clone(), + metadata: self.metadata.clone(), + } } } @@ -829,17 +819,15 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { - name: name.to_string(), - unit: None, - value, - validators: None, - hardware_info_id: None, - subcomponent: None, - metadata: None, - }), - }) + spec::Measurement { + name: name.to_string(), + unit: None, + value, + validators: None, + hardware_info_id: None, + subcomponent: None, + metadata: None, + } ); Ok(()) @@ -874,17 +862,15 @@ mod tests { let artifact = measurement.to_artifact(); assert_eq!( artifact, - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Measurement(spec::Measurement { - name, - unit: Some(unit.to_string()), - value, - validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(metadata), - }), - }) + spec::Measurement { + name, + unit: Some(unit.to_string()), + value, + validators: Some(vec![validator.to_spec(), validator.to_spec()]), + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(metadata), + } ); Ok(()) @@ -899,19 +885,15 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( - spec::MeasurementSeriesStart { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - ), - }) + spec::MeasurementSeriesStart { + name: name.to_string(), + unit: None, + series_id: series_id.to_string(), + validators: None, + hardware_info: None, + subcomponent: None, + metadata: None, + } ); Ok(()) @@ -938,22 +920,18 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesStart( - spec::MeasurementSeriesStart { - name, - unit: Some("unit".to_string()), - series_id: series_id.to_string(), - validators: Some(vec![validator.to_spec(), validator2.to_spec()]), - hardware_info: Some(hw_info.to_spec()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(serde_json::Map::from_iter([ - ("key".to_string(), "value".into()), - ("key2".to_string(), "value2".into()) - ])), - } - ), - }) + spec::MeasurementSeriesStart { + name, + unit: Some("unit".to_string()), + series_id: series_id.to_string(), + validators: Some(vec![validator.to_spec(), validator2.to_spec()]), + hardware_info: Some(hw_info.to_spec()), + subcomponent: Some(subcomponent.to_spec()), + metadata: Some(serde_json::Map::from_iter([ + ("key".to_string(), "value".into()), + ("key2".to_string(), "value2".into()) + ])), + } ); Ok(()) @@ -967,14 +945,10 @@ mod tests { let artifact = series.to_artifact(); assert_eq!( artifact, - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::MeasurementSeriesEnd( - spec::MeasurementSeriesEnd { - series_id: series_id.to_string(), - total_count: 1, - } - ), - }) + spec::MeasurementSeriesEnd { + series_id: series_id.to_string(), + total_count: 1, + } ); Ok(()) diff --git a/src/output/run.rs b/src/output/run.rs index c3cc844..d468d9f 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -5,6 +5,8 @@ // https://opensource.org/licenses/MIT. use std::env; +use std::sync::atomic; +use std::sync::atomic::Ordering; use std::sync::Arc; use serde_json::Map; @@ -115,7 +117,7 @@ impl TestRun { .emit(&start.to_artifact()) .await?; - Ok(StartedTestRun { run: self }) + Ok(StartedTestRun::new(self)) } // disabling this for the moment so we don't publish api that's unusable. @@ -283,9 +285,17 @@ impl TestRunBuilder { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart pub struct StartedTestRun { run: TestRun, + + step_seqno: atomic::AtomicU64, } impl StartedTestRun { + fn new(run: TestRun) -> StartedTestRun { + StartedTestRun { + run, + step_seqno: atomic::AtomicU64::new(0), + } + } /// Ends the test run. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend @@ -505,7 +515,8 @@ impl StartedTestRun { } pub fn step(&self, name: &str) -> TestStep { - TestStep::new(name, self.run.state.clone()) + let step_id = format!("step_{}", self.step_seqno.fetch_add(1, Ordering::AcqRel)); + TestStep::new(&step_id, name, self.run.state.clone()) } } diff --git a/src/output/step.rs b/src/output/step.rs index 20fa755..6b9fa36 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -12,21 +12,27 @@ use tokio::sync::Mutex; use crate::output as tv; use crate::spec; use tv::measurement::MeasurementSeries; -use tv::{emitter, error, log, measurement, state, step}; +use tv::{emitter, error, log, measurement, state}; + +use super::WriterError; /// A single test step in the scope of a [`TestRun`]. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts pub struct TestStep { name: String, - state: Arc>, + + emitter: StepEmitter, } impl TestStep { - pub(crate) fn new(name: &str, state: Arc>) -> TestStep { + pub(crate) fn new(id: &str, name: &str, state: Arc>) -> TestStep { TestStep { - name: name.to_string(), - state, + name: name.to_owned(), + emitter: StepEmitter { + state, + step_id: id.to_owned(), + }, } } @@ -47,13 +53,8 @@ impl TestStep { /// # }); /// ``` pub async fn start(self) -> Result { - let start = step::TestStepStart::new(&self.name); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; + let start = TestStepStart::new(&self.name); + self.emitter.emit(&start.to_artifact()).await?; Ok(StartedTestStep { step: self, @@ -125,14 +126,8 @@ impl StartedTestStep { /// # }); /// ``` pub async fn end(&self, status: spec::TestStatus) -> Result<(), emitter::WriterError> { - let end = step::TestStepEnd::new(status); - self.step - .state - .lock() - .await - .emitter - .emit(&end.to_artifact()) - .await?; + let end = TestStepEnd::new(status); + self.step.emitter.emit(&end.to_artifact()).await?; Ok(()) } @@ -183,13 +178,10 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); - let emitter = &self.step.state.lock().await.emitter; - let artifact = spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), - }; - emitter - .emit(&spec::RootArtifact::TestStepArtifact(artifact)) + self.step + .emitter + .emit(&spec::TestStepArtifactDescendant::Log(log.to_artifact())) .await?; Ok(()) @@ -221,13 +213,9 @@ impl StartedTestStep { /// # }); /// ``` pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { - let emitter = &self.step.state.lock().await.emitter; - - let artifact = spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Log(log.to_artifact()), - }; - emitter - .emit(&spec::RootArtifact::TestStepArtifact(artifact)) + self.step + .emitter + .emit(&spec::TestStepArtifactDescendant::Log(log.to_artifact())) .await?; Ok(()) @@ -273,13 +261,12 @@ impl StartedTestStep { /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); - let emitter = &self.step.state.lock().await.emitter; - let artifact = spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), - }; - emitter - .emit(&spec::RootArtifact::TestStepArtifact(artifact)) + self.step + .emitter + .emit(&spec::TestStepArtifactDescendant::Error( + error.to_artifact(), + )) .await?; Ok(()) @@ -330,13 +317,12 @@ impl StartedTestStep { msg: &str, ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); - let emitter = &self.step.state.lock().await.emitter; - let artifact = spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), - }; - emitter - .emit(&spec::RootArtifact::TestStepArtifact(artifact)) + self.step + .emitter + .emit(&spec::TestStepArtifactDescendant::Error( + error.to_artifact(), + )) .await?; Ok(()) @@ -372,13 +358,11 @@ impl StartedTestStep { &self, error: &error::Error, ) -> Result<(), emitter::WriterError> { - let emitter = &self.step.state.lock().await.emitter; - - let artifact = spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::Error(error.to_artifact()), - }; - emitter - .emit(&spec::RootArtifact::TestStepArtifact(artifact)) + self.step + .emitter + .emit(&spec::TestStepArtifactDescendant::Error( + error.to_artifact(), + )) .await?; Ok(()) @@ -409,13 +393,14 @@ impl StartedTestStep { value: Value, ) -> Result<(), emitter::WriterError> { let measurement = measurement::Measurement::new(name, value); + self.step - .state - .lock() - .await .emitter - .emit(&measurement.to_artifact()) + .emit(&spec::TestStepArtifactDescendant::Measurement( + measurement.to_artifact(), + )) .await?; + Ok(()) } @@ -451,12 +436,12 @@ impl StartedTestStep { measurement: &measurement::Measurement, ) -> Result<(), emitter::WriterError> { self.step - .state - .lock() - .await .emitter - .emit(&measurement.to_artifact()) + .emit(&spec::TestStepArtifactDescendant::Measurement( + measurement.to_artifact(), + )) .await?; + Ok(()) } @@ -487,7 +472,7 @@ impl StartedTestStep { self.measurement_id_no.load(atomic::Ordering::SeqCst) ); - MeasurementSeries::new(&series_id, name, self.step.state.clone()) + MeasurementSeries::new(&series_id, name, &self.step.emitter) } /// Starts a Measurement Series (a time-series list of measurements). @@ -513,7 +498,7 @@ impl StartedTestStep { &self, start: measurement::MeasurementSeriesStart, ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, self.step.state.clone()) + MeasurementSeries::new_with_details(start, &self.step.emitter) } } @@ -528,11 +513,9 @@ impl TestStepStart { } } - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::TestStepStart(spec::TestStepStart { - name: self.name.clone(), - }), + pub fn to_artifact(&self) -> spec::TestStepArtifactDescendant { + spec::TestStepArtifactDescendant::TestStepStart(spec::TestStepStart { + name: self.name.clone(), }) } } @@ -546,14 +529,33 @@ impl TestStepEnd { TestStepEnd { status } } - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { - descendant: spec::TestStepArtifactDescendant::TestStepEnd(spec::TestStepEnd { - status: self.status.clone(), - }), + pub fn to_artifact(&self) -> spec::TestStepArtifactDescendant { + spec::TestStepArtifactDescendant::TestStepEnd(spec::TestStepEnd { + status: self.status.clone(), }) } } +// TODO: move this away from here; extract trait Emitter, dont rely on json +// it will be used in measurement series +pub struct StepEmitter { + // emitter: JsonEmitter, + state: Arc>, + step_id: String, +} + +impl StepEmitter { + pub async fn emit(&self, object: &spec::TestStepArtifactDescendant) -> Result<(), WriterError> { + let root = spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + id: self.step_id.clone(), + // TODO: can these copies be avoided? + descendant: object.clone(), + }); + self.state.lock().await.emitter.emit(&root).await?; + + Ok(()) + } +} + #[cfg(test)] mod tests {} diff --git a/src/spec.rs b/src/spec.rs index dc91774..0161cb8 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -494,6 +494,9 @@ pub struct SourceLocation { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestStepArtifact { + #[serde(rename = "testStepId")] + pub id: String, + #[serde(flatten)] pub descendant: TestStepArtifactDescendant, } diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 82426fc..b1f5413 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -498,6 +498,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { json!({ "sequenceNumber": 4, "testStepArtifact": { + "testStepId": "step_0", "error": { "message": "Error message", "softwareInfoIds":[{ @@ -579,12 +580,14 @@ async fn test_step_with_measurement() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { + "testStepId": "step_0", "measurement": { - "name": "name", "value": 50 + "name": "name", + "value": 50 } - } + }, + "sequenceNumber": 4 }), json_step_complete(5), json_run_pass(6), @@ -601,6 +604,8 @@ async fn test_step_with_measurement() -> Result<()> { .await } +// TODO: intentionally leaving these tests broken so that it's obvious later that the +// assert_json_includes was not sufficient; this case is missing `testStepId` field #[tokio::test] async fn test_step_with_measurement_builder() -> Result<()> { let expected = [ From e68165df1f7a42d165930bf742b872135d4c8d18 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 22:42:45 +0100 Subject: [PATCH 24/96] remove a bunch of boilerplate Signed-off-by: mimir-d --- src/output/emitter.rs | 24 ++- src/output/{measurement.rs => measure.rs} | 125 +++-------- src/output/mod.rs | 4 +- src/output/run.rs | 251 +++------------------- src/output/step.rs | 90 +++----- src/spec.rs | 143 ++++++------ 6 files changed, 174 insertions(+), 463 deletions(-) rename src/output/{measurement.rs => measure.rs} (90%) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 9bfd8bd..01752dc 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -98,15 +98,15 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &spec::RootArtifact) -> serde_json::Value { + fn serialize_artifact(&self, object: &spec::RootImpl) -> serde_json::Value { let now = chrono::Local::now(); let now_tz = now.with_timezone(&self.timezone); - let out_artifact = spec::Root { + let root = spec::Root { artifact: object.clone(), timestamp: now_tz, seqno: self.next_sequence_no(), }; - serde_json::json!(out_artifact) + serde_json::json!(root) } fn next_sequence_no(&self) -> u64 { @@ -114,7 +114,7 @@ impl JsonEmitter { self.seqno.load(atomic::Ordering::SeqCst) } - pub async fn emit(&self, object: &spec::RootArtifact) -> Result<(), WriterError> { + pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), WriterError> { let serialized = self.serialize_artifact(object); match self.writer { WriterType::File(ref file) => file.write(&serialized.to_string()).await?, @@ -132,8 +132,6 @@ mod tests { use serde_json::json; use super::*; - use crate::output as tv; - use tv::run::SchemaVersion; #[tokio::test] async fn test_emit_using_buffer_writer() -> Result<()> { @@ -149,8 +147,11 @@ mod tests { let writer = BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); - let version = SchemaVersion::new(); - emitter.emit(&version.to_artifact()).await?; + emitter + .emit(&spec::RootImpl::SchemaVersion( + spec::SchemaVersion::default(), + )) + .await?; let deserialized = serde_json::from_str::( buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, @@ -180,9 +181,10 @@ mod tests { let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); - let version = SchemaVersion::new(); - emitter.emit(&version.to_artifact()).await?; - emitter.emit(&version.to_artifact()).await?; + + let version = spec::RootImpl::SchemaVersion(spec::SchemaVersion::default()); + emitter.emit(&version).await?; + emitter.emit(&version).await?; let deserialized = serde_json::from_str::( buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, diff --git a/src/output/measurement.rs b/src/output/measure.rs similarity index 90% rename from src/output/measurement.rs rename to src/output/measure.rs index a874bd9..152af80 100644 --- a/src/output/measurement.rs +++ b/src/output/measure.rs @@ -8,7 +8,6 @@ use std::future::Future; use std::sync::atomic; use std::sync::Arc; -use chrono::DateTime; use serde_json::Map; use serde_json::Value; use tokio::sync::Mutex; @@ -82,7 +81,7 @@ impl<'a> MeasurementSeries<'a> { /// ``` pub async fn start(&self) -> Result<(), emitter::WriterError> { self.emitter - .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesStart( + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart( self.start.to_artifact(), )) .await?; @@ -110,12 +109,13 @@ impl<'a> MeasurementSeries<'a> { /// # }); /// ``` pub async fn end(&self) -> Result<(), emitter::WriterError> { - let end = - MeasurementSeriesEnd::new(self.start.get_series_id(), self.current_sequence_no().await); + let end = spec::MeasurementSeriesEnd { + series_id: self.start.series_id.clone(), + total_count: self.current_sequence_no().await, + }; + self.emitter - .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesEnd( - end.to_artifact(), - )) + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesEnd(end)) .await?; Ok(()) @@ -142,17 +142,18 @@ impl<'a> MeasurementSeries<'a> { /// # }); /// ``` pub async fn add_measurement(&self, value: Value) -> Result<(), emitter::WriterError> { - let element = MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - None, - ); + let element = spec::MeasurementSeriesElement { + index: self.current_sequence_no().await, + value: value.clone(), + timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + series_id: self.start.series_id.clone(), + metadata: None, + }; self.increment_sequence_no().await; self.emitter - .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesElement( - element.to_artifact(), + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( + element, )) .await?; @@ -185,19 +186,20 @@ impl<'a> MeasurementSeries<'a> { value: Value, metadata: Vec<(&str, Value)>, ) -> Result<(), emitter::WriterError> { - let element = MeasurementSeriesElement::new( - self.current_sequence_no().await, - value, - &self.start, - Some(Map::from_iter( + let element = spec::MeasurementSeriesElement { + index: self.current_sequence_no().await, + value: value.clone(), + timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + series_id: self.start.series_id.clone(), + metadata: Some(Map::from_iter( metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), )), - ); + }; self.increment_sequence_no().await; self.emitter - .emit(&spec::TestStepArtifactDescendant::MeasurementSeriesElement( - element.to_artifact(), + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( + element, )) .await?; @@ -656,10 +658,6 @@ impl MeasurementSeriesStart { metadata: self.metadata.clone(), } } - - pub fn get_series_id(&self) -> &str { - &self.series_id - } } pub struct MeasurementSeriesStartBuilder { @@ -744,62 +742,6 @@ impl MeasurementSeriesStartBuilder { } } -pub struct MeasurementSeriesEnd { - series_id: String, - total_count: u64, -} - -impl MeasurementSeriesEnd { - pub(crate) fn new(series_id: &str, total_count: u64) -> MeasurementSeriesEnd { - MeasurementSeriesEnd { - series_id: series_id.to_string(), - total_count, - } - } - - pub fn to_artifact(&self) -> spec::MeasurementSeriesEnd { - spec::MeasurementSeriesEnd { - series_id: self.series_id.clone(), - total_count: self.total_count, - } - } -} - -pub struct MeasurementSeriesElement { - index: u64, - value: Value, - timestamp: DateTime, - series_id: String, - metadata: Option>, -} - -impl MeasurementSeriesElement { - pub(crate) fn new( - index: u64, - value: Value, - series: &MeasurementSeriesStart, - metadata: Option>, - ) -> MeasurementSeriesElement { - MeasurementSeriesElement { - index, - value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), - series_id: series.series_id.to_string(), - metadata, - } - } - - pub fn to_artifact(&self) -> spec::MeasurementSeriesElement { - spec::MeasurementSeriesElement { - index: self.index, - value: self.value.clone(), - timestamp: self.timestamp, - series_id: self.series_id.clone(), - metadata: self.metadata.clone(), - } - } -} - #[cfg(test)] mod tests { use super::*; @@ -937,23 +879,6 @@ mod tests { Ok(()) } - #[test] - fn test_measurement_series_end_to_artifact() -> Result<()> { - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesEnd::new(&series_id, 1); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - spec::MeasurementSeriesEnd { - series_id: series_id.to_string(), - total_count: 1, - } - ); - - Ok(()) - } - #[test] fn test_validator() -> Result<()> { let validator = Validator::builder(ValidatorType::Equal, 30.into()) diff --git a/src/output/mod.rs b/src/output/mod.rs index 17cd5f4..e7b1e5c 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -10,7 +10,7 @@ mod emitter; mod error; mod log; mod macros; -mod measurement; +mod measure; mod run; mod state; mod step; @@ -25,7 +25,7 @@ pub use dut::*; pub use emitter::*; pub use error::*; pub use log::*; -pub use measurement::*; +pub use measure::*; pub use run::*; pub use step::*; diff --git a/src/output/run.rs b/src/output/run.rs index d468d9f..77e7b77 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -16,7 +16,7 @@ use tokio::sync::Mutex; use crate::output as tv; use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log, run, state}; +use tv::{config, dut, emitter, error, log, state}; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -33,10 +33,10 @@ pub struct TestRunOutcome { pub struct TestRun { name: String, version: String, - parameters: Map, + parameters: Map, dut: dut::DutInfo, command_line: String, - metadata: Option>, + metadata: Option>, state: Arc>, } @@ -87,35 +87,28 @@ impl TestRun { /// # }); /// ``` pub async fn start(self) -> Result { - let version = SchemaVersion::new(); + // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version self.state .lock() .await .emitter - .emit(&version.to_artifact()) + .emit(&spec::RootImpl::SchemaVersion( + spec::SchemaVersion::default(), + )) .await?; - let mut builder = run::TestRunStart::builder( - &self.name, - &self.version, - &self.command_line, - &self.parameters, - &self.dut, - ); - - if let Some(m) = &self.metadata { - for m in m { - builder = builder.add_metadata(m.0, m.1.clone()) - } - } + let start = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactImpl::TestRunStart(spec::TestRunStart { + name: self.name.clone(), + version: self.version.clone(), + command_line: self.command_line.clone(), + parameters: self.parameters.clone(), + metadata: self.metadata.clone(), + dut_info: self.dut.to_spec(), + }), + }); - let start = builder.build(); - self.state - .lock() - .await - .emitter - .emit(&start.to_artifact()) - .await?; + self.state.lock().await.emitter.emit(&start).await?; Ok(StartedTestRun::new(self)) } @@ -317,14 +310,13 @@ impl StartedTestRun { status: spec::TestStatus, result: spec::TestResult, ) -> Result<(), emitter::WriterError> { - let end = run::TestRunEnd::builder() - .status(status) - .result(result) - .build(); + let end = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), + }); let emitter = &self.run.state.lock().await.emitter; - emitter.emit(&end.to_artifact()).await?; + emitter.emit(&end).await?; Ok(()) } @@ -360,10 +352,10 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; emitter - .emit(&spec::RootArtifact::TestRunArtifact(artifact)) + .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; Ok(()) @@ -396,10 +388,10 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::Log(log.to_artifact()), + artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; emitter - .emit(&spec::RootArtifact::TestRunArtifact(artifact)) + .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; Ok(()) @@ -428,10 +420,10 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; emitter - .emit(&spec::RootArtifact::TestRunArtifact(artifact)) + .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; Ok(()) @@ -465,10 +457,10 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; emitter - .emit(&spec::RootArtifact::TestRunArtifact(artifact)) + .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; Ok(()) @@ -505,10 +497,10 @@ impl StartedTestRun { let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::Error(error.to_artifact()), + artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; emitter - .emit(&spec::RootArtifact::TestRunArtifact(artifact)) + .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; Ok(()) @@ -519,182 +511,3 @@ impl StartedTestRun { TestStep::new(&step_id, name, self.run.state.clone()) } } - -pub struct TestRunStart { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: dut::DutInfo, -} - -impl TestRunStart { - pub fn builder( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &dut::DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder::new(name, version, command_line, parameters, dut_info) - } - - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::TestRunStart(spec::TestRunStart { - name: self.name.clone(), - version: self.version.clone(), - command_line: self.command_line.clone(), - parameters: self.parameters.clone(), - metadata: self.metadata.clone(), - dut_info: self.dut_info.to_spec(), - }), - }) - } -} - -pub struct TestRunStartBuilder { - name: String, - version: String, - command_line: String, - parameters: Map, - metadata: Option>, - dut_info: dut::DutInfo, -} - -impl TestRunStartBuilder { - pub fn new( - name: &str, - version: &str, - command_line: &str, - parameters: &Map, - dut_info: &dut::DutInfo, - ) -> TestRunStartBuilder { - TestRunStartBuilder { - name: name.to_string(), - version: version.to_string(), - command_line: command_line.to_string(), - parameters: parameters.clone(), - metadata: None, - dut_info: dut_info.clone(), - } - } - - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunStartBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - }; - self - } - - pub fn build(self) -> TestRunStart { - TestRunStart { - name: self.name, - version: self.version, - command_line: self.command_line, - parameters: self.parameters, - metadata: self.metadata, - dut_info: self.dut_info, - } - } -} - -pub struct TestRunEnd { - status: spec::TestStatus, - result: spec::TestResult, -} - -impl TestRunEnd { - pub fn builder() -> TestRunEndBuilder { - TestRunEndBuilder::new() - } - - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::TestRunArtifact(spec::TestRunArtifact { - artifact: spec::TestRunArtifactDescendant::TestRunEnd(spec::TestRunEnd { - status: self.status.clone(), - result: self.result.clone(), - }), - }) - } -} - -#[derive(Debug)] -pub struct TestRunEndBuilder { - status: spec::TestStatus, - result: spec::TestResult, -} - -#[allow(clippy::new_without_default)] -impl TestRunEndBuilder { - pub fn new() -> TestRunEndBuilder { - TestRunEndBuilder { - status: spec::TestStatus::Complete, - result: spec::TestResult::Pass, - } - } - pub fn status(mut self, value: spec::TestStatus) -> TestRunEndBuilder { - self.status = value; - self - } - - pub fn result(mut self, value: spec::TestResult) -> TestRunEndBuilder { - self.result = value; - self - } - - pub fn build(self) -> TestRunEnd { - TestRunEnd { - status: self.status, - result: self.result, - } - } -} - -// TODO: this likely will go into the emitter since it's not the run's job to emit the schema version -pub struct SchemaVersion { - major: i8, - minor: i8, -} - -#[allow(clippy::new_without_default)] -impl SchemaVersion { - pub fn new() -> SchemaVersion { - SchemaVersion { - major: spec::SPEC_VERSION.0, - minor: spec::SPEC_VERSION.1, - } - } - - pub fn to_artifact(&self) -> spec::RootArtifact { - spec::RootArtifact::SchemaVersion(spec::SchemaVersion { - major: self.major, - minor: self.minor, - }) - } -} - -#[cfg(test)] -mod tests { - use anyhow::Result; - - use super::*; - use crate::spec; - - #[test] - fn test_schema_creation_from_builder() -> Result<()> { - let version = SchemaVersion::new(); - assert_eq!(version.major, spec::SPEC_VERSION.0); - assert_eq!(version.minor, spec::SPEC_VERSION.1); - Ok(()) - } -} diff --git a/src/output/step.rs b/src/output/step.rs index 6b9fa36..e754dd6 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -10,9 +10,10 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; -use crate::spec; -use tv::measurement::MeasurementSeries; -use tv::{emitter, error, log, measurement, state}; +use crate::spec::TestStepStart; +use crate::spec::{self, TestStepArtifactImpl}; +use tv::measure::MeasurementSeries; +use tv::{emitter, error, log, measure, state}; use super::WriterError; @@ -53,8 +54,11 @@ impl TestStep { /// # }); /// ``` pub async fn start(self) -> Result { - let start = TestStepStart::new(&self.name); - self.emitter.emit(&start.to_artifact()).await?; + self.emitter + .emit(&TestStepArtifactImpl::TestStepStart(TestStepStart { + name: self.name.clone(), + })) + .await?; Ok(StartedTestStep { step: self, @@ -126,12 +130,13 @@ impl StartedTestStep { /// # }); /// ``` pub async fn end(&self, status: spec::TestStatus) -> Result<(), emitter::WriterError> { - let end = TestStepEnd::new(status); - self.step.emitter.emit(&end.to_artifact()).await?; + let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); + + self.step.emitter.emit(&end).await?; Ok(()) } - /// Eemits Log message. + /// Emits Log message. /// This method accepts a [`models::LogSeverity`] to define the severity /// and a [`std::string::String`] for the message. /// @@ -181,7 +186,7 @@ impl StartedTestStep { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Log(log.to_artifact())) + .emit(&TestStepArtifactImpl::Log(log.to_artifact())) .await?; Ok(()) @@ -215,7 +220,7 @@ impl StartedTestStep { pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Log(log.to_artifact())) + .emit(&TestStepArtifactImpl::Log(log.to_artifact())) .await?; Ok(()) @@ -264,9 +269,7 @@ impl StartedTestStep { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Error( - error.to_artifact(), - )) + .emit(&TestStepArtifactImpl::Error(error.to_artifact())) .await?; Ok(()) @@ -320,9 +323,7 @@ impl StartedTestStep { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Error( - error.to_artifact(), - )) + .emit(&TestStepArtifactImpl::Error(error.to_artifact())) .await?; Ok(()) @@ -360,9 +361,7 @@ impl StartedTestStep { ) -> Result<(), emitter::WriterError> { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Error( - error.to_artifact(), - )) + .emit(&TestStepArtifactImpl::Error(error.to_artifact())) .await?; Ok(()) @@ -392,11 +391,11 @@ impl StartedTestStep { name: &str, value: Value, ) -> Result<(), emitter::WriterError> { - let measurement = measurement::Measurement::new(name, value); + let measurement = measure::Measurement::new(name, value); self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Measurement( + .emit(&TestStepArtifactImpl::Measurement( measurement.to_artifact(), )) .await?; @@ -433,11 +432,11 @@ impl StartedTestStep { /// ``` pub async fn add_measurement_with_details( &self, - measurement: &measurement::Measurement, + measurement: &measure::Measurement, ) -> Result<(), emitter::WriterError> { self.step .emitter - .emit(&spec::TestStepArtifactDescendant::Measurement( + .emit(&spec::TestStepArtifactImpl::Measurement( measurement.to_artifact(), )) .await?; @@ -496,46 +495,12 @@ impl StartedTestStep { /// ``` pub fn measurement_series_with_details( &self, - start: measurement::MeasurementSeriesStart, + start: measure::MeasurementSeriesStart, ) -> MeasurementSeries { MeasurementSeries::new_with_details(start, &self.step.emitter) } } -pub struct TestStepStart { - name: String, -} - -impl TestStepStart { - pub fn new(name: &str) -> TestStepStart { - TestStepStart { - name: name.to_string(), - } - } - - pub fn to_artifact(&self) -> spec::TestStepArtifactDescendant { - spec::TestStepArtifactDescendant::TestStepStart(spec::TestStepStart { - name: self.name.clone(), - }) - } -} - -pub struct TestStepEnd { - status: spec::TestStatus, -} - -impl TestStepEnd { - pub fn new(status: spec::TestStatus) -> TestStepEnd { - TestStepEnd { status } - } - - pub fn to_artifact(&self) -> spec::TestStepArtifactDescendant { - spec::TestStepArtifactDescendant::TestStepEnd(spec::TestStepEnd { - status: self.status.clone(), - }) - } -} - // TODO: move this away from here; extract trait Emitter, dont rely on json // it will be used in measurement series pub struct StepEmitter { @@ -545,17 +510,14 @@ pub struct StepEmitter { } impl StepEmitter { - pub async fn emit(&self, object: &spec::TestStepArtifactDescendant) -> Result<(), WriterError> { - let root = spec::RootArtifact::TestStepArtifact(spec::TestStepArtifact { + pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), WriterError> { + let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { id: self.step_id.clone(), // TODO: can these copies be avoided? - descendant: object.clone(), + artifact: object.clone(), }); self.state.lock().await.emitter.emit(&root).await?; Ok(()) } } - -#[cfg(test)] -mod tests {} diff --git a/src/spec.rs b/src/spec.rs index 0161cb8..545d306 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -38,70 +38,6 @@ mod rfc3339_format { } } -#[derive(Debug, Serialize, PartialEq, Clone)] -pub enum TestRunArtifactDescendant { - #[serde(rename = "testRunStart")] - TestRunStart(TestRunStart), - - #[serde(rename = "testRunEnd")] - TestRunEnd(TestRunEnd), - - #[serde(rename = "log")] - Log(Log), - - #[serde(rename = "error")] - Error(Error), -} - -#[derive(Debug, Serialize, PartialEq, Clone)] -pub enum RootArtifact { - #[serde(rename = "schemaVersion")] - SchemaVersion(SchemaVersion), - - #[serde(rename = "testRunArtifact")] - TestRunArtifact(TestRunArtifact), - - #[serde(rename = "testStepArtifact")] - TestStepArtifact(TestStepArtifact), -} - -#[allow(clippy::large_enum_variant)] -#[derive(Debug, Serialize, PartialEq, Clone)] -pub enum TestStepArtifactDescendant { - #[serde(rename = "testStepStart")] - TestStepStart(TestStepStart), - - #[serde(rename = "testStepEnd")] - TestStepEnd(TestStepEnd), - - #[serde(rename = "measurement")] - Measurement(Measurement), - - #[serde(rename = "measurementSeriesStart")] - MeasurementSeriesStart(MeasurementSeriesStart), - - #[serde(rename = "measurementSeriesEnd")] - MeasurementSeriesEnd(MeasurementSeriesEnd), - - #[serde(rename = "measurementSeriesElement")] - MeasurementSeriesElement(MeasurementSeriesElement), - - #[serde(rename = "diagnosis")] - Diagnosis(Diagnosis), - - #[serde(rename = "log")] - Log(Log), - - #[serde(rename = "error")] - Error(Error), - - #[serde(rename = "file")] - File(File), - - #[serde(rename = "extension")] - Extension(Extension), -} - #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum ValidatorType { @@ -241,7 +177,7 @@ pub enum SoftwareType { #[derive(Debug, Serialize, Clone)] pub struct Root { #[serde(flatten)] - pub artifact: RootArtifact, + pub artifact: RootImpl, // TODO : manage different timezones #[serde(rename = "timestamp")] @@ -252,6 +188,18 @@ pub struct Root { pub seqno: u64, } +#[derive(Debug, Serialize, PartialEq, Clone)] +pub enum RootImpl { + #[serde(rename = "schemaVersion")] + SchemaVersion(SchemaVersion), + + #[serde(rename = "testRunArtifact")] + TestRunArtifact(TestRunArtifact), + + #[serde(rename = "testStepArtifact")] + TestStepArtifact(TestStepArtifact), +} + /// Low-level model for the `schemaVersion` spec object. /// Specifies the version that should be used to interpret following json outputs. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion @@ -267,6 +215,15 @@ pub struct SchemaVersion { pub minor: i8, } +impl Default for SchemaVersion { + fn default() -> Self { + SchemaVersion { + major: SPEC_VERSION.0, + minor: SPEC_VERSION.1, + } + } +} + /// Low-level model for the `testRunArtifact` spec object. /// Container for the run level artifacts. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-run-artifacts @@ -275,7 +232,22 @@ pub struct SchemaVersion { #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifact { #[serde(flatten)] - pub artifact: TestRunArtifactDescendant, + pub artifact: TestRunArtifactImpl, +} + +#[derive(Debug, Serialize, PartialEq, Clone)] +pub enum TestRunArtifactImpl { + #[serde(rename = "testRunStart")] + TestRunStart(TestRunStart), + + #[serde(rename = "testRunEnd")] + TestRunEnd(TestRunEnd), + + #[serde(rename = "log")] + Log(Log), + + #[serde(rename = "error")] + Error(Error), } /// Low-level model for the `testRunStart` spec object. @@ -498,7 +470,44 @@ pub struct TestStepArtifact { pub id: String, #[serde(flatten)] - pub descendant: TestStepArtifactDescendant, + pub artifact: TestStepArtifactImpl, +} + +#[allow(clippy::large_enum_variant)] +#[derive(Debug, Serialize, PartialEq, Clone)] +pub enum TestStepArtifactImpl { + #[serde(rename = "testStepStart")] + TestStepStart(TestStepStart), + + #[serde(rename = "testStepEnd")] + TestStepEnd(TestStepEnd), + + #[serde(rename = "measurement")] + Measurement(Measurement), + + #[serde(rename = "measurementSeriesStart")] + MeasurementSeriesStart(MeasurementSeriesStart), + + #[serde(rename = "measurementSeriesEnd")] + MeasurementSeriesEnd(MeasurementSeriesEnd), + + #[serde(rename = "measurementSeriesElement")] + MeasurementSeriesElement(MeasurementSeriesElement), + + #[serde(rename = "diagnosis")] + Diagnosis(Diagnosis), + + #[serde(rename = "log")] + Log(Log), + + #[serde(rename = "error")] + Error(Error), + + #[serde(rename = "file")] + File(File), + + #[serde(rename = "extension")] + Extension(Extension), } /// Low-level model for the `testStepStart` spec object. From f8a664ea6f8889f22f614043209bd6721cb9fcc6 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 23:09:43 +0100 Subject: [PATCH 25/96] remove TestState - this didnt seem to be more than the emitter, so just keep that for the time being; - this is an impl detail, so we can revisit this later if needed Signed-off-by: mimir-d --- src/output/measure.rs | 12 +++++------ src/output/mod.rs | 1 - src/output/run.rs | 49 +++++++++++++++++++------------------------ src/output/state.rs | 18 ---------------- src/output/step.rs | 25 ++++++++++------------ 5 files changed, 38 insertions(+), 67 deletions(-) delete mode 100644 src/output/state.rs diff --git a/src/output/measure.rs b/src/output/measure.rs index 152af80..25a7220 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -20,17 +20,15 @@ use tv::{dut, emitter, step}; /// A Measurement Series is a time-series list of measurements. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -pub struct MeasurementSeries<'a> { - // note: intentional design to only allow 1 thread to output; may need - // revisiting in the future, if there's a case for multithreaded writers - emitter: &'a step::StepEmitter, +pub struct MeasurementSeries { + emitter: Arc, seq_no: Arc>, start: MeasurementSeriesStart, } -impl<'a> MeasurementSeries<'a> { - pub(crate) fn new(series_id: &str, name: &str, emitter: &'a step::StepEmitter) -> Self { +impl MeasurementSeries { + pub(crate) fn new(series_id: &str, name: &str, emitter: Arc) -> Self { Self { emitter, seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), @@ -40,7 +38,7 @@ impl<'a> MeasurementSeries<'a> { pub(crate) fn new_with_details( start: MeasurementSeriesStart, - emitter: &'a step::StepEmitter, + emitter: Arc, ) -> Self { Self { emitter, diff --git a/src/output/mod.rs b/src/output/mod.rs index e7b1e5c..a666060 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -12,7 +12,6 @@ mod log; mod macros; mod measure; mod run; -mod state; mod step; pub use crate::spec::LogSeverity; diff --git a/src/output/run.rs b/src/output/run.rs index 77e7b77..f72bb22 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -11,12 +11,13 @@ use std::sync::Arc; use serde_json::Map; use serde_json::Value; -use tokio::sync::Mutex; use crate::output as tv; use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log, state}; +use tv::{config, dut, emitter, error, log}; + +use super::JsonEmitter; /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. @@ -37,7 +38,8 @@ pub struct TestRun { dut: dut::DutInfo, command_line: String, metadata: Option>, - state: Arc>, + + emitter: Arc, } impl TestRun { @@ -88,10 +90,7 @@ impl TestRun { /// ``` pub async fn start(self) -> Result { // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version - self.state - .lock() - .await - .emitter + self.emitter .emit(&spec::RootImpl::SchemaVersion( spec::SchemaVersion::default(), )) @@ -108,7 +107,7 @@ impl TestRun { }), }); - self.state.lock().await.emitter.emit(&start).await?; + self.emitter.emit(&start).await?; Ok(StartedTestRun::new(self)) } @@ -260,7 +259,7 @@ impl TestRunBuilder { pub fn build(self) -> TestRun { let config = self.config.unwrap_or(config::Config::builder().build()); let emitter = emitter::JsonEmitter::new(config.timezone, config.writer); - let state = state::TestState::new(emitter); + TestRun { name: self.name, dut: self.dut, @@ -268,7 +267,8 @@ impl TestRunBuilder { parameters: self.parameters, command_line: self.command_line, metadata: self.metadata, - state: Arc::new(Mutex::new(state)), + + emitter: Arc::new(emitter), } } } @@ -314,9 +314,7 @@ impl StartedTestRun { artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), }); - let emitter = &self.run.state.lock().await.emitter; - - emitter.emit(&end).await?; + self.run.emitter.emit(&end).await?; Ok(()) } @@ -349,12 +347,11 @@ impl StartedTestRun { ) -> Result<(), emitter::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); - let emitter = &self.run.state.lock().await.emitter; - let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; - emitter + self.run + .emitter .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; @@ -385,12 +382,11 @@ impl StartedTestRun { /// # }); /// ``` pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; - emitter + self.run + .emitter .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; @@ -417,12 +413,12 @@ impl StartedTestRun { /// ``` pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).build(); - let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; - emitter + self.run + .emitter .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; @@ -454,12 +450,12 @@ impl StartedTestRun { msg: &str, ) -> Result<(), emitter::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); - let emitter = &self.run.state.lock().await.emitter; let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; - emitter + self.run + .emitter .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; @@ -494,12 +490,11 @@ impl StartedTestRun { &self, error: &error::Error, ) -> Result<(), emitter::WriterError> { - let emitter = &self.run.state.lock().await.emitter; - let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; - emitter + self.run + .emitter .emit(&spec::RootImpl::TestRunArtifact(artifact)) .await?; @@ -508,6 +503,6 @@ impl StartedTestRun { pub fn step(&self, name: &str) -> TestStep { let step_id = format!("step_{}", self.step_seqno.fetch_add(1, Ordering::AcqRel)); - TestStep::new(&step_id, name, self.run.state.clone()) + TestStep::new(&step_id, name, Arc::clone(&self.run.emitter)) } } diff --git a/src/output/state.rs b/src/output/state.rs deleted file mode 100644 index df1fe96..0000000 --- a/src/output/state.rs +++ /dev/null @@ -1,18 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use crate::output::emitter; - -// TODO: will prob need some redesign -pub struct TestState { - pub emitter: emitter::JsonEmitter, -} - -impl TestState { - pub fn new(emitter: emitter::JsonEmitter) -> TestState { - TestState { emitter } - } -} diff --git a/src/output/step.rs b/src/output/step.rs index e754dd6..39662f4 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -7,14 +7,14 @@ use serde_json::Value; use std::sync::atomic; use std::sync::Arc; -use tokio::sync::Mutex; use crate::output as tv; use crate::spec::TestStepStart; use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; -use tv::{emitter, error, log, measure, state}; +use tv::{emitter, error, log, measure}; +use super::JsonEmitter; use super::WriterError; /// A single test step in the scope of a [`TestRun`]. @@ -23,17 +23,17 @@ use super::WriterError; pub struct TestStep { name: String, - emitter: StepEmitter, + emitter: Arc, } impl TestStep { - pub(crate) fn new(id: &str, name: &str, state: Arc>) -> TestStep { + pub(crate) fn new(id: &str, name: &str, run_emitter: Arc) -> Self { TestStep { name: name.to_owned(), - emitter: StepEmitter { - state, + emitter: Arc::new(StepEmitter { step_id: id.to_owned(), - }, + run_emitter, + }), } } @@ -471,7 +471,7 @@ impl StartedTestStep { self.measurement_id_no.load(atomic::Ordering::SeqCst) ); - MeasurementSeries::new(&series_id, name, &self.step.emitter) + MeasurementSeries::new(&series_id, name, Arc::clone(&self.step.emitter)) } /// Starts a Measurement Series (a time-series list of measurements). @@ -497,16 +497,13 @@ impl StartedTestStep { &self, start: measure::MeasurementSeriesStart, ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, &self.step.emitter) + MeasurementSeries::new_with_details(start, Arc::clone(&self.step.emitter)) } } -// TODO: move this away from here; extract trait Emitter, dont rely on json -// it will be used in measurement series pub struct StepEmitter { - // emitter: JsonEmitter, - state: Arc>, step_id: String, + run_emitter: Arc, } impl StepEmitter { @@ -516,7 +513,7 @@ impl StepEmitter { // TODO: can these copies be avoided? artifact: object.clone(), }); - self.state.lock().await.emitter.emit(&root).await?; + self.run_emitter.emit(&root).await?; Ok(()) } From ab483c58bd453acf68056dd7c61a9f1b1b388a80 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 4 Oct 2024 20:09:27 +0100 Subject: [PATCH 26/96] fix atomics usage; ordering - unnecessarily strict ordering for simple atomic counters; SeqCst - fix seqno in artifacts, spec says it starts at 0 - simplify some of the counting methods Signed-off-by: mimir-d --- src/output/emitter.rs | 15 +- src/output/measure.rs | 28 ++-- src/output/step.rs | 10 +- tests/output/macros.rs | 28 ++-- tests/output/runner.rs | 331 ++++++++++++++++++++--------------------- 5 files changed, 199 insertions(+), 213 deletions(-) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 01752dc..ab738c8 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -9,7 +9,7 @@ use std::clone::Clone; use std::io; use std::io::Write; use std::path::Path; -use std::sync::atomic; +use std::sync::atomic::{self, Ordering}; use std::sync::Arc; use tokio::fs::File; @@ -104,14 +104,13 @@ impl JsonEmitter { let root = spec::Root { artifact: object.clone(), timestamp: now_tz, - seqno: self.next_sequence_no(), + seqno: self.incr_seqno(), }; serde_json::json!(root) } - fn next_sequence_no(&self) -> u64 { - self.seqno.fetch_add(1, atomic::Ordering::SeqCst); - self.seqno.load(atomic::Ordering::SeqCst) + fn incr_seqno(&self) -> u64 { + self.seqno.fetch_add(1, Ordering::AcqRel) } pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), WriterError> { @@ -140,7 +139,7 @@ mod tests { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 1 + "sequenceNumber": 0 }); let buffer = Arc::new(Mutex::new(vec![])); @@ -168,14 +167,14 @@ mod tests { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 1 + "sequenceNumber": 0 }); let expected_2 = json!({ "schemaVersion": { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 2 + "sequenceNumber": 1 }); let buffer = Arc::new(Mutex::new(vec![])); diff --git a/src/output/measure.rs b/src/output/measure.rs index 25a7220..2cd6bc9 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -5,12 +5,11 @@ // https://opensource.org/licenses/MIT. use std::future::Future; -use std::sync::atomic; +use std::sync::atomic::{self, Ordering}; use std::sync::Arc; use serde_json::Map; use serde_json::Value; -use tokio::sync::Mutex; use crate::output as tv; use crate::spec; @@ -23,7 +22,7 @@ use tv::{dut, emitter, step}; pub struct MeasurementSeries { emitter: Arc, - seq_no: Arc>, + seq_no: Arc, start: MeasurementSeriesStart, } @@ -31,7 +30,7 @@ impl MeasurementSeries { pub(crate) fn new(series_id: &str, name: &str, emitter: Arc) -> Self { Self { emitter, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + seq_no: Arc::new(atomic::AtomicU64::new(0)), start: MeasurementSeriesStart::new(name, series_id), } } @@ -42,20 +41,13 @@ impl MeasurementSeries { ) -> Self { Self { emitter, - seq_no: Arc::new(Mutex::new(atomic::AtomicU64::new(0))), + seq_no: Arc::new(atomic::AtomicU64::new(0)), start, } } - async fn current_sequence_no(&self) -> u64 { - self.seq_no.lock().await.load(atomic::Ordering::SeqCst) - } - - async fn increment_sequence_no(&self) { - self.seq_no - .lock() - .await - .fetch_add(1, atomic::Ordering::SeqCst); + fn incr_seqno(&self) -> u64 { + self.seq_no.fetch_add(1, Ordering::AcqRel) } /// Starts the measurement series. @@ -109,7 +101,7 @@ impl MeasurementSeries { pub async fn end(&self) -> Result<(), emitter::WriterError> { let end = spec::MeasurementSeriesEnd { series_id: self.start.series_id.clone(), - total_count: self.current_sequence_no().await, + total_count: self.seq_no.load(Ordering::Acquire), }; self.emitter @@ -141,13 +133,12 @@ impl MeasurementSeries { /// ``` pub async fn add_measurement(&self, value: Value) -> Result<(), emitter::WriterError> { let element = spec::MeasurementSeriesElement { - index: self.current_sequence_no().await, + index: self.incr_seqno(), value: value.clone(), timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), series_id: self.start.series_id.clone(), metadata: None, }; - self.increment_sequence_no().await; self.emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( @@ -185,7 +176,7 @@ impl MeasurementSeries { metadata: Vec<(&str, Value)>, ) -> Result<(), emitter::WriterError> { let element = spec::MeasurementSeriesElement { - index: self.current_sequence_no().await, + index: self.incr_seqno(), value: value.clone(), timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), series_id: self.start.series_id.clone(), @@ -193,7 +184,6 @@ impl MeasurementSeries { metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), )), }; - self.increment_sequence_no().await; self.emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( diff --git a/src/output/step.rs b/src/output/step.rs index 39662f4..bf04dc6 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -5,7 +5,7 @@ // https://opensource.org/licenses/MIT. use serde_json::Value; -use std::sync::atomic; +use std::sync::atomic::{self, Ordering}; use std::sync::Arc; use crate::output as tv; @@ -62,7 +62,7 @@ impl TestStep { Ok(StartedTestStep { step: self, - measurement_id_no: Arc::new(atomic::AtomicU64::new(0)), + measurement_id_seqno: Arc::new(atomic::AtomicU64::new(0)), }) } @@ -107,7 +107,7 @@ impl TestStep { pub struct StartedTestStep { step: TestStep, - measurement_id_no: Arc, + measurement_id_seqno: Arc, } impl StartedTestStep { @@ -464,11 +464,9 @@ impl StartedTestStep { /// # }); /// ``` pub fn measurement_series(&self, name: &str) -> MeasurementSeries { - self.measurement_id_no - .fetch_add(1, atomic::Ordering::SeqCst); let series_id: String = format!( "series_{}", - self.measurement_id_no.load(atomic::Ordering::SeqCst) + self.measurement_id_seqno.fetch_add(1, Ordering::AcqRel) ); MeasurementSeries::new(&series_id, name, Arc::clone(&self.step.emitter)) diff --git a/tests/output/macros.rs b/tests/output/macros.rs index 4eeaf22..6da7b9d 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -109,7 +109,7 @@ async fn test_ocptv_error_macro_with_symptom_and_message() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "error", |run| async move { @@ -127,7 +127,7 @@ async fn test_ocptv_error_macro_with_symptom() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "error", |run| async move { @@ -146,7 +146,7 @@ async fn test_ocptv_log_debug() -> Result<()> { "severity": "DEBUG" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "log", |run| async move { @@ -166,7 +166,7 @@ async fn test_ocptv_log_info() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "log", |run| async move { @@ -185,7 +185,7 @@ async fn test_ocptv_log_warning() -> Result<()> { "severity": "WARNING" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "log", |run| async move { @@ -204,7 +204,7 @@ async fn test_ocptv_log_error() -> Result<()> { "severity": "ERROR" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "log", |run| async move { @@ -223,7 +223,7 @@ async fn test_ocptv_log_fatal() -> Result<()> { "severity": "FATAL" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }); check_output_run(&expected, "log", |run| async move { @@ -242,7 +242,7 @@ async fn test_ocptv_error_macro_with_symptom_and_message_in_step() -> Result<()> "symptom":"symptom" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "error", |step| async move { @@ -260,7 +260,7 @@ async fn test_ocptv_error_macro_with_symptom_in_step() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "error", |step| async move { @@ -279,7 +279,7 @@ async fn test_ocptv_log_debug_in_step() -> Result<()> { "severity": "DEBUG" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "log", |step| async move { @@ -298,7 +298,7 @@ async fn test_ocptv_log_info_in_step() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "log", |step| async move { @@ -317,7 +317,7 @@ async fn test_ocptv_log_warning_in_step() -> Result<()> { "severity":"WARNING" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "log", |step| async move { @@ -336,7 +336,7 @@ async fn test_ocptv_log_error_in_step() -> Result<()> { "severity": "ERROR" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "log", |step| async move { @@ -355,7 +355,7 @@ async fn test_ocptv_log_fatal_in_step() -> Result<()> { "severity": "FATAL" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }); check_output_step(&expected, "log", |step| async move { diff --git a/tests/output/runner.rs b/tests/output/runner.rs index b1f5413..e8548cb 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -26,18 +26,18 @@ use tv::{ }; fn json_schema_version() -> serde_json::Value { - // seqno for schemaVersion is always 1 + // seqno for schemaVersion is always 0 json!({ "schemaVersion": { "major": tv::SPEC_VERSION.0, "minor": tv::SPEC_VERSION.1 }, - "sequenceNumber": 1 + "sequenceNumber": 0 }) } fn json_run_default_start() -> serde_json::Value { - // seqno for the default test run start is always 2 + // seqno for the default test run start is always 1 json!({ "testRunArtifact": { "testRunStart": { @@ -49,7 +49,7 @@ fn json_run_default_start() -> serde_json::Value { "version": "1.0" } }, - "sequenceNumber": 2 + "sequenceNumber": 1 }) } @@ -66,14 +66,14 @@ fn json_run_pass(seqno: i32) -> serde_json::Value { } fn json_step_default_start() -> serde_json::Value { - // seqno for the default test run start is always 3 + // seqno for the default test run start is always 2 json!({ "testStepArtifact": { "testStepStart": { "name": "first step" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }) } @@ -151,7 +151,7 @@ async fn test_testrun_start_and_end() -> Result<()> { let expected = [ json_schema_version(), json_run_default_start(), - json_run_pass(3), + json_run_pass(2), ]; check_output_run(&expected, |_| async { Ok(()) }.boxed()).await @@ -169,9 +169,9 @@ async fn test_testrun_with_log() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; check_output_run(&expected, |run| { @@ -203,9 +203,9 @@ async fn test_testrun_with_log_with_details() -> Result<()> { } } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; check_output_run(&expected, |run| { @@ -234,9 +234,9 @@ async fn test_testrun_with_error() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; check_output_run(&expected, |run| { @@ -257,9 +257,9 @@ async fn test_testrun_with_error_with_message() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; check_output_run(&expected, |run| { @@ -288,9 +288,9 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; check_output_run(&expected, |run| { @@ -321,9 +321,9 @@ async fn test_testrun_with_error_with_details() -> Result<()> { // "severity": "INFO" // } // }, -// "sequenceNumber": 3 +// "sequenceNumber": 2 // }), -// json_run_pass(4), +// json_run_pass(3), // ]; // check_output(&expected, |run_builder| async { @@ -349,8 +349,8 @@ async fn test_testrun_with_step() -> Result<()> { json_schema_version(), json_run_default_start(), json_step_default_start(), - json_step_complete(4), - json_run_pass(5), + json_step_complete(3), + json_run_pass(4), ]; check_output_step(&expected, |_| async { Ok(()) }.boxed()).await @@ -369,10 +369,10 @@ async fn test_testrun_step_log() -> Result<()> { "severity": "INFO" } }, - "sequenceNumber": 4 + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -397,7 +397,6 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "log": { "message": "This is a log message with INFO severity", @@ -407,10 +406,11 @@ async fn test_testrun_step_log_with_details() -> Result<()> { "line": 1 } } - } + }, + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -437,15 +437,15 @@ async fn test_testrun_step_error() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "error": { "symptom": "symptom" } - } + }, + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -466,16 +466,16 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "error": { "message": "Error message", "symptom": "symptom" } - } + }, + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -496,7 +496,6 @@ async fn test_testrun_step_error_with_details() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "testStepId": "step_0", "error": { @@ -511,10 +510,11 @@ async fn test_testrun_step_error_with_details() -> Result<()> { }, "symptom": "symptom" } - } + }, + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -542,7 +542,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { // json_run_default_start(), // json_step_default_start(), // json!({ -// "sequenceNumber": 4, +// "sequenceNumber": 3, // "testStepArtifact": { // "log": { // "message": "This is a log message with INFO severity", @@ -550,8 +550,8 @@ async fn test_testrun_step_error_with_details() -> Result<()> { // } // } // }), -// json_step_complete(5), -// json_run_pass(6), +// json_step_complete(4), +// json_run_pass(5), // ]; // check_output_run(&expected, |run| { @@ -587,10 +587,10 @@ async fn test_step_with_measurement() -> Result<()> { "value": 50 } }, - "sequenceNumber": 4 + "sequenceNumber": 3, }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -613,7 +613,6 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurement": { "hardwareInfoId": "id", @@ -630,10 +629,11 @@ async fn test_step_with_measurement_builder() -> Result<()> { }], "value": 50 } - } + }, + "sequenceNumber": 3 }), - json_step_complete(5), - json_run_pass(6), + json_step_complete(4), + json_run_pass(5), ]; check_output_step(&expected, |step| { @@ -660,26 +660,25 @@ async fn test_step_with_measurement_series() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": - "series_1", + "measurementSeriesId": "series_0", "totalCount": 0 } - } + }, + "sequenceNumber": 4, }), - json_step_complete(6), - json_run_pass(7), + json_step_complete(5), + json_run_pass(6), ]; check_output_step(&expected, |step| { @@ -702,43 +701,43 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 0 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_2", + "measurementSeriesId": "series_1", "name": "name" } - } + }, + "sequenceNumber": 5 }), json!({ - "sequenceNumber": 7, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_2", + "measurementSeriesId": "series_1", "totalCount": 0 } - } + }, + "sequenceNumber": 6 }), - json_step_complete(8), - json_run_pass(9), + json_step_complete(7), + json_run_pass(8), ]; check_output_step(&expected, |step| { @@ -765,23 +764,23 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { "measurementSeriesId": "series_id", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } - } + }, + "sequenceNumber": 4 }), - json_step_complete(6), - json_run_pass(7), + json_step_complete(5), + json_run_pass(6), ]; check_output_step(&expected, |step| { @@ -805,7 +804,6 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { "hardwareInfoId": { @@ -825,19 +823,20 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "value": 30 }] } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } - } + }, + "sequenceNumber": 4 }), - json_step_complete(6), - json_run_pass(7), + json_step_complete(5), + json_run_pass(6), ]; check_output_step(&expected, |step| { @@ -867,35 +866,35 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 60 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 1 } - } + }, + "sequenceNumber": 5 }), - json_step_complete(7), - json_run_pass(8), + json_step_complete(6), + json_run_pass(7), ]; check_output_step(&expected, |step| { @@ -919,55 +918,55 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 60 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 70 } - } + }, + "sequenceNumber": 5 }), json!({ - "sequenceNumber": 7, "testStepArtifact": { "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 80 } - } + }, + "sequenceNumber": 6 }), json!({ - "sequenceNumber": 8, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 3 } - } + }, + "sequenceNumber": 7 }), - json_step_complete(9), - json_run_pass(10), + json_step_complete(8), + json_run_pass(9), ]; check_output_step(&expected, |step| { @@ -994,38 +993,38 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3, }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "metadata": { "key": "value" }, "value": 60 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 1 } - } + }, + "sequenceNumber": 5 }), - json_step_complete(7), - json_run_pass(8), + json_step_complete(6), + json_run_pass(7), ]; check_output_step(&expected, |step| { @@ -1051,58 +1050,58 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "metadata": {"key": "value"}, "value": 60 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "metadata": {"key2": "value2"}, "value": 70 } - } + }, + "sequenceNumber": 5 }), json!({ - "sequenceNumber": 7, "testStepArtifact": { "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "metadata": {"key3": "value3"}, "value": 80 } - } + }, + "sequenceNumber": 6 }), json!({ - "sequenceNumber": 8, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 3 } - } + }, + "sequenceNumber": 7 }), - json_step_complete(9), - json_run_pass(10), + json_step_complete(8), + json_run_pass(9), ]; check_output_step(&expected, |step| { @@ -1135,55 +1134,55 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { json_run_default_start(), json_step_default_start(), json!({ - "sequenceNumber": 4, "testStepArtifact": { "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "name": "name" } - } + }, + "sequenceNumber": 3 }), json!({ - "sequenceNumber": 5, "testStepArtifact": { "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 60 } - } + }, + "sequenceNumber": 4 }), json!({ - "sequenceNumber": 6, "testStepArtifact": { "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 70 } - } + }, + "sequenceNumber": 5 }), json!({ - "sequenceNumber": 7, "testStepArtifact": { "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "value": 80 } - } + }, + "sequenceNumber": 6 }), json!({ - "sequenceNumber": 8, "testStepArtifact": { "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "series_0", "totalCount": 3 } - } + }, + "sequenceNumber": 7 }), - json_step_complete(9), - json_run_pass(10), + json_step_complete(8), + json_run_pass(9), ]; check_output_step(&expected, |step| { @@ -1222,9 +1221,9 @@ async fn test_config_builder_with_file() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 2 }), - json_run_pass(4), + json_run_pass(3), ]; let fs = assert_fs::TempDir::new()?; @@ -1264,7 +1263,7 @@ async fn test_testrun_instantiation_with_new() -> Result<()> { let expected = [ json_schema_version(), json_run_default_start(), - json_run_pass(3), + json_run_pass(2), ]; let buffer: Arc>> = Arc::new(Mutex::new(vec![])); @@ -1284,7 +1283,6 @@ async fn test_testrun_metadata() -> Result<()> { let expected = [ json_schema_version(), json!({ - "sequenceNumber": 2, "testRunArtifact": { "testRunStart": { "dutInfo": { @@ -1295,9 +1293,10 @@ async fn test_testrun_metadata() -> Result<()> { "parameters": {}, "version": "1.0" } - } + }, + "sequenceNumber": 1 }), - json_run_pass(3), + json_run_pass(2), ]; check_output(&expected, |run_builder| async { @@ -1335,9 +1334,9 @@ async fn test_testrun_builder() -> Result<()> { "version": "1.0" } }, - "sequenceNumber": 2 + "sequenceNumber": 1 }), - json_run_pass(3), + json_run_pass(2), ]; check_output(&expected, |run_builder| async { From cb073776d70da990f1238c35b9ccf86be4573b4b Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 23:31:34 +0100 Subject: [PATCH 27/96] add typestate pattern for measurement series - commenting out the `scope` method as in the other typestate commits; in a future PR, these will be gated by a feature Signed-off-by: mimir-d --- src/output/measure.rs | 163 +++++++++++++++++++--------------- tests/output/runner.rs | 194 ++++++++++++++++++++--------------------- 2 files changed, 189 insertions(+), 168 deletions(-) diff --git a/src/output/measure.rs b/src/output/measure.rs index 2cd6bc9..bed5a94 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use std::future::Future; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; @@ -20,34 +19,26 @@ use tv::{dut, emitter, step}; /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart pub struct MeasurementSeries { - emitter: Arc, - - seq_no: Arc, start: MeasurementSeriesStart, + + emitter: Arc, } impl MeasurementSeries { pub(crate) fn new(series_id: &str, name: &str, emitter: Arc) -> Self { Self { - emitter, - seq_no: Arc::new(atomic::AtomicU64::new(0)), start: MeasurementSeriesStart::new(name, series_id), + emitter, } } + // TODO: we should allow the user to start a series with details, but still have the series id on + // an auto-generator, since it's more of a spec detail pub(crate) fn new_with_details( start: MeasurementSeriesStart, emitter: Arc, ) -> Self { - Self { - emitter, - seq_no: Arc::new(atomic::AtomicU64::new(0)), - start, - } - } - - fn incr_seqno(&self) -> u64 { - self.seq_no.fetch_add(1, Ordering::AcqRel) + Self { start, emitter } } /// Starts the measurement series. @@ -69,13 +60,68 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(&self) -> Result<(), emitter::WriterError> { + pub async fn start(self) -> Result { self.emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart( self.start.to_artifact(), )) .await?; - Ok(()) + + Ok(StartedMeasurementSeries { + parent: self, + seqno: Arc::new(atomic::AtomicU64::new(0)), + }) + } + + // /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + // /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + // /// After the scope is constructed, additional objects may be added to it. + // /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees + // /// all the messages are emitted between the start and end messages, the order + // /// is respected and no messages is lost. + // /// + // /// # Examples + // /// + // /// ```rust + // /// # tokio_test::block_on(async { + // /// # use ocptv::output::*; + // /// + // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + // /// let step = run.step("step_name").start().await?; + // /// + // /// let series = step.measurement_series("name"); + // /// series.start().await?; + // /// series.scope(|s| async { + // /// s.add_measurement(60.into()).await?; + // /// s.add_measurement(70.into()).await?; + // /// s.add_measurement(80.into()).await?; + // /// Ok(()) + // /// }).await?; + // /// + // /// # Ok::<(), WriterError>(()) + // /// # }); + // /// ``` + // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), emitter::WriterError> + // where + // R: Future>, + // F: std::ops::FnOnce(&'s MeasurementSeries) -> R, + // { + // self.start().await?; + // func(self).await?; + // self.end().await?; + // Ok(()) + // } +} + +pub struct StartedMeasurementSeries { + parent: MeasurementSeries, + + seqno: Arc, +} + +impl StartedMeasurementSeries { + fn incr_seqno(&self) -> u64 { + self.seqno.fetch_add(1, Ordering::AcqRel) } /// Ends the measurement series. @@ -91,8 +137,7 @@ impl MeasurementSeries { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// let step = run.step("step_name").start().await?; /// - /// let series = step.measurement_series("name"); - /// series.start().await?; + /// let series = step.measurement_series("name").start().await?; /// series.end().await?; /// /// # Ok::<(), WriterError>(()) @@ -100,11 +145,12 @@ impl MeasurementSeries { /// ``` pub async fn end(&self) -> Result<(), emitter::WriterError> { let end = spec::MeasurementSeriesEnd { - series_id: self.start.series_id.clone(), - total_count: self.seq_no.load(Ordering::Acquire), + series_id: self.parent.start.series_id.clone(), + total_count: self.seqno.load(Ordering::Acquire), }; - self.emitter + self.parent + .emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesEnd(end)) .await?; @@ -124,8 +170,7 @@ impl MeasurementSeries { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// let step = run.step("step_name").start().await?; /// - /// let series = step.measurement_series("name"); - /// series.start().await?; + /// let series = step.measurement_series("name").start().await?; /// series.add_measurement(60.into()).await?; /// /// # Ok::<(), WriterError>(()) @@ -136,11 +181,12 @@ impl MeasurementSeries { index: self.incr_seqno(), value: value.clone(), timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), - series_id: self.start.series_id.clone(), + series_id: self.parent.start.series_id.clone(), metadata: None, }; - self.emitter + self.parent + .emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( element, )) @@ -163,8 +209,7 @@ impl MeasurementSeries { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// let step = run.step("step_name").start().await?; /// - /// let series = step.measurement_series("name"); - /// series.start().await?; + /// let series = step.measurement_series("name").start().await?; /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; /// /// # Ok::<(), WriterError>(()) @@ -179,13 +224,14 @@ impl MeasurementSeries { index: self.incr_seqno(), value: value.clone(), timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), - series_id: self.start.series_id.clone(), + series_id: self.parent.start.series_id.clone(), metadata: Some(Map::from_iter( metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), )), }; - self.emitter + self.parent + .emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( element, )) @@ -193,45 +239,6 @@ impl MeasurementSeries { Ok(()) } - - /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. - /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees - /// all the messages are emitted between the start and end messages, the order - /// is respected and no messages is lost. - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// - /// let series = step.measurement_series("name"); - /// series.start().await?; - /// series.scope(|s| async { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; - /// Ok(()) - /// }).await?; - /// - /// # Ok::<(), WriterError>(()) - /// # }); - /// ``` - pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), emitter::WriterError> - where - R: Future>, - F: std::ops::FnOnce(&'s MeasurementSeries) -> R, - { - self.start().await?; - func(self).await?; - self.end().await?; - Ok(()) - } } #[derive(Clone)] @@ -730,6 +737,24 @@ impl MeasurementSeriesStartBuilder { } } +// pub struct MeasurementSeriesEmitter { +// series_id: String, +// step_emitter: Arc, +// } + +// impl StepEmitter { +// pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), WriterError> { +// let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { +// id: self.step_id.clone(), +// // TODO: can these copies be avoided? +// artifact: object.clone(), +// }); +// self.run_emitter.emit(&root).await?; + +// Ok(()) +// } +// } + #[cfg(test)] mod tests { use super::*; diff --git a/tests/output/runner.rs b/tests/output/runner.rs index e8548cb..0c58e6f 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -683,8 +683,7 @@ async fn test_step_with_measurement_series() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; series.end().await?; Ok(()) @@ -742,12 +741,10 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; series.end().await?; - let series_2 = step.measurement_series("name"); - series_2.start().await?; + let series_2 = step.measurement_series("name").start().await?; series_2.end().await?; Ok(()) @@ -786,8 +783,9 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { check_output_step(&expected, |step| { async { let series = step - .measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); - series.start().await?; + .measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")) + .start() + .await?; series.end().await?; Ok(()) @@ -841,15 +839,17 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R check_output_step(&expected, |step| { async { - let series = step.measurement_series_with_details( - MeasurementSeriesStart::builder("name", "series_id") - .add_metadata("key", "value".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(&HardwareInfo::builder("id", "name").build()) - .subcomponent(&Subcomponent::builder("name").build()) - .build(), - ); - series.start().await?; + let series = step + .measurement_series_with_details( + MeasurementSeriesStart::builder("name", "series_id") + .add_metadata("key", "value".into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .subcomponent(&Subcomponent::builder("name").build()) + .build(), + ) + .start() + .await?; series.end().await?; Ok(()) @@ -899,8 +899,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; series.add_measurement(60.into()).await?; series.end().await?; @@ -971,8 +970,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; // add more than one element to check the index increments correctly series.add_measurement(60.into()).await?; series.add_measurement(70.into()).await?; @@ -1029,8 +1027,7 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) .await?; @@ -1106,8 +1103,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R check_output_step(&expected, |step| { async { - let series = step.measurement_series("name"); - series.start().await?; + let series = step.measurement_series("name").start().await?; // add more than one element to check the index increments correctly series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) @@ -1127,83 +1123,83 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R .await } -#[tokio::test] -async fn test_step_with_measurement_series_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "measurementSeriesStart": { - "measurementSeriesId": "series_0", - "name": "name" - } - }, - "sequenceNumber": 3 - }), - json!({ - "testStepArtifact": { - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "series_0", - "value": 60 - } - }, - "sequenceNumber": 4 - }), - json!({ - "testStepArtifact": { - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "series_0", - "value": 70 - } - }, - "sequenceNumber": 5 - }), - json!({ - "testStepArtifact": { - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "series_0", - "value": 80 - } - }, - "sequenceNumber": 6 - }), - json!({ - "testStepArtifact": { - "measurementSeriesEnd": { - "measurementSeriesId": "series_0", - "totalCount": 3 - } - }, - "sequenceNumber": 7 - }), - json_step_complete(8), - json_run_pass(9), - ]; +// #[tokio::test] +// async fn test_step_with_measurement_series_scope() -> Result<()> { +// let expected = [ +// json_schema_version(), +// json_run_default_start(), +// json_step_default_start(), +// json!({ +// "testStepArtifact": { +// "measurementSeriesStart": { +// "measurementSeriesId": "series_0", +// "name": "name" +// } +// }, +// "sequenceNumber": 3 +// }), +// json!({ +// "testStepArtifact": { +// "measurementSeriesElement": { +// "index": 0, +// "measurementSeriesId": "series_0", +// "value": 60 +// } +// }, +// "sequenceNumber": 4 +// }), +// json!({ +// "testStepArtifact": { +// "measurementSeriesElement": { +// "index": 1, +// "measurementSeriesId": "series_0", +// "value": 70 +// } +// }, +// "sequenceNumber": 5 +// }), +// json!({ +// "testStepArtifact": { +// "measurementSeriesElement": { +// "index": 2, +// "measurementSeriesId": "series_0", +// "value": 80 +// } +// }, +// "sequenceNumber": 6 +// }), +// json!({ +// "testStepArtifact": { +// "measurementSeriesEnd": { +// "measurementSeriesId": "series_0", +// "totalCount": 3 +// } +// }, +// "sequenceNumber": 7 +// }), +// json_step_complete(8), +// json_run_pass(9), +// ]; - check_output_step(&expected, |step| { - async { - let series = step.measurement_series("name"); - series - .scope(|s| async { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; +// check_output_step(&expected, |step| { +// async { +// let series = step.measurement_series("name"); +// series +// .scope(|s| async { +// s.add_measurement(60.into()).await?; +// s.add_measurement(70.into()).await?; +// s.add_measurement(80.into()).await?; - Ok(()) - }) - .await?; +// Ok(()) +// }) +// .await?; - Ok(()) - } - .boxed() - }) - .await -} +// Ok(()) +// } +// .boxed() +// }) +// .await +// } // reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot // disable the coverage itself, only run this test when in coverage mode because assert_fs From 8fa22b49952fb895efb21bec96dbcb509293f0b5 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 5 Oct 2024 22:42:45 +0100 Subject: [PATCH 28/96] remove a bunch of boilerplate Signed-off-by: mimir-d --- src/output/measure.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/output/measure.rs b/src/output/measure.rs index bed5a94..d4d47be 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -737,24 +737,6 @@ impl MeasurementSeriesStartBuilder { } } -// pub struct MeasurementSeriesEmitter { -// series_id: String, -// step_emitter: Arc, -// } - -// impl StepEmitter { -// pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), WriterError> { -// let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { -// id: self.step_id.clone(), -// // TODO: can these copies be avoided? -// artifact: object.clone(), -// }); -// self.run_emitter.emit(&root).await?; - -// Ok(()) -// } -// } - #[cfg(test)] mod tests { use super::*; From f98061b6d85511201456c04250168b5bf64cba24 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 01:47:53 +0100 Subject: [PATCH 29/96] add dependency injection for timestamps - fix all sorts of breakages in tests due to incorrect json assert - introduced some hacks, should be cleaned on this branch Signed-off-by: mimir-d --- src/output/config.rs | 45 ++++- src/output/emitter.rs | 25 ++- src/output/measure.rs | 4 +- src/output/run.rs | 2 +- src/output/step.rs | 6 +- tests/output/runner.rs | 420 ++++++++++++++++++++++++++++++++--------- 6 files changed, 397 insertions(+), 105 deletions(-) diff --git a/src/output/config.rs b/src/output/config.rs index d5c27b8..bbefc12 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -12,7 +12,7 @@ use crate::output::emitter; /// The configuration repository for the TestRun. pub struct Config { - pub(crate) timezone: chrono_tz::Tz, + pub(crate) timestamp_provider: Box, pub(crate) writer: emitter::WriterType, } @@ -32,20 +32,28 @@ impl Config { /// The builder for the [`Config`] object. pub struct ConfigBuilder { - timezone: Option, + timestamp_provider: Box, writer: Option, } impl ConfigBuilder { fn new() -> Self { Self { - timezone: None, + timestamp_provider: Box::new(ConfiguredTzProvider { tz: chrono_tz::UTC }), writer: Some(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { - self.timezone = Some(timezone); + self.timestamp_provider = Box::new(ConfiguredTzProvider { tz: timezone }); + self + } + + pub fn with_timestamp_provider( + mut self, + timestamp_provider: Box, + ) -> Self { + self.timestamp_provider = timestamp_provider; self } @@ -68,10 +76,37 @@ impl ConfigBuilder { pub fn build(self) -> Config { Config { - timezone: self.timezone.unwrap_or(chrono_tz::UTC), + timestamp_provider: self.timestamp_provider, writer: self .writer .unwrap_or(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), } } } + +pub trait TimestampProvider { + fn now(&self) -> chrono::DateTime; + + fn to_string(&self) -> String { + self.now() + .to_rfc3339_opts(chrono::SecondsFormat::Millis, true) + } +} + +struct ConfiguredTzProvider { + tz: chrono_tz::Tz, +} + +impl TimestampProvider for ConfiguredTzProvider { + fn now(&self) -> chrono::DateTime { + chrono::Local::now().with_timezone(&self.tz) + } +} + +pub struct NullTimestampProvider {} + +impl TimestampProvider for NullTimestampProvider { + fn now(&self) -> chrono::DateTime { + chrono::DateTime::from_timestamp_nanos(0).with_timezone(&chrono_tz::UTC) + } +} diff --git a/src/output/emitter.rs b/src/output/emitter.rs index ab738c8..4e899c6 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -16,6 +16,7 @@ use tokio::fs::File; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; +use crate::output::config; use crate::spec; #[derive(Debug, thiserror::Error, derive_more::Display)] @@ -84,26 +85,28 @@ impl StdoutWriter { } pub struct JsonEmitter { - timezone: chrono_tz::Tz, + // HACK: public for tests, but this should come from config directly to where needed + pub(crate) timestamp_provider: Box, writer: WriterType, seqno: Arc, } impl JsonEmitter { - pub(crate) fn new(timezone: chrono_tz::Tz, writer: WriterType) -> Self { + pub(crate) fn new( + timestamp_provider: Box, + writer: WriterType, + ) -> Self { JsonEmitter { - timezone, + timestamp_provider, writer, seqno: Arc::new(atomic::AtomicU64::new(0)), } } fn serialize_artifact(&self, object: &spec::RootImpl) -> serde_json::Value { - let now = chrono::Local::now(); - let now_tz = now.with_timezone(&self.timezone); let root = spec::Root { artifact: object.clone(), - timestamp: now_tz, + timestamp: self.timestamp_provider.now(), seqno: self.incr_seqno(), }; serde_json::json!(root) @@ -144,7 +147,10 @@ mod tests { let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); - let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + let emitter = JsonEmitter::new( + Box::new(config::NullTimestampProvider {}), + WriterType::Buffer(writer), + ); emitter .emit(&spec::RootImpl::SchemaVersion( @@ -179,7 +185,10 @@ mod tests { let buffer = Arc::new(Mutex::new(vec![])); let writer = BufferWriter::new(buffer.clone()); - let emitter = JsonEmitter::new(chrono_tz::UTC, WriterType::Buffer(writer)); + let emitter = JsonEmitter::new( + Box::new(config::NullTimestampProvider {}), + WriterType::Buffer(writer), + ); let version = spec::RootImpl::SchemaVersion(spec::SchemaVersion::default()); emitter.emit(&version).await?; diff --git a/src/output/measure.rs b/src/output/measure.rs index d4d47be..fe8acb5 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -180,7 +180,7 @@ impl StartedMeasurementSeries { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + timestamp: self.parent.emitter.timestamp_provider().now(), series_id: self.parent.start.series_id.clone(), metadata: None, }; @@ -223,7 +223,7 @@ impl StartedMeasurementSeries { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), - timestamp: chrono::Local::now().with_timezone(&chrono_tz::Tz::UTC), + timestamp: self.parent.emitter.timestamp_provider().now(), series_id: self.parent.start.series_id.clone(), metadata: Some(Map::from_iter( metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), diff --git a/src/output/run.rs b/src/output/run.rs index f72bb22..1e9192e 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -258,7 +258,7 @@ impl TestRunBuilder { pub fn build(self) -> TestRun { let config = self.config.unwrap_or(config::Config::builder().build()); - let emitter = emitter::JsonEmitter::new(config.timezone, config.writer); + let emitter = emitter::JsonEmitter::new(config.timestamp_provider, config.writer); TestRun { name: self.name, diff --git a/src/output/step.rs b/src/output/step.rs index bf04dc6..c048d54 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -14,8 +14,8 @@ use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; use tv::{emitter, error, log, measure}; -use super::JsonEmitter; use super::WriterError; +use super::{JsonEmitter, TimestampProvider}; /// A single test step in the scope of a [`TestRun`]. /// @@ -515,4 +515,8 @@ impl StepEmitter { Ok(()) } + + pub fn timestamp_provider(&self) -> &dyn TimestampProvider { + &*self.run_emitter.timestamp_provider + } } diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 0c58e6f..a857e4d 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use anyhow::Result; use assert_fs::prelude::*; -use assert_json_diff::assert_json_include; +use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; @@ -22,9 +22,21 @@ use ocptv::output as tv; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, SoftwareInfo, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, - TestRunBuilder, TestRunOutcome, TestStatus, TestStep, Validator, ValidatorType, + TestRunBuilder, TestRunOutcome, TestStatus, TestStep, TimestampProvider, Validator, + ValidatorType, }; +const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); +const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; +struct FixedTsProvider {} + +impl TimestampProvider for FixedTsProvider { + fn now(&self) -> chrono::DateTime { + // all cases will use time 0 but this is configurable + DATETIME.with_timezone(&chrono_tz::UTC) + } +} + fn json_schema_version() -> serde_json::Value { // seqno for schemaVersion is always 0 json!({ @@ -32,7 +44,8 @@ fn json_schema_version() -> serde_json::Value { "major": tv::SPEC_VERSION.0, "minor": tv::SPEC_VERSION.1 }, - "sequenceNumber": 0 + "sequenceNumber": 0, + "timestamp": DATETIME_FORMATTED }) } @@ -42,14 +55,24 @@ fn json_run_default_start() -> serde_json::Value { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + + "name": null, + "metadata": null, + "softwareInfos": null, + "hardwareInfos": null, + "platformInfos": null }, "name": "run_name", "parameters": {}, - "version": "1.0" + "version": "1.0", + + "commandLine": "", + "metadata": null, } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }) } @@ -61,7 +84,8 @@ fn json_run_pass(seqno: i32) -> serde_json::Value { "status": "COMPLETE" } }, - "sequenceNumber": seqno + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED }) } @@ -69,22 +93,26 @@ fn json_step_default_start() -> serde_json::Value { // seqno for the default test run start is always 2 json!({ "testStepArtifact": { + "testStepId": "step_0", "testStepStart": { "name": "first step" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }) } fn json_step_complete(seqno: i32) -> serde_json::Value { json!({ "testStepArtifact": { + "testStepId": "step_0", "testStepEnd": { "status": "COMPLETE" } }, - "sequenceNumber": seqno + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED }) } @@ -98,15 +126,16 @@ where let run_builder = TestRun::builder("run_name", &dut, "1.0").config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) .build(), ); // run the main test closure test_fn(run_builder).await?; - for (idx, entry) in buffer.lock().await.iter().enumerate() { + for (i, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); + assert_json_eq!(value, expected[i]); } Ok(()) @@ -166,10 +195,13 @@ async fn test_testrun_with_log() -> Result<()> { "testRunArtifact": { "log": { "message": "This is a log message with INFO severity", - "severity": "INFO" + "severity": "INFO", + + "sourceLocation": null, } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -203,7 +235,8 @@ async fn test_testrun_with_log_with_details() -> Result<()> { } } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -231,10 +264,15 @@ async fn test_testrun_with_error() -> Result<()> { json!({ "testRunArtifact": { "error": { - "symptom": "symptom" + "symptom": "symptom", + + "message": null, + "softwareInfoIds": null, + "sourceLocation": null, } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -254,10 +292,14 @@ async fn test_testrun_with_error_with_message() -> Result<()> { "testRunArtifact": { "error": { "message": "Error message", - "symptom": "symptom" + "symptom": "symptom", + + "sourceLocation": null, + "softwareInfoIds": null, } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -277,9 +319,14 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "testRunArtifact": { "error": { "message": "Error message", - "softwareInfoIds":[{ + "softwareInfoIds": [{ "name": "name", "softwareInfoId": "id", + + "softwareType": null, + "version": null, + "computerSystem": null, + "revision": null, }], "sourceLocation": { "file": "file", @@ -288,7 +335,8 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -364,12 +412,16 @@ async fn test_testrun_step_log() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "log": { "message": "This is a log message with INFO severity", - "severity": "INFO" + "severity": "INFO", + + "sourceLocation": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -398,6 +450,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO", @@ -407,7 +460,8 @@ async fn test_testrun_step_log_with_details() -> Result<()> { } } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -438,11 +492,17 @@ async fn test_testrun_step_error() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "error": { - "symptom": "symptom" + "symptom": "symptom", + + "sourceLocation": null, + "softwareInfoIds": null, + "message": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -467,12 +527,17 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "error": { "message": "Error message", - "symptom": "symptom" + "symptom": "symptom", + + "sourceLocation": null, + "softwareInfoIds": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -500,9 +565,14 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "testStepId": "step_0", "error": { "message": "Error message", - "softwareInfoIds":[{ + "softwareInfoIds": [{ "name": "name", - "softwareInfoId": "id" + "softwareInfoId": "id", + + "revision": null, + "computerSystem": null, + "version": null, + "softwareType": null, }], "sourceLocation": { "file": "file", @@ -511,7 +581,8 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -584,10 +655,17 @@ async fn test_step_with_measurement() -> Result<()> { "testStepId": "step_0", "measurement": { "name": "name", - "value": 50 + "value": 50, + + "metadata": null, + "hardwareInfoId": null, + "subcomponent": null, + "unit": null, + "validators": null, } }, "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -604,8 +682,6 @@ async fn test_step_with_measurement() -> Result<()> { .await } -// TODO: intentionally leaving these tests broken so that it's obvious later that the -// assert_json_includes was not sufficient; this case is missing `testStepId` field #[tokio::test] async fn test_step_with_measurement_builder() -> Result<()> { let expected = [ @@ -614,6 +690,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurement": { "hardwareInfoId": "id", "metadata": { @@ -621,16 +698,27 @@ async fn test_step_with_measurement_builder() -> Result<()> { }, "name": "name", "subcomponent": { - "name": "name" + "name": "name", + + "type": null, + "revision": null, + "version": null, + "location": null, }, - "validators":[{ + "validators": [{ "type": "EQUAL", - "value": 30 + "value": 30, + + "name": null, + "metadata": null, }], - "value": 50 + "value": 50, + + "unit": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json_step_complete(4), json_run_pass(5), @@ -661,21 +749,31 @@ async fn test_step_with_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 0 } }, "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -701,39 +799,59 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + "unit": null, + "subComponent": null, + "metadata": null, + "validators": null, + "hardwareInfoId": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_1", - "name": "name" + "name": "name", + + "unit": null, + "subComponent": null, + "metadata": null, + "validators": null, + "hardwareInfoId": null, } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_1", "totalCount": 0 } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json_step_complete(7), json_run_pass(8), @@ -762,19 +880,30 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { - "measurementSeriesId": "series_id", "name": "name" + "measurementSeriesId": "series_id", + "name": "name", + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -803,10 +932,22 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "hardwareInfoId": { "hardwareInfoId": "id", - "name": "name" + "name": "name", + + "serialNumber": null, + "revision": null, + "computerSystem": null, + "location": null, + "odataId": null, + "version": null, + "manufacturerPartNumber": null, + "manufacturer": null, + "manager": null, + "partNumber": null, }, "measurementSeriesId": "series_id", "metadata": { @@ -814,24 +955,37 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R }, "name": "name", "subComponent": { - "name": "name" + "name": "name", + + "type": null, + "version": null, + "location": null, + "revision": null }, "validators":[{ "type": "EQUAL", - "value": 30 - }] + "value": 30, + + "metadata": null, + "name": null, + }], + + "unit": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json_step_complete(5), json_run_pass(6), @@ -867,31 +1021,46 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, + + "metadata": null, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 1 } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json_step_complete(6), json_run_pass(7), @@ -918,51 +1087,76 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, + + "metadata": null, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 1, "measurementSeriesId": "series_0", - "value": 70 + "value": 70, + "timestamp": DATETIME_FORMATTED, + + "metadata": null, } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 2, "measurementSeriesId": "series_0", - "value": 80 + "value": 80, + "timestamp": DATETIME_FORMATTED, + + "metadata": null, } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 3 } }, - "sequenceNumber": 7 + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED }), json_step_complete(8), json_run_pass(9), @@ -992,34 +1186,48 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", "metadata": { "key": "value" }, - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 1 } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json_step_complete(6), json_run_pass(7), @@ -1048,54 +1256,73 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R json_step_default_start(), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name" + "name": "name", + + "unit": null, + "hardwareInfoId": null, + "metadata": null, + "subComponent": null, + "validators": null, } }, - "sequenceNumber": 3 + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 0, "measurementSeriesId": "series_0", "metadata": {"key": "value"}, - "value": 60 + "value": 60, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 4 + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 1, "measurementSeriesId": "series_0", "metadata": {"key2": "value2"}, - "value": 70 + "value": 70, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 5 + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesElement": { "index": 2, "measurementSeriesId": "series_0", "metadata": {"key3": "value3"}, - "value": 80 + "value": 80, + "timestamp": DATETIME_FORMATTED, } }, - "sequenceNumber": 6 + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED }), json!({ "testStepArtifact": { + "testStepId": "step_0", "measurementSeriesEnd": { "measurementSeriesId": "series_0", "totalCount": 3 } }, - "sequenceNumber": 7 + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED }), json_step_complete(8), json_run_pass(9), @@ -1217,7 +1444,8 @@ async fn test_config_builder_with_file() -> Result<()> { "symptom": "symptom" } }, - "sequenceNumber": 2 + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED }), json_run_pass(3), ]; @@ -1282,15 +1510,24 @@ async fn test_testrun_metadata() -> Result<()> { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + + "name": null, + "metadata": null, + "softwareInfos": null, + "hardwareInfos": null, + "platformInfos": null }, "metadata": {"key": "value"}, "name": "run_name", "parameters": {}, - "version": "1.0" + "version": "1.0", + + "commandLine": "", } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }), json_run_pass(2), ]; @@ -1317,7 +1554,13 @@ async fn test_testrun_builder() -> Result<()> { "testRunStart": { "commandLine": "cmd_line", "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + + "name": null, + "metadata": null, + "softwareInfos": null, + "hardwareInfos": null, + "platformInfos": null }, "metadata": { "key": "value", @@ -1330,7 +1573,8 @@ async fn test_testrun_builder() -> Result<()> { "version": "1.0" } }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED }), json_run_pass(2), ]; From f4caa17932aa6ca8c0736857abb913df9e79f1ef Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 01:49:34 +0100 Subject: [PATCH 30/96] typo in spec implementation - subcomponent is always full lowercase Signed-off-by: mimir-d --- src/spec.rs | 2 +- tests/output/runner.rs | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/spec.rs b/src/spec.rs index 545d306..9a33681 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -632,7 +632,7 @@ pub struct MeasurementSeriesStart { #[serde(rename = "hardwareInfoId")] pub hardware_info: Option, - #[serde(rename = "subComponent")] + #[serde(rename = "subcomponent")] pub subcomponent: Option, #[serde(rename = "metadata")] diff --git a/tests/output/runner.rs b/tests/output/runner.rs index a857e4d..291227c 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -757,7 +757,7 @@ async fn test_step_with_measurement_series() -> Result<()> { "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, @@ -805,7 +805,7 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { "name": "name", "unit": null, - "subComponent": null, + "subcomponent": null, "metadata": null, "validators": null, "hardwareInfoId": null, @@ -833,7 +833,7 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { "name": "name", "unit": null, - "subComponent": null, + "subcomponent": null, "metadata": null, "validators": null, "hardwareInfoId": null, @@ -888,7 +888,7 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, @@ -954,7 +954,7 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "key": "value" }, "name": "name", - "subComponent": { + "subcomponent": { "name": "name", "type": null, @@ -1029,7 +1029,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, @@ -1095,7 +1095,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, @@ -1195,7 +1195,7 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, @@ -1264,7 +1264,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R "unit": null, "hardwareInfoId": null, "metadata": null, - "subComponent": null, + "subcomponent": null, "validators": null, } }, From b5f9fe29004addab2815101987e70f9d0c39c5bf Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 02:01:34 +0100 Subject: [PATCH 31/96] remove annoying nulls - now that the tests pass, remove the nulls from the json output - while the spec says this is ok, it just makes the tests unnecessarily verbose Signed-off-by: mimir-d --- src/output/config.rs | 5 - src/output/error.rs | 34 ++----- src/spec.rs | 48 ++++++++++ tests/output/runner.rs | 209 +++++++---------------------------------- 4 files changed, 91 insertions(+), 205 deletions(-) diff --git a/src/output/config.rs b/src/output/config.rs index bbefc12..0c98dfc 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -86,11 +86,6 @@ impl ConfigBuilder { pub trait TimestampProvider { fn now(&self) -> chrono::DateTime; - - fn to_string(&self) -> String { - self.now() - .to_rfc3339_opts(chrono::SecondsFormat::Millis, true) - } } struct ConfiguredTzProvider { diff --git a/src/output/error.rs b/src/output/error.rs index 22b1d17..1fbe845 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -82,8 +82,7 @@ impl ErrorBuilder { #[cfg(test)] mod tests { use anyhow::Result; - - use assert_json_diff::assert_json_include; + use assert_json_diff::assert_json_eq; use super::*; use crate::output as tv; @@ -140,45 +139,30 @@ mod tests { "message": "message", "softwareInfoIds": [ { - "computerSystem": null, "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null + "softwareInfoId": "software_id", }, { - "computerSystem": null, "name": "name", - "revision": null, - "softwareInfoId": - "software_id", - "softwareType": null, - "version": null + "softwareInfoId": "software_id", } ], - "sourceLocation": {"file": "file.rs", "line": 1}, + "sourceLocation": { + "file": "file.rs", + "line": 1 + }, "symptom": "symptom" }); let expected_step = serde_json::json!({ "message": "message", "softwareInfoIds": [ { - "computerSystem": null, "name": "name", - "revision": null, "softwareInfoId": "software_id", - "softwareType": null, - "version": null }, { - "computerSystem": null, "name": "name", - "revision": null, "softwareInfoId": "software_id", - "softwareType": null, - "version": null } ], "sourceLocation": {"file":"file.rs","line":1}, @@ -195,11 +179,11 @@ mod tests { let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_run); + assert_json_eq!(actual, expected_run); let spec_error = error.to_artifact(); let actual = serde_json::json!(spec_error); - assert_json_include!(actual: actual, expected: &expected_step); + assert_json_eq!(actual, expected_step); Ok(()) } diff --git a/src/spec.rs b/src/spec.rs index 9a33681..b6ce19f 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -273,6 +273,7 @@ pub struct TestRunStart { #[serde(rename = "dutInfo")] pub dut_info: DutInfo, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -288,18 +289,23 @@ pub struct DutInfo { #[serde(rename = "dutInfoId")] pub id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "name")] pub name: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "platformInfos")] pub platform_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareInfos")] pub software_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfos")] pub hardware_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -330,15 +336,19 @@ pub struct SoftwareInfo { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareType")] pub software_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "computerSystem")] pub computer_system: Option, } @@ -357,33 +367,43 @@ pub struct HardwareInfo { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "location")] pub location: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "serialNumber")] pub serial_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "partNumber")] pub part_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manufacturer")] pub manufacturer: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manufacturerPartNumber")] pub manufacturer_part_no: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "odataId")] pub odata_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "computerSystem")] pub computer_system: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "manager")] pub manager: Option, } @@ -415,13 +435,16 @@ pub struct Error { #[serde(rename = "symptom")] pub symptom: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "message")] pub message: Option, // TODO: support this field during serialization to print only the id of SoftwareInfo struct + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareInfoIds")] pub software_infos: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -440,6 +463,7 @@ pub struct Log { #[serde(rename = "message")] pub message: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -548,18 +572,23 @@ pub struct Measurement { #[serde(rename = "value")] pub value: Value, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "unit")] pub unit: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub validators: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] pub hardware_info_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -572,6 +601,7 @@ pub struct Measurement { #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] pub struct Validator { + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "name")] pub name: Option, @@ -581,6 +611,7 @@ pub struct Validator { #[serde(rename = "value")] pub value: Value, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -593,18 +624,22 @@ pub struct Validator { #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] pub struct Subcomponent { + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "type")] pub subcomponent_type: Option, #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "location")] pub location: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "version")] pub version: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "revision")] pub revision: Option, } @@ -620,21 +655,26 @@ pub struct MeasurementSeriesStart { #[serde(rename = "name")] pub name: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "unit")] pub unit: Option, #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub validators: Option>, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] pub hardware_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subcomponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -674,6 +714,7 @@ pub struct MeasurementSeriesElement { #[serde(rename = "measurementSeriesId")] pub series_id: String, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } @@ -692,15 +733,19 @@ pub struct Diagnosis { #[serde(rename = "type")] pub diagnosis_type: DiagnosisType, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "message")] pub message: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "validators")] pub hardware_info: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subComponent")] pub subcomponent: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "sourceLocation")] pub source_location: Option, } @@ -722,12 +767,15 @@ pub struct File { #[serde(rename = "isSnapshot")] pub is_snapshot: bool, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "description")] pub description: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "contentType")] pub content_type: Option, + #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] pub metadata: Option>, } diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 291227c..82c7b85 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -55,20 +55,12 @@ fn json_run_default_start() -> serde_json::Value { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id", - - "name": null, - "metadata": null, - "softwareInfos": null, - "hardwareInfos": null, - "platformInfos": null + "dutInfoId": "dut_id" }, "name": "run_name", "parameters": {}, "version": "1.0", - - "commandLine": "", - "metadata": null, + "commandLine": "" } }, "sequenceNumber": 1, @@ -195,9 +187,7 @@ async fn test_testrun_with_log() -> Result<()> { "testRunArtifact": { "log": { "message": "This is a log message with INFO severity", - "severity": "INFO", - - "sourceLocation": null, + "severity": "INFO" } }, "sequenceNumber": 2, @@ -264,11 +254,7 @@ async fn test_testrun_with_error() -> Result<()> { json!({ "testRunArtifact": { "error": { - "symptom": "symptom", - - "message": null, - "softwareInfoIds": null, - "sourceLocation": null, + "symptom": "symptom" } }, "sequenceNumber": 2, @@ -292,10 +278,7 @@ async fn test_testrun_with_error_with_message() -> Result<()> { "testRunArtifact": { "error": { "message": "Error message", - "symptom": "symptom", - - "sourceLocation": null, - "softwareInfoIds": null, + "symptom": "symptom" } }, "sequenceNumber": 2, @@ -321,12 +304,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "message": "Error message", "softwareInfoIds": [{ "name": "name", - "softwareInfoId": "id", - - "softwareType": null, - "version": null, - "computerSystem": null, - "revision": null, + "softwareInfoId": "id" }], "sourceLocation": { "file": "file", @@ -415,9 +393,7 @@ async fn test_testrun_step_log() -> Result<()> { "testStepId": "step_0", "log": { "message": "This is a log message with INFO severity", - "severity": "INFO", - - "sourceLocation": null, + "severity": "INFO" } }, "sequenceNumber": 3, @@ -494,11 +470,7 @@ async fn test_testrun_step_error() -> Result<()> { "testStepArtifact": { "testStepId": "step_0", "error": { - "symptom": "symptom", - - "sourceLocation": null, - "softwareInfoIds": null, - "message": null, + "symptom": "symptom" } }, "sequenceNumber": 3, @@ -530,10 +502,7 @@ async fn test_testrun_step_error_with_message() -> Result<()> { "testStepId": "step_0", "error": { "message": "Error message", - "symptom": "symptom", - - "sourceLocation": null, - "softwareInfoIds": null, + "symptom": "symptom" } }, "sequenceNumber": 3, @@ -567,12 +536,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "message": "Error message", "softwareInfoIds": [{ "name": "name", - "softwareInfoId": "id", - - "revision": null, - "computerSystem": null, - "version": null, - "softwareType": null, + "softwareInfoId": "id" }], "sourceLocation": { "file": "file", @@ -655,13 +619,7 @@ async fn test_step_with_measurement() -> Result<()> { "testStepId": "step_0", "measurement": { "name": "name", - "value": 50, - - "metadata": null, - "hardwareInfoId": null, - "subcomponent": null, - "unit": null, - "validators": null, + "value": 50 } }, "sequenceNumber": 3, @@ -698,23 +656,13 @@ async fn test_step_with_measurement_builder() -> Result<()> { }, "name": "name", "subcomponent": { - "name": "name", - - "type": null, - "revision": null, - "version": null, - "location": null, + "name": "name" }, "validators": [{ "type": "EQUAL", - "value": 30, - - "name": null, - "metadata": null, + "value": 30 }], - "value": 50, - - "unit": null, + "value": 50 } }, "sequenceNumber": 3, @@ -752,13 +700,7 @@ async fn test_step_with_measurement_series() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -802,13 +744,7 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - "unit": null, - "subcomponent": null, - "metadata": null, - "validators": null, - "hardwareInfoId": null, + "name": "name" } }, "sequenceNumber": 3, @@ -830,13 +766,7 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_1", - "name": "name", - - "unit": null, - "subcomponent": null, - "metadata": null, - "validators": null, - "hardwareInfoId": null, + "name": "name" } }, "sequenceNumber": 5, @@ -883,13 +813,7 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_id", - "name": "name", - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -936,18 +860,7 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "measurementSeriesStart": { "hardwareInfoId": { "hardwareInfoId": "id", - "name": "name", - - "serialNumber": null, - "revision": null, - "computerSystem": null, - "location": null, - "odataId": null, - "version": null, - "manufacturerPartNumber": null, - "manufacturer": null, - "manager": null, - "partNumber": null, + "name": "name" }, "measurementSeriesId": "series_id", "metadata": { @@ -955,22 +868,12 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R }, "name": "name", "subcomponent": { - "name": "name", - - "type": null, - "version": null, - "location": null, - "revision": null + "name": "name" }, - "validators":[{ + "validators": [{ "type": "EQUAL", - "value": 30, - - "metadata": null, - "name": null, - }], - - "unit": null, + "value": 30 + }] } }, "sequenceNumber": 3, @@ -1024,13 +927,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -1043,9 +940,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { "index": 0, "measurementSeriesId": "series_0", "value": 60, - "timestamp": DATETIME_FORMATTED, - - "metadata": null, + "timestamp": DATETIME_FORMATTED } }, "sequenceNumber": 4, @@ -1090,13 +985,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -1109,9 +998,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { "index": 0, "measurementSeriesId": "series_0", "value": 60, - "timestamp": DATETIME_FORMATTED, - - "metadata": null, + "timestamp": DATETIME_FORMATTED } }, "sequenceNumber": 4, @@ -1124,9 +1011,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { "index": 1, "measurementSeriesId": "series_0", "value": 70, - "timestamp": DATETIME_FORMATTED, - - "metadata": null, + "timestamp": DATETIME_FORMATTED } }, "sequenceNumber": 5, @@ -1139,9 +1024,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { "index": 2, "measurementSeriesId": "series_0", "value": 80, - "timestamp": DATETIME_FORMATTED, - - "metadata": null, + "timestamp": DATETIME_FORMATTED } }, "sequenceNumber": 6, @@ -1189,14 +1072,7 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -1259,13 +1135,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R "testStepId": "step_0", "measurementSeriesStart": { "measurementSeriesId": "series_0", - "name": "name", - - "unit": null, - "hardwareInfoId": null, - "metadata": null, - "subcomponent": null, - "validators": null, + "name": "name" } }, "sequenceNumber": 3, @@ -1459,6 +1329,7 @@ async fn test_config_builder_with_file() -> Result<()> { .config( Config::builder() .timezone(chrono_tz::Europe::Rome) + .with_timestamp_provider(Box::new(FixedTsProvider {})) .with_file_output(output_file.path()) .await? .build(), @@ -1510,13 +1381,7 @@ async fn test_testrun_metadata() -> Result<()> { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id", - - "name": null, - "metadata": null, - "softwareInfos": null, - "hardwareInfos": null, - "platformInfos": null + "dutInfoId": "dut_id" }, "metadata": {"key": "value"}, "name": "run_name", @@ -1554,13 +1419,7 @@ async fn test_testrun_builder() -> Result<()> { "testRunStart": { "commandLine": "cmd_line", "dutInfo": { - "dutInfoId": "dut_id", - - "name": null, - "metadata": null, - "softwareInfos": null, - "hardwareInfos": null, - "platformInfos": null + "dutInfoId": "dut_id" }, "metadata": { "key": "value", From e49ae702b50552a9e038f569d121eb902fdb9fc1 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 18:24:04 +0100 Subject: [PATCH 32/96] move writer to config - this is public api; consolidate with public config types - the emitter is not public, so it sits in its own file Signed-off-by: mimir-d --- src/output/config.rs | 21 ++++------ src/output/emitter.rs | 97 ++++++------------------------------------- src/output/measure.rs | 14 +++---- src/output/mod.rs | 2 + src/output/run.rs | 16 +++---- src/output/step.rs | 26 ++++++------ src/output/writer.rs | 81 ++++++++++++++++++++++++++++++++++++ 7 files changed, 133 insertions(+), 124 deletions(-) create mode 100644 src/output/writer.rs diff --git a/src/output/config.rs b/src/output/config.rs index 0c98dfc..a1b8544 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -6,14 +6,15 @@ use std::path::Path; use std::sync::Arc; + use tokio::sync::Mutex; -use crate::output::emitter; +use crate::output::writer::{self, BufferWriter, FileWriter, StdoutWriter, WriterType}; /// The configuration repository for the TestRun. pub struct Config { pub(crate) timestamp_provider: Box, - pub(crate) writer: emitter::WriterType, + pub(crate) writer: WriterType, } impl Config { @@ -33,14 +34,14 @@ impl Config { /// The builder for the [`Config`] object. pub struct ConfigBuilder { timestamp_provider: Box, - writer: Option, + writer: Option, } impl ConfigBuilder { fn new() -> Self { Self { timestamp_provider: Box::new(ConfiguredTzProvider { tz: chrono_tz::UTC }), - writer: Some(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), + writer: Some(WriterType::Stdout(StdoutWriter::new())), } } @@ -58,19 +59,15 @@ impl ConfigBuilder { } pub fn with_buffer_output(mut self, buffer: Arc>>) -> Self { - self.writer = Some(emitter::WriterType::Buffer(emitter::BufferWriter::new( - buffer, - ))); + self.writer = Some(WriterType::Buffer(BufferWriter::new(buffer))); self } pub async fn with_file_output>( mut self, path: P, - ) -> Result { - self.writer = Some(emitter::WriterType::File( - emitter::FileWriter::new(path).await?, - )); + ) -> Result { + self.writer = Some(WriterType::File(FileWriter::new(path).await?)); Ok(self) } @@ -79,7 +76,7 @@ impl ConfigBuilder { timestamp_provider: self.timestamp_provider, writer: self .writer - .unwrap_or(emitter::WriterType::Stdout(emitter::StdoutWriter::new())), + .unwrap_or(WriterType::Stdout(StdoutWriter::new())), } } } diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 4e899c6..e78785a 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -4,97 +4,23 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use core::fmt::Debug; -use std::clone::Clone; -use std::io; -use std::io::Write; -use std::path::Path; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; -use tokio::fs::File; -use tokio::io::AsyncWriteExt; -use tokio::sync::Mutex; - -use crate::output::config; +use crate::output::{config, writer}; use crate::spec; -#[derive(Debug, thiserror::Error, derive_more::Display)] -#[non_exhaustive] -pub enum WriterError { - IoError(#[from] io::Error), -} - -pub(crate) enum WriterType { - Stdout(StdoutWriter), - File(FileWriter), - Buffer(BufferWriter), -} - -pub struct FileWriter { - file: Arc>, -} - -impl FileWriter { - pub async fn new>(path: P) -> Result { - let file = File::create(path).await.map_err(WriterError::IoError)?; - Ok(FileWriter { - file: Arc::new(Mutex::new(file)), - }) - } - - async fn write(&self, s: &str) -> Result<(), WriterError> { - let mut handle = self.file.lock().await; - let mut buf = Vec::::new(); - writeln!(buf, "{}", s)?; - handle.write_all(&buf).await.map_err(WriterError::IoError)?; - handle.flush().await.map_err(WriterError::IoError)?; - Ok(()) - } -} - -#[derive(Debug)] -pub struct BufferWriter { - buffer: Arc>>, -} - -impl BufferWriter { - pub fn new(buffer: Arc>>) -> Self { - Self { buffer } - } - - async fn write(&self, s: &str) -> Result<(), WriterError> { - self.buffer.lock().await.push(s.to_string()); - Ok(()) - } -} - -#[derive(Debug, Clone)] -pub struct StdoutWriter {} - -#[allow(clippy::new_without_default)] -impl StdoutWriter { - pub fn new() -> Self { - StdoutWriter {} - } - - async fn write(&self, s: &str) -> Result<(), WriterError> { - println!("{}", s); - Ok(()) - } -} - pub struct JsonEmitter { // HACK: public for tests, but this should come from config directly to where needed pub(crate) timestamp_provider: Box, - writer: WriterType, + writer: writer::WriterType, seqno: Arc, } impl JsonEmitter { pub(crate) fn new( timestamp_provider: Box, - writer: WriterType, + writer: writer::WriterType, ) -> Self { JsonEmitter { timestamp_provider, @@ -116,12 +42,12 @@ impl JsonEmitter { self.seqno.fetch_add(1, Ordering::AcqRel) } - pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), WriterError> { + pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), writer::WriterError> { let serialized = self.serialize_artifact(object); match self.writer { - WriterType::File(ref file) => file.write(&serialized.to_string()).await?, - WriterType::Stdout(ref stdout) => stdout.write(&serialized.to_string()).await?, - WriterType::Buffer(ref buffer) => buffer.write(&serialized.to_string()).await?, + writer::WriterType::File(ref file) => file.write(&serialized.to_string()).await?, + writer::WriterType::Stdout(ref stdout) => stdout.write(&serialized.to_string()).await?, + writer::WriterType::Buffer(ref buffer) => buffer.write(&serialized.to_string()).await?, } Ok(()) } @@ -132,6 +58,7 @@ mod tests { use anyhow::{anyhow, Result}; use assert_json_diff::assert_json_include; use serde_json::json; + use tokio::sync::Mutex; use super::*; @@ -146,10 +73,10 @@ mod tests { }); let buffer = Arc::new(Mutex::new(vec![])); - let writer = BufferWriter::new(buffer.clone()); + let writer = writer::BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new( Box::new(config::NullTimestampProvider {}), - WriterType::Buffer(writer), + writer::WriterType::Buffer(writer), ); emitter @@ -184,10 +111,10 @@ mod tests { }); let buffer = Arc::new(Mutex::new(vec![])); - let writer = BufferWriter::new(buffer.clone()); + let writer = writer::BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new( Box::new(config::NullTimestampProvider {}), - WriterType::Buffer(writer), + writer::WriterType::Buffer(writer), ); let version = spec::RootImpl::SchemaVersion(spec::SchemaVersion::default()); diff --git a/src/output/measure.rs b/src/output/measure.rs index fe8acb5..50a9677 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -12,7 +12,7 @@ use serde_json::Value; use crate::output as tv; use crate::spec; -use tv::{dut, emitter, step}; +use tv::{dut, step, writer}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -60,7 +60,7 @@ impl MeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { self.emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart( self.start.to_artifact(), @@ -101,9 +101,9 @@ impl MeasurementSeries { // /// # Ok::<(), WriterError>(()) // /// # }); // /// ``` - // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), emitter::WriterError> + // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), writer::WriterError> // where - // R: Future>, + // R: Future>, // F: std::ops::FnOnce(&'s MeasurementSeries) -> R, // { // self.start().await?; @@ -143,7 +143,7 @@ impl StartedMeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self) -> Result<(), emitter::WriterError> { + pub async fn end(&self) -> Result<(), writer::WriterError> { let end = spec::MeasurementSeriesEnd { series_id: self.parent.start.series_id.clone(), total_count: self.seqno.load(Ordering::Acquire), @@ -176,7 +176,7 @@ impl StartedMeasurementSeries { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), emitter::WriterError> { + pub async fn add_measurement(&self, value: Value) -> Result<(), writer::WriterError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), @@ -219,7 +219,7 @@ impl StartedMeasurementSeries { &self, value: Value, metadata: Vec<(&str, Value)>, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), diff --git a/src/output/mod.rs b/src/output/mod.rs index a666060..5e28a5e 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -13,6 +13,7 @@ mod macros; mod measure; mod run; mod step; +mod writer; pub use crate::spec::LogSeverity; pub use crate::spec::TestResult; @@ -27,5 +28,6 @@ pub use log::*; pub use measure::*; pub use run::*; pub use step::*; +pub use writer::WriterError; pub use serde_json::Value; diff --git a/src/output/run.rs b/src/output/run.rs index 1e9192e..64b740c 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -15,7 +15,7 @@ use serde_json::Value; use crate::output as tv; use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log}; +use tv::{config, dut, emitter, error, log, writer}; use super::JsonEmitter; @@ -88,7 +88,7 @@ impl TestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version self.emitter .emit(&spec::RootImpl::SchemaVersion( @@ -309,7 +309,7 @@ impl StartedTestRun { &self, status: spec::TestStatus, result: spec::TestResult, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let end = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), }); @@ -344,7 +344,7 @@ impl StartedTestRun { &self, severity: spec::LogSeverity, msg: &str, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); let artifact = spec::TestRunArtifact { @@ -381,7 +381,7 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), writer::WriterError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -411,7 +411,7 @@ impl StartedTestRun { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), writer::WriterError> { let error = error::Error::builder(symptom).build(); let artifact = spec::TestRunArtifact { @@ -448,7 +448,7 @@ impl StartedTestRun { &self, symptom: &str, msg: &str, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); let artifact = spec::TestRunArtifact { @@ -489,7 +489,7 @@ impl StartedTestRun { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; diff --git a/src/output/step.rs b/src/output/step.rs index c048d54..f4f1833 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -12,9 +12,8 @@ use crate::output as tv; use crate::spec::TestStepStart; use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; -use tv::{emitter, error, log, measure}; +use tv::{error, log, measure, writer}; -use super::WriterError; use super::{JsonEmitter, TimestampProvider}; /// A single test step in the scope of a [`TestRun`]. @@ -53,7 +52,7 @@ impl TestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { self.emitter .emit(&TestStepArtifactImpl::TestStepStart(TestStepStart { name: self.name.clone(), @@ -129,7 +128,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn end(&self, status: spec::TestStatus) -> Result<(), emitter::WriterError> { + pub async fn end(&self, status: spec::TestStatus) -> Result<(), writer::WriterError> { let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); self.step.emitter.emit(&end).await?; @@ -181,7 +180,7 @@ impl StartedTestStep { &self, severity: spec::LogSeverity, msg: &str, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let log = log::Log::builder(msg).severity(severity).build(); self.step @@ -217,7 +216,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), emitter::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), writer::WriterError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -264,7 +263,7 @@ impl StartedTestStep { /// # Ok::<(), WriterError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), emitter::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), writer::WriterError> { let error = error::Error::builder(symptom).build(); self.step @@ -318,7 +317,7 @@ impl StartedTestStep { &self, symptom: &str, msg: &str, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let error = error::Error::builder(symptom).message(msg).build(); self.step @@ -358,7 +357,7 @@ impl StartedTestStep { pub async fn error_with_details( &self, error: &error::Error, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -390,7 +389,7 @@ impl StartedTestStep { &self, name: &str, value: Value, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { let measurement = measure::Measurement::new(name, value); self.step @@ -433,7 +432,7 @@ impl StartedTestStep { pub async fn add_measurement_with_details( &self, measurement: &measure::Measurement, - ) -> Result<(), emitter::WriterError> { + ) -> Result<(), writer::WriterError> { self.step .emitter .emit(&spec::TestStepArtifactImpl::Measurement( @@ -505,7 +504,10 @@ pub struct StepEmitter { } impl StepEmitter { - pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), WriterError> { + pub async fn emit( + &self, + object: &spec::TestStepArtifactImpl, + ) -> Result<(), writer::WriterError> { let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { id: self.step_id.clone(), // TODO: can these copies be avoided? diff --git a/src/output/writer.rs b/src/output/writer.rs new file mode 100644 index 0000000..ce444f5 --- /dev/null +++ b/src/output/writer.rs @@ -0,0 +1,81 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::io::{self, Write}; +use std::path::Path; +use std::sync::Arc; + +use tokio::fs; +use tokio::io::AsyncWriteExt; +use tokio::sync::Mutex; + +#[derive(Debug, thiserror::Error, derive_more::Display)] +#[non_exhaustive] +pub enum WriterError { + IoError(#[from] io::Error), +} + +pub(crate) enum WriterType { + Stdout(StdoutWriter), + File(FileWriter), + Buffer(BufferWriter), +} + +pub struct FileWriter { + file: Arc>, +} + +impl FileWriter { + pub async fn new>(path: P) -> Result { + let file = fs::File::create(path).await.map_err(WriterError::IoError)?; + Ok(FileWriter { + file: Arc::new(Mutex::new(file)), + }) + } + + pub async fn write(&self, s: &str) -> Result<(), WriterError> { + let mut handle = self.file.lock().await; + + let mut buf = Vec::::new(); + writeln!(buf, "{}", s)?; + + handle.write_all(&buf).await.map_err(WriterError::IoError)?; + handle.flush().await.map_err(WriterError::IoError)?; + + Ok(()) + } +} + +#[derive(Debug)] +pub struct BufferWriter { + buffer: Arc>>, +} + +impl BufferWriter { + pub fn new(buffer: Arc>>) -> Self { + Self { buffer } + } + + pub async fn write(&self, s: &str) -> Result<(), WriterError> { + self.buffer.lock().await.push(s.to_string()); + Ok(()) + } +} + +#[derive(Debug, Clone)] +pub struct StdoutWriter {} + +#[allow(clippy::new_without_default)] +impl StdoutWriter { + pub fn new() -> Self { + StdoutWriter {} + } + + pub async fn write(&self, s: &str) -> Result<(), WriterError> { + println!("{}", s); + Ok(()) + } +} From c705906c785ec991fabaead2a138dc5713de5aa0 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 18:52:15 +0100 Subject: [PATCH 33/96] refactor crate error type - add a single error type for most of the api Signed-off-by: mimir-d --- src/output/config.rs | 5 ++-- src/output/macros.rs | 6 ++-- src/output/measure.rs | 24 ++++++++-------- src/output/mod.rs | 9 +++++- src/output/run.rs | 43 +++++++++++----------------- src/output/step.rs | 63 ++++++++++++++++-------------------------- tests/output/runner.rs | 4 +-- 7 files changed, 68 insertions(+), 86 deletions(-) diff --git a/src/output/config.rs b/src/output/config.rs index a1b8544..55e0602 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -9,7 +9,8 @@ use std::sync::Arc; use tokio::sync::Mutex; -use crate::output::writer::{self, BufferWriter, FileWriter, StdoutWriter, WriterType}; +use crate::output as tv; +use crate::output::writer::{BufferWriter, FileWriter, StdoutWriter, WriterType}; /// The configuration repository for the TestRun. pub struct Config { @@ -66,7 +67,7 @@ impl ConfigBuilder { pub async fn with_file_output>( mut self, path: P, - ) -> Result { + ) -> Result { self.writer = Some(WriterType::File(FileWriter::new(path).await?)); Ok(self) } diff --git a/src/output/macros.rs b/src/output/macros.rs index e09717f..6cb11e4 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -31,7 +31,7 @@ /// ocptv_error!(test_run, "symptom"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// -/// # Ok::<(), WriterError>(()) +/// # Ok::<(), OcptvError>(()) /// # }); /// ``` /// @@ -47,7 +47,7 @@ /// ocptv_error!(test_run, "symptom", "Error message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// -/// # Ok::<(), WriterError>(()) +/// # Ok::<(), OcptvError>(()) /// # }); /// ``` #[macro_export] @@ -94,7 +94,7 @@ macro_rules! ocptv_error { /// ocptv_log_debug!(run, "Log message"); /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// -/// # Ok::<(), WriterError>(()) +/// # Ok::<(), OcptvError>(()) /// # }); /// ``` diff --git a/src/output/measure.rs b/src/output/measure.rs index 50a9677..07cc86d 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -12,7 +12,7 @@ use serde_json::Value; use crate::output as tv; use crate::spec; -use tv::{dut, step, writer}; +use tv::{dut, step}; /// The measurement series. /// A Measurement Series is a time-series list of measurements. @@ -57,10 +57,10 @@ impl MeasurementSeries { /// let series = step.measurement_series("name"); /// series.start().await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { self.emitter .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart( self.start.to_artifact(), @@ -98,12 +98,12 @@ impl MeasurementSeries { // /// Ok(()) // /// }).await?; // /// - // /// # Ok::<(), WriterError>(()) + // /// # Ok::<(), OcptvError>(()) // /// # }); // /// ``` - // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), writer::WriterError> + // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), tv::OcptvError> // where - // R: Future>, + // R: Future>, // F: std::ops::FnOnce(&'s MeasurementSeries) -> R, // { // self.start().await?; @@ -140,10 +140,10 @@ impl StartedMeasurementSeries { /// let series = step.measurement_series("name").start().await?; /// series.end().await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn end(&self) -> Result<(), writer::WriterError> { + pub async fn end(&self) -> Result<(), tv::OcptvError> { let end = spec::MeasurementSeriesEnd { series_id: self.parent.start.series_id.clone(), total_count: self.seqno.load(Ordering::Acquire), @@ -173,10 +173,10 @@ impl StartedMeasurementSeries { /// let series = step.measurement_series("name").start().await?; /// series.add_measurement(60.into()).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), writer::WriterError> { + pub async fn add_measurement(&self, value: Value) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), @@ -212,14 +212,14 @@ impl StartedMeasurementSeries { /// let series = step.measurement_series("name").start().await?; /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub async fn add_measurement_with_metadata( &self, value: Value, metadata: Vec<(&str, Value)>, - ) -> Result<(), writer::WriterError> { + ) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), diff --git a/src/output/mod.rs b/src/output/mod.rs index 5e28a5e..03e6875 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -28,6 +28,13 @@ pub use log::*; pub use measure::*; pub use run::*; pub use step::*; -pub use writer::WriterError; +pub use writer::*; +// re-export this as a public type we present pub use serde_json::Value; + +#[derive(Debug, thiserror::Error, derive_more::Display)] +#[non_exhaustive] +pub enum OcptvError { + WriterError(#[from] writer::WriterError), +} diff --git a/src/output/run.rs b/src/output/run.rs index 64b740c..3457f77 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -15,7 +15,7 @@ use serde_json::Value; use crate::output as tv; use crate::spec; use tv::step::TestStep; -use tv::{config, dut, emitter, error, log, writer}; +use tv::{config, dut, emitter, error, log}; use super::JsonEmitter; @@ -85,10 +85,10 @@ impl TestRun { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); /// run.start().await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version self.emitter .emit(&spec::RootImpl::SchemaVersion( @@ -137,7 +137,7 @@ impl TestRun { // /// }) // /// }).await?; // /// - // /// # Ok::<(), WriterError>(()) + // /// # Ok::<(), OcptvError>(()) // /// # }); // /// ``` // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> @@ -302,14 +302,14 @@ impl StartedTestRun { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub async fn end( &self, status: spec::TestStatus, result: spec::TestResult, - ) -> Result<(), writer::WriterError> { + ) -> Result<(), tv::OcptvError> { let end = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), }); @@ -337,14 +337,10 @@ impl StartedTestRun { /// ).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log( - &self, - severity: spec::LogSeverity, - msg: &str, - ) -> Result<(), writer::WriterError> { + pub async fn log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError> { let log = log::Log::builder(msg).severity(severity).build(); let artifact = spec::TestRunArtifact { @@ -378,10 +374,10 @@ impl StartedTestRun { /// ).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), writer::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -408,10 +404,10 @@ impl StartedTestRun { /// run.error("symptom").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), writer::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); let artifact = spec::TestRunArtifact { @@ -441,14 +437,10 @@ impl StartedTestRun { /// run.error_with_msg("symptom", "error messasge").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), writer::WriterError> { + pub async fn error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); let artifact = spec::TestRunArtifact { @@ -483,13 +475,10 @@ impl StartedTestRun { /// ).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_details( - &self, - error: &error::Error, - ) -> Result<(), writer::WriterError> { + pub async fn error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; diff --git a/src/output/step.rs b/src/output/step.rs index f4f1833..cee4aac 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -49,10 +49,10 @@ impl TestStep { /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// let step = run.step("step_name").start().await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { + pub async fn start(self) -> Result { self.emitter .emit(&TestStepArtifactImpl::TestStepStart(TestStepStart { name: self.name.clone(), @@ -89,7 +89,7 @@ impl TestStep { // /// Ok(TestStatus::Complete) // /// }).await?; // /// - // /// # Ok::<(), WriterError>(()) + // /// # Ok::<(), OcptvError>(()) // /// # }); // /// ``` // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> @@ -125,10 +125,10 @@ impl StartedTestStep { /// let step = run.step("step_name").start().await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn end(&self, status: spec::TestStatus) -> Result<(), writer::WriterError> { + pub async fn end(&self, status: spec::TestStatus) -> Result<(), tv::OcptvError> { let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); self.step.emitter.emit(&end).await?; @@ -156,7 +156,7 @@ impl StartedTestStep { /// ).await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` /// ## Using macros @@ -173,14 +173,10 @@ impl StartedTestStep { /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log( - &self, - severity: spec::LogSeverity, - msg: &str, - ) -> Result<(), writer::WriterError> { + pub async fn log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError> { let log = log::Log::builder(msg).severity(severity).build(); self.step @@ -213,10 +209,10 @@ impl StartedTestStep { /// ).await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), writer::WriterError> { + pub async fn log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -242,7 +238,7 @@ impl StartedTestStep { /// step.error("symptom").await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` /// @@ -260,10 +256,10 @@ impl StartedTestStep { /// ocptv_error!(step, "symptom").await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), writer::WriterError> { + pub async fn error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); self.step @@ -292,7 +288,7 @@ impl StartedTestStep { /// step.error_with_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` /// @@ -310,14 +306,10 @@ impl StartedTestStep { /// ocptv_error!(step, "symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_msg( - &self, - symptom: &str, - msg: &str, - ) -> Result<(), writer::WriterError> { + pub async fn error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); self.step @@ -351,13 +343,10 @@ impl StartedTestStep { /// ).await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_details( - &self, - error: &error::Error, - ) -> Result<(), writer::WriterError> { + pub async fn error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -382,14 +371,10 @@ impl StartedTestStep { /// step.add_measurement("name", 50.into()).await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement( - &self, - name: &str, - value: Value, - ) -> Result<(), writer::WriterError> { + pub async fn add_measurement(&self, name: &str, value: Value) -> Result<(), tv::OcptvError> { let measurement = measure::Measurement::new(name, value); self.step @@ -426,13 +411,13 @@ impl StartedTestStep { /// step.add_measurement_with_details(&measurement).await?; /// step.end(TestStatus::Complete).await?; /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub async fn add_measurement_with_details( &self, measurement: &measure::Measurement, - ) -> Result<(), writer::WriterError> { + ) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&spec::TestStepArtifactImpl::Measurement( @@ -459,7 +444,7 @@ impl StartedTestStep { /// let step = run.step("step_name").start().await?; /// let series = step.measurement_series("name"); /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub fn measurement_series(&self, name: &str) -> MeasurementSeries { @@ -487,7 +472,7 @@ impl StartedTestStep { /// let series = /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); /// - /// # Ok::<(), WriterError>(()) + /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub fn measurement_series_with_details( diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 82c7b85..bf9b998 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -135,7 +135,7 @@ where async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestRun) -> BoxFuture<'a, Result<(), tv::WriterError>> + Send, + F: for<'a> FnOnce(&'a StartedTestRun) -> BoxFuture<'a, Result<(), tv::OcptvError>> + Send, { check_output(expected, |run_builder| async { let run = run_builder.build(); @@ -151,7 +151,7 @@ where async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestStep) -> BoxFuture<'a, Result<(), tv::WriterError>>, + F: for<'a> FnOnce(&'a StartedTestStep) -> BoxFuture<'a, Result<(), tv::OcptvError>>, { check_output(expected, |run_builder| async { let run = run_builder.build().start().await?; From 57f7673d0379bc6e137208756b0f444f83ca19c8 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 19:51:19 +0100 Subject: [PATCH 34/96] cleanup WriterError; custom writer type - remove WriterError because it'll always be a type of io::Error - add WriterType::Custom so that we can write a test checking an actual error return from an api call (missing before this commit) Signed-off-by: mimir-d --- Cargo.lock | 57 +++++++++++-------------------------- Cargo.toml | 3 +- src/output/config.rs | 10 ++++++- src/output/emitter.rs | 24 +++++++++++----- src/output/mod.rs | 7 +++-- src/output/step.rs | 8 ++---- src/output/writer.rs | 66 +++++++++++++++++++++++++++++++++++-------- 7 files changed, 106 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f289b38..ec3f2d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -100,6 +100,17 @@ dependencies = [ "syn", ] +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -199,15 +210,6 @@ dependencies = [ "phf_codegen", ] -[[package]] -name = "convert_case" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -239,28 +241,6 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" -[[package]] -name = "derive_more" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" -dependencies = [ - "derive_more-impl", -] - -[[package]] -name = "derive_more-impl" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" -dependencies = [ - "convert_case", - "proc-macro2", - "quote", - "syn", - "unicode-xid", -] - [[package]] name = "difflib" version = "0.4.0" @@ -535,9 +515,9 @@ dependencies = [ "anyhow", "assert-json-diff", "assert_fs", + "async-trait", "chrono", "chrono-tz", - "derive_more", "futures", "predicates", "serde", @@ -545,6 +525,7 @@ dependencies = [ "thiserror", "tokio", "tokio-test", + "unwrap-infallible", ] [[package]] @@ -904,16 +885,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" +name = "unwrap-infallible" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" [[package]] name = "walkdir" diff --git a/Cargo.toml b/Cargo.toml index 23c89ff..70fec41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,9 @@ license = "MIT" edition = "2021" [dependencies] +async-trait = "0.1.83" chrono = "0.4.38" chrono-tz = "0.10.0" -derive_more = { version = "1.0.0", features = ["full"] } serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" thiserror = "1.0.64" @@ -23,6 +23,7 @@ tokio = { version = "1.40.0", features = [ "fs", "sync", ] } +unwrap-infallible = "0.1.5" [dev-dependencies] anyhow = "1.0.89" diff --git a/src/output/config.rs b/src/output/config.rs index 55e0602..ca421a1 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -10,7 +10,7 @@ use std::sync::Arc; use tokio::sync::Mutex; use crate::output as tv; -use crate::output::writer::{BufferWriter, FileWriter, StdoutWriter, WriterType}; +use crate::output::writer::{self, BufferWriter, FileWriter, StdoutWriter, WriterType}; /// The configuration repository for the TestRun. pub struct Config { @@ -72,6 +72,14 @@ impl ConfigBuilder { Ok(self) } + pub fn with_custom_output( + mut self, + custom: Box, + ) -> Self { + self.writer = Some(WriterType::Custom(custom)); + self + } + pub fn build(self) -> Config { Config { timestamp_provider: self.timestamp_provider, diff --git a/src/output/emitter.rs b/src/output/emitter.rs index e78785a..7c9cc7e 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -4,10 +4,16 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::io; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; -use crate::output::{config, writer}; +use unwrap_infallible::UnwrapInfallible; + +use crate::output::{ + config, + writer::{self, WriterType}, +}; use crate::spec; pub struct JsonEmitter { @@ -42,13 +48,17 @@ impl JsonEmitter { self.seqno.fetch_add(1, Ordering::AcqRel) } - pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), writer::WriterError> { - let serialized = self.serialize_artifact(object); - match self.writer { - writer::WriterType::File(ref file) => file.write(&serialized.to_string()).await?, - writer::WriterType::Stdout(ref stdout) => stdout.write(&serialized.to_string()).await?, - writer::WriterType::Buffer(ref buffer) => buffer.write(&serialized.to_string()).await?, + pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), io::Error> { + let s = self.serialize_artifact(object).to_string(); + + match &self.writer { + WriterType::File(file) => file.write(&s).await?, + WriterType::Stdout(stdout) => stdout.write(&s).await.unwrap_infallible(), + WriterType::Buffer(buffer) => buffer.write(&s).await.unwrap_infallible(), + + WriterType::Custom(custom) => custom.write(&s).await?, } + Ok(()) } } diff --git a/src/output/mod.rs b/src/output/mod.rs index 03e6875..4e00034 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![deny(warnings)] mod config; mod dut; @@ -33,8 +34,10 @@ pub use writer::*; // re-export this as a public type we present pub use serde_json::Value; -#[derive(Debug, thiserror::Error, derive_more::Display)] +#[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum OcptvError { - WriterError(#[from] writer::WriterError), + #[error("failed to write to output stream")] + IoError(#[from] std::io::Error), + // other? } diff --git a/src/output/step.rs b/src/output/step.rs index cee4aac..cb99208 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -5,6 +5,7 @@ // https://opensource.org/licenses/MIT. use serde_json::Value; +use std::io; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; @@ -12,7 +13,7 @@ use crate::output as tv; use crate::spec::TestStepStart; use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; -use tv::{error, log, measure, writer}; +use tv::{error, log, measure}; use super::{JsonEmitter, TimestampProvider}; @@ -489,10 +490,7 @@ pub struct StepEmitter { } impl StepEmitter { - pub async fn emit( - &self, - object: &spec::TestStepArtifactImpl, - ) -> Result<(), writer::WriterError> { + pub async fn emit(&self, object: &spec::TestStepArtifactImpl) -> Result<(), io::Error> { let root = spec::RootImpl::TestStepArtifact(spec::TestStepArtifact { id: self.step_id.clone(), // TODO: can these copies be avoided? diff --git a/src/output/writer.rs b/src/output/writer.rs index ce444f5..2c660da 100644 --- a/src/output/writer.rs +++ b/src/output/writer.rs @@ -4,24 +4,28 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::convert::Infallible; use std::io::{self, Write}; use std::path::Path; use std::sync::Arc; +use async_trait::async_trait; use tokio::fs; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; -#[derive(Debug, thiserror::Error, derive_more::Display)] -#[non_exhaustive] -pub enum WriterError { - IoError(#[from] io::Error), +#[async_trait] +pub trait Writer { + async fn write(&self, s: &str) -> Result<(), io::Error>; } -pub(crate) enum WriterType { +pub enum WriterType { + // optimization: static dispatch for these known types Stdout(StdoutWriter), File(FileWriter), Buffer(BufferWriter), + + Custom(Box), } pub struct FileWriter { @@ -29,21 +33,21 @@ pub struct FileWriter { } impl FileWriter { - pub async fn new>(path: P) -> Result { - let file = fs::File::create(path).await.map_err(WriterError::IoError)?; + pub async fn new>(path: P) -> Result { + let file = fs::File::create(path).await?; Ok(FileWriter { file: Arc::new(Mutex::new(file)), }) } - pub async fn write(&self, s: &str) -> Result<(), WriterError> { + pub async fn write(&self, s: &str) -> Result<(), io::Error> { let mut handle = self.file.lock().await; let mut buf = Vec::::new(); writeln!(buf, "{}", s)?; - handle.write_all(&buf).await.map_err(WriterError::IoError)?; - handle.flush().await.map_err(WriterError::IoError)?; + handle.write_all(&buf).await?; + handle.flush().await?; Ok(()) } @@ -59,7 +63,7 @@ impl BufferWriter { Self { buffer } } - pub async fn write(&self, s: &str) -> Result<(), WriterError> { + pub async fn write(&self, s: &str) -> Result<(), Infallible> { self.buffer.lock().await.push(s.to_string()); Ok(()) } @@ -74,8 +78,46 @@ impl StdoutWriter { StdoutWriter {} } - pub async fn write(&self, s: &str) -> Result<(), WriterError> { + pub async fn write(&self, s: &str) -> Result<(), Infallible> { println!("{}", s); Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::output::*; + use anyhow::Result; + + struct ErrorWriter {} + + #[async_trait] + impl Writer for ErrorWriter { + async fn write(&self, _s: &str) -> Result<(), io::Error> { + Err(io::Error::other("err")) + } + } + + #[tokio::test] + async fn test_ocptv_error_has_public_source() -> Result<()> { + let dut = DutInfo::builder("dut_id").build(); + let run_builder = TestRun::builder("run_name", &dut, "1.0").config( + Config::builder() + .with_custom_output(Box::new(ErrorWriter {})) + .build(), + ); + + let actual = run_builder.build().start().await; + assert!(actual.is_err()); + + match &actual { + Err(OcptvError::IoError(ioe)) => { + assert_eq!(ioe.kind(), io::ErrorKind::Other); + } + _ => panic!("unknown error"), + } + + Ok(()) + } +} From 9a6bee9f45a51fe0488bece93d835ba5c26980d4 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sun, 6 Oct 2024 20:01:33 +0100 Subject: [PATCH 35/96] explicitly present public crate api Signed-off-by: mimir-d --- src/output/mod.rs | 29 +++++++++++++++-------------- src/output/run.rs | 4 +--- src/output/step.rs | 11 +++++------ 3 files changed, 21 insertions(+), 23 deletions(-) diff --git a/src/output/mod.rs b/src/output/mod.rs index 4e00034..be498b8 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -16,20 +16,21 @@ mod run; mod step; mod writer; -pub use crate::spec::LogSeverity; -pub use crate::spec::TestResult; -pub use crate::spec::TestStatus; -pub use crate::spec::ValidatorType; -pub use crate::spec::SPEC_VERSION; -pub use config::*; -pub use dut::*; -pub use emitter::*; -pub use error::*; -pub use log::*; -pub use measure::*; -pub use run::*; -pub use step::*; -pub use writer::*; +pub use crate::spec::{LogSeverity, TestResult, TestStatus, ValidatorType, SPEC_VERSION}; +pub use config::{Config, ConfigBuilder, TimestampProvider}; +pub use dut::{ + DutInfo, DutInfoBuilder, HardwareInfo, HardwareInfoBuilder, PlatformInfo, PlatformInfoBuilder, + SoftwareInfo, SoftwareInfoBuilder, Subcomponent, SubcomponentBuilder, +}; +pub use error::{Error, ErrorBuilder}; +pub use log::{Log, LogBuilder}; +pub use measure::{ + Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesStart, + MeasurementSeriesStartBuilder, StartedMeasurementSeries, Validator, ValidatorBuilder, +}; +pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; +pub use step::{StartedTestStep, TestStep}; +pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export this as a public type we present pub use serde_json::Value; diff --git a/src/output/run.rs b/src/output/run.rs index 3457f77..b26d462 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -17,8 +17,6 @@ use crate::spec; use tv::step::TestStep; use tv::{config, dut, emitter, error, log}; -use super::JsonEmitter; - /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. pub struct TestRunOutcome { @@ -39,7 +37,7 @@ pub struct TestRun { command_line: String, metadata: Option>, - emitter: Arc, + emitter: Arc, } impl TestRun { diff --git a/src/output/step.rs b/src/output/step.rs index cb99208..47d15f5 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -13,9 +13,7 @@ use crate::output as tv; use crate::spec::TestStepStart; use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; -use tv::{error, log, measure}; - -use super::{JsonEmitter, TimestampProvider}; +use tv::{emitter, error, log, measure}; /// A single test step in the scope of a [`TestRun`]. /// @@ -27,7 +25,7 @@ pub struct TestStep { } impl TestStep { - pub(crate) fn new(id: &str, name: &str, run_emitter: Arc) -> Self { + pub(crate) fn new(id: &str, name: &str, run_emitter: Arc) -> Self { TestStep { name: name.to_owned(), emitter: Arc::new(StepEmitter { @@ -486,7 +484,7 @@ impl StartedTestStep { pub struct StepEmitter { step_id: String, - run_emitter: Arc, + run_emitter: Arc, } impl StepEmitter { @@ -501,7 +499,8 @@ impl StepEmitter { Ok(()) } - pub fn timestamp_provider(&self) -> &dyn TimestampProvider { + // HACK: + pub fn timestamp_provider(&self) -> &dyn tv::config::TimestampProvider { &*self.run_emitter.timestamp_provider } } From 13912a97d09e2c98b4034e86f811ce0ac287e4a0 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 17:11:00 +0100 Subject: [PATCH 36/96] end methods should consume self - this change disallows usage of contextual objects after emitting their end artifact, at compile time - previously a user could have emitted, for example, a step log after a step end Signed-off-by: mimir-d --- src/output/measure.rs | 2 +- src/output/run.rs | 2 +- src/output/step.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/output/measure.rs b/src/output/measure.rs index 07cc86d..56befb6 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -143,7 +143,7 @@ impl StartedMeasurementSeries { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn end(&self) -> Result<(), tv::OcptvError> { + pub async fn end(self) -> Result<(), tv::OcptvError> { let end = spec::MeasurementSeriesEnd { series_id: self.parent.start.series_id.clone(), total_count: self.seqno.load(Ordering::Acquire), diff --git a/src/output/run.rs b/src/output/run.rs index b26d462..059a164 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -304,7 +304,7 @@ impl StartedTestRun { /// # }); /// ``` pub async fn end( - &self, + self, status: spec::TestStatus, result: spec::TestResult, ) -> Result<(), tv::OcptvError> { diff --git a/src/output/step.rs b/src/output/step.rs index 47d15f5..e7b2fe5 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -127,7 +127,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn end(&self, status: spec::TestStatus) -> Result<(), tv::OcptvError> { + pub async fn end(self, status: spec::TestStatus) -> Result<(), tv::OcptvError> { let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); self.step.emitter.emit(&end).await?; From 71bcce6864792a7ae7726548fa26aed0501a85c8 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 17:19:59 +0100 Subject: [PATCH 37/96] make create api methods naming consistent - some methods had a verb, some didnt; add a verb to all the public api methods for consistency (also matches the python api more) - another reason for this is that the verb allows for backward/future compat in the api (eg. `add_step` could turn into `start_step`, with the semantic of also starting the step, not just creating it; all while being able to keep the previous `add_step` name. if the method was called just `step`, this wouldnt be possible) Signed-off-by: mimir-d --- src/output/macros.rs | 6 ++-- src/output/measure.rs | 20 ++++++------- src/output/run.rs | 30 +++++++++++-------- src/output/step.rs | 68 ++++++++++++++++++++++-------------------- tests/output/macros.rs | 2 +- tests/output/runner.rs | 51 ++++++++++++++++--------------- 6 files changed, 95 insertions(+), 82 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index 6cb11e4..41587e5 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -53,7 +53,7 @@ #[macro_export] macro_rules! ocptv_error { ($runner:expr, $symptom:expr, $msg:expr) => { - $runner.error_with_details( + $runner.add_error_with_details( &$crate::output::Error::builder($symptom) .message($msg) .source(file!(), line!() as i32) @@ -62,7 +62,7 @@ macro_rules! ocptv_error { }; ($runner:expr, $symptom:expr) => { - $runner.error_with_details( + $runner.add_error_with_details( &$crate::output::Error::builder($symptom) .source(file!(), line!() as i32) .build(), @@ -103,7 +103,7 @@ macro_rules! ocptv_log { #[macro_export] macro_rules! $name { ($artifact:expr, $msg:expr) => { - $artifact.log_with_details( + $artifact.add_log_with_details( &$crate::output::Log::builder($msg) .severity($crate::output::LogSeverity::$severity) .source(file!(), line!() as i32) diff --git a/src/output/measure.rs b/src/output/measure.rs index 56befb6..cb4f42c 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -52,9 +52,9 @@ impl MeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// - /// let series = step.measurement_series("name"); + /// let series = step.add_measurement_series("name"); /// series.start().await?; /// /// # Ok::<(), OcptvError>(()) @@ -87,9 +87,9 @@ impl MeasurementSeries { // /// # use ocptv::output::*; // /// // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - // /// let step = run.step("step_name").start().await?; + // /// let step = run.add_step("step_name").start().await?; // /// - // /// let series = step.measurement_series("name"); + // /// let series = step.add_measurement_series("name"); // /// series.start().await?; // /// series.scope(|s| async { // /// s.add_measurement(60.into()).await?; @@ -135,9 +135,9 @@ impl StartedMeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// - /// let series = step.measurement_series("name").start().await?; + /// let series = step.add_measurement_series("name").start().await?; /// series.end().await?; /// /// # Ok::<(), OcptvError>(()) @@ -168,9 +168,9 @@ impl StartedMeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// - /// let series = step.measurement_series("name").start().await?; + /// let series = step.add_measurement_series("name").start().await?; /// series.add_measurement(60.into()).await?; /// /// # Ok::<(), OcptvError>(()) @@ -207,9 +207,9 @@ impl StartedMeasurementSeries { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// - /// let series = step.measurement_series("name").start().await?; + /// let series = step.add_measurement_series("name").start().await?; /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; /// /// # Ok::<(), OcptvError>(()) diff --git a/src/output/run.rs b/src/output/run.rs index 059a164..350a8d0 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -128,7 +128,7 @@ impl TestRun { // /// // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); // /// run.scope(|r| async { - // /// r.log(LogSeverity::Info, "First message").await?; + // /// r.add_log(LogSeverity::Info, "First message").await?; // /// Ok(TestRunOutcome { // /// status: TestStatus::Complete, // /// result: TestResult::Pass, @@ -329,7 +329,7 @@ impl StartedTestRun { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log( + /// run.add_log( /// LogSeverity::Info, /// "This is a log message with INFO severity", /// ).await?; @@ -338,7 +338,11 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_log( + &self, + severity: spec::LogSeverity, + msg: &str, + ) -> Result<(), tv::OcptvError> { let log = log::Log::builder(msg).severity(severity).build(); let artifact = spec::TestRunArtifact { @@ -364,7 +368,7 @@ impl StartedTestRun { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.log_with_details( + /// run.add_log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) @@ -375,7 +379,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -399,13 +403,13 @@ impl StartedTestRun { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error("symptom").await?; + /// run.add_error("symptom").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); let artifact = spec::TestRunArtifact { @@ -432,13 +436,13 @@ impl StartedTestRun { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_msg("symptom", "error messasge").await?; + /// run.add_error_with_msg("symptom", "error messasge").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); let artifact = spec::TestRunArtifact { @@ -464,7 +468,7 @@ impl StartedTestRun { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// run.error_with_details( + /// run.add_error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) @@ -476,7 +480,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; @@ -488,7 +492,9 @@ impl StartedTestRun { Ok(()) } - pub fn step(&self, name: &str) -> TestStep { + /// Create a new step for this test run. + /// TODO: docs + example + pub fn add_step(&self, name: &str) -> TestStep { let step_id = format!("step_{}", self.step_seqno.fetch_add(1, Ordering::AcqRel)); TestStep::new(&step_id, name, Arc::clone(&self.run.emitter)) } diff --git a/src/output/step.rs b/src/output/step.rs index e7b2fe5..d2834d3 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -46,7 +46,7 @@ impl TestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// /// # Ok::<(), OcptvError>(()) /// # }); @@ -79,9 +79,9 @@ impl TestStep { // /// // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; // /// - // /// let step = run.step("first step")?; + // /// let step = run.add_step("first step")?; // /// step.scope(|s| async { - // /// s.log( + // /// s.add_log( // /// LogSeverity::Info, // /// "This is a log message with INFO severity", // /// ).await?; @@ -121,7 +121,7 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) @@ -148,8 +148,8 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; - /// step.log( + /// let step = run.add_step("step_name").start().await?; + /// step.add_log( /// LogSeverity::Info, /// "This is a log message with INFO severity", /// ).await?; @@ -168,14 +168,18 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_log( + &self, + severity: spec::LogSeverity, + msg: &str, + ) -> Result<(), tv::OcptvError> { let log = log::Log::builder(msg).severity(severity).build(); self.step @@ -199,8 +203,8 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; - /// step.log_with_details( + /// let step = run.add_step("step_name").start().await?; + /// step.add_log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) @@ -211,7 +215,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -233,8 +237,8 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; - /// step.error("symptom").await?; + /// let step = run.add_step("step_name").start().await?; + /// step.add_error("symptom").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) @@ -251,14 +255,14 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// ocptv_error!(step, "symptom").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error(&self, symptom: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); self.step @@ -283,8 +287,8 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_msg("symptom", "error message").await?; + /// let step = run.add_step("step_name").start().await?; + /// step.add_error_with_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) @@ -301,14 +305,14 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// ocptv_error!(step, "symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); self.step @@ -332,8 +336,8 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; - /// step.error_with_details( + /// let step = run.add_step("step_name").start().await?; + /// step.add_error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) @@ -345,7 +349,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -366,7 +370,7 @@ impl StartedTestStep { /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; /// - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// step.add_measurement("name", 50.into()).await?; /// step.end(TestStatus::Complete).await?; /// @@ -399,7 +403,7 @@ impl StartedTestStep { /// /// let hwinfo = HardwareInfo::builder("id", "fan").build(); /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) /// .hardware_info(&hwinfo) @@ -427,7 +431,7 @@ impl StartedTestStep { Ok(()) } - /// Starts a Measurement Series (a time-series list of measurements). + /// Create a Measurement Series (a time-series list of measurements). /// This method accepts a [`std::string::String`] as series ID and /// a [`std::string::String`] as series name. /// @@ -440,13 +444,13 @@ impl StartedTestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; - /// let series = step.measurement_series("name"); + /// let step = run.add_step("step_name").start().await?; + /// let series = step.add_measurement_series("name"); /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub fn measurement_series(&self, name: &str) -> MeasurementSeries { + pub fn add_measurement_series(&self, name: &str) -> MeasurementSeries { let series_id: String = format!( "series_{}", self.measurement_id_seqno.fetch_add(1, Ordering::AcqRel) @@ -455,7 +459,7 @@ impl StartedTestStep { MeasurementSeries::new(&series_id, name, Arc::clone(&self.step.emitter)) } - /// Starts a Measurement Series (a time-series list of measurements). + /// Create a Measurement Series (a time-series list of measurements). /// This method accepts a [`objects::MeasurementSeriesStart`] object. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart @@ -467,14 +471,14 @@ impl StartedTestStep { /// # use ocptv::output::*; /// /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - /// let step = run.step("step_name").start().await?; + /// let step = run.add_step("step_name").start().await?; /// let series = - /// step.measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// step.add_measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub fn measurement_series_with_details( + pub fn add_measurement_series_with_details( &self, start: measure::MeasurementSeriesStart, ) -> MeasurementSeries { diff --git a/tests/output/macros.rs b/tests/output/macros.rs index 6da7b9d..e1fcf6d 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -78,7 +78,7 @@ where F: FnOnce(StartedTestStep) -> R, { let actual = check_output::<_, _, 4>(expected, |run| async move { - let step = run.step("step_name").start().await?; + let step = run.add_step("step_name").start().await?; func(step).await?; Ok(()) diff --git a/tests/output/runner.rs b/tests/output/runner.rs index bf9b998..178ddbf 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -156,7 +156,7 @@ where check_output(expected, |run_builder| async { let run = run_builder.build().start().await?; - let step = run.step("first step").start().await?; + let step = run.add_step("first step").start().await?; test_fn(&step).await?; step.end(TestStatus::Complete).await?; @@ -198,7 +198,7 @@ async fn test_testrun_with_log() -> Result<()> { check_output_run(&expected, |run| { async { - run.log( + run.add_log( LogSeverity::Info, "This is a log message with INFO severity", ) @@ -233,7 +233,7 @@ async fn test_testrun_with_log_with_details() -> Result<()> { check_output_run(&expected, |run| { async { - run.log_with_details( + run.add_log_with_details( &Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) @@ -264,7 +264,7 @@ async fn test_testrun_with_error() -> Result<()> { ]; check_output_run(&expected, |run| { - async { run.error("symptom").await }.boxed() + async { run.add_error("symptom").await }.boxed() }) .await } @@ -288,7 +288,7 @@ async fn test_testrun_with_error_with_message() -> Result<()> { ]; check_output_run(&expected, |run| { - async { run.error_with_msg("symptom", "Error message").await }.boxed() + async { run.add_error_with_msg("symptom", "Error message").await }.boxed() }) .await } @@ -321,7 +321,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { check_output_run(&expected, |run| { async { - run.error_with_details( + run.add_error_with_details( &Error::builder("symptom") .message("Error message") .source("file", 1) @@ -356,7 +356,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { // let run = run_builder.build(); // run.scope(|r| async { -// r.log(LogSeverity::Info, "First message").await?; +// r.add_log(LogSeverity::Info, "First message").await?; // Ok(TestRunOutcome { // status: TestStatus::Complete, // result: TestResult::Pass, @@ -405,7 +405,7 @@ async fn test_testrun_step_log() -> Result<()> { check_output_step(&expected, |step| { async { - step.log( + step.add_log( LogSeverity::Info, "This is a log message with INFO severity", ) @@ -445,7 +445,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { check_output_step(&expected, |step| { async { - step.log_with_details( + step.add_log_with_details( &Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) @@ -482,7 +482,7 @@ async fn test_testrun_step_error() -> Result<()> { check_output_step(&expected, |step| { async { - step.error("symptom").await?; + step.add_error("symptom").await?; Ok(()) } @@ -514,7 +514,7 @@ async fn test_testrun_step_error_with_message() -> Result<()> { check_output_step(&expected, |step| { async { - step.error_with_msg("symptom", "Error message").await?; + step.add_error_with_msg("symptom", "Error message").await?; Ok(()) } @@ -554,7 +554,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { check_output_step(&expected, |step| { async { - step.error_with_details( + step.add_error_with_details( &Error::builder("symptom") .message("Error message") .source("file", 1) @@ -594,7 +594,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { // run.step("first step") // .start() // .scope(|s| async { -// s.log( +// s.add_log( // LogSeverity::Info, // "This is a log message with INFO severity", // ) @@ -723,7 +723,7 @@ async fn test_step_with_measurement_series() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; series.end().await?; Ok(()) @@ -789,10 +789,10 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; series.end().await?; - let series_2 = step.measurement_series("name").start().await?; + let series_2 = step.add_measurement_series("name").start().await?; series_2.end().await?; Ok(()) @@ -836,7 +836,10 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { check_output_step(&expected, |step| { async { let series = step - .measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")) + .add_measurement_series_with_details(MeasurementSeriesStart::new( + "name", + "series_id", + )) .start() .await?; series.end().await?; @@ -897,7 +900,7 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R check_output_step(&expected, |step| { async { let series = step - .measurement_series_with_details( + .add_measurement_series_with_details( MeasurementSeriesStart::builder("name", "series_id") .add_metadata("key", "value".into()) .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) @@ -963,7 +966,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; series.add_measurement(60.into()).await?; series.end().await?; @@ -1047,7 +1050,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series.add_measurement(60.into()).await?; series.add_measurement(70.into()).await?; @@ -1111,7 +1114,7 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) .await?; @@ -1200,7 +1203,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R check_output_step(&expected, |step| { async { - let series = step.measurement_series("name").start().await?; + let series = step.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) @@ -1280,7 +1283,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R // check_output_step(&expected, |step| { // async { -// let series = step.measurement_series("name"); +// let series = step.add_measurement_series("name"); // series // .scope(|s| async { // s.add_measurement(60.into()).await?; @@ -1338,7 +1341,7 @@ async fn test_config_builder_with_file() -> Result<()> { .start() .await?; - run.error_with_msg("symptom", "Error message").await?; + run.add_error_with_msg("symptom", "Error message").await?; run.end(TestStatus::Complete, TestResult::Pass).await?; From 83eabea14b364404a0f2404e9b96cb4cece5ef7c Mon Sep 17 00:00:00 2001 From: mimir-d Date: Mon, 7 Oct 2024 00:01:27 +0100 Subject: [PATCH 38/96] replace unnecessary serde_json::Maps with std - this json impl was leaking into the spec definition - replace the maps with BTreeMap and the serde_json::Value with tv::Value (which is a type alias, but we control it) Signed-off-by: mimir-d --- Cargo.lock | 7 ++++ Cargo.toml | 1 + src/output/config.rs | 6 +++ src/output/dut.rs | 18 ++++---- src/output/emitter.rs | 17 ++++---- src/output/error.rs | 9 ++-- src/output/measure.rs | 97 +++++++++++++++++++++---------------------- src/output/run.rs | 33 +++++++-------- src/output/step.rs | 7 +++- src/spec.rs | 28 +++++++------ 10 files changed, 123 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ec3f2d2..9969944 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,12 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "memchr" version = "2.7.4" @@ -519,6 +525,7 @@ dependencies = [ "chrono", "chrono-tz", "futures", + "maplit", "predicates", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 70fec41..4dd9351 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ edition = "2021" async-trait = "0.1.83" chrono = "0.4.38" chrono-tz = "0.10.0" +maplit = "1.0.2" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" thiserror = "1.0.64" diff --git a/src/output/config.rs b/src/output/config.rs index ca421a1..c765fea 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -106,6 +106,12 @@ impl TimestampProvider for ConfiguredTzProvider { pub struct NullTimestampProvider {} +impl NullTimestampProvider { + // warn: linter is wrong here, this is used in a serde_json::json! block + #[allow(dead_code)] + pub const FORMATTED: &str = "1970-01-01T00:00:00.000Z"; +} + impl TimestampProvider for NullTimestampProvider { fn now(&self) -> chrono::DateTime { chrono::DateTime::from_timestamp_nanos(0).with_timezone(&chrono_tz::UTC) diff --git a/src/output/dut.rs b/src/output/dut.rs index b4d6c20..d9504cc 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -4,6 +4,10 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use maplit::{btreemap, convert_args}; +use std::collections::BTreeMap; + +use crate::output as tv; use crate::spec; #[derive(Default, Debug, Clone, PartialEq)] @@ -13,7 +17,7 @@ pub struct DutInfo { platform_infos: Option>, software_infos: Option>, hardware_infos: Option>, - metadata: Option>, + metadata: Option>, } impl DutInfo { @@ -52,7 +56,7 @@ pub struct DutInfoBuilder { platform_infos: Option>, software_infos: Option>, hardware_infos: Option>, - metadata: Option>, + metadata: Option>, } impl DutInfoBuilder { @@ -104,17 +108,15 @@ impl DutInfoBuilder { self } - pub fn add_metadata(mut self, key: &str, value: serde_json::Value) -> DutInfoBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { self.metadata = match self.metadata { Some(mut metadata) => { metadata.insert(key.to_string(), value.clone()); Some(metadata) } - None => { - let mut metadata = serde_json::Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } + None => Some(convert_args!(btreemap!( + key => value + ))), }; self } diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 7c9cc7e..21a2fa7 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -66,7 +66,7 @@ impl JsonEmitter { #[cfg(test)] mod tests { use anyhow::{anyhow, Result}; - use assert_json_diff::assert_json_include; + use assert_json_diff::assert_json_eq; use serde_json::json; use tokio::sync::Mutex; @@ -79,7 +79,8 @@ mod tests { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 0 + "sequenceNumber": 0, + "timestamp": config::NullTimestampProvider::FORMATTED, }); let buffer = Arc::new(Mutex::new(vec![])); @@ -98,7 +99,7 @@ mod tests { let deserialized = serde_json::from_str::( buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, )?; - assert_json_include!(actual: deserialized, expected: expected); + assert_json_eq!(deserialized, expected); Ok(()) } @@ -110,14 +111,16 @@ mod tests { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 0 + "sequenceNumber": 0, + "timestamp": config::NullTimestampProvider::FORMATTED, }); let expected_2 = json!({ "schemaVersion": { "major": spec::SPEC_VERSION.0, "minor": spec::SPEC_VERSION.1, }, - "sequenceNumber": 1 + "sequenceNumber": 1, + "timestamp": config::NullTimestampProvider::FORMATTED, }); let buffer = Arc::new(Mutex::new(vec![])); @@ -134,12 +137,12 @@ mod tests { let deserialized = serde_json::from_str::( buffer.lock().await.first().ok_or(anyhow!("no outputs"))?, )?; - assert_json_include!(actual: deserialized, expected: expected_1); + assert_json_eq!(deserialized, expected_1); let deserialized = serde_json::from_str::( buffer.lock().await.get(1).ok_or(anyhow!("no outputs"))?, )?; - assert_json_include!(actual: deserialized, expected: expected_2); + assert_json_eq!(deserialized, expected_2); Ok(()) } diff --git a/src/output/error.rs b/src/output/error.rs index 1fbe845..840ff13 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -83,6 +83,7 @@ impl ErrorBuilder { mod tests { use anyhow::Result; use assert_json_diff::assert_json_eq; + use serde_json::json; use super::*; use crate::output as tv; @@ -135,7 +136,7 @@ mod tests { #[test] fn test_error() -> Result<()> { - let expected_run = serde_json::json!({ + let expected_run = json!({ "message": "message", "softwareInfoIds": [ { @@ -153,7 +154,7 @@ mod tests { }, "symptom": "symptom" }); - let expected_step = serde_json::json!({ + let expected_step = json!({ "message": "message", "softwareInfoIds": [ { @@ -178,11 +179,11 @@ mod tests { .build(); let spec_error = error.to_artifact(); - let actual = serde_json::json!(spec_error); + let actual = json!(spec_error); assert_json_eq!(actual, expected_run); let spec_error = error.to_artifact(); - let actual = serde_json::json!(spec_error); + let actual = json!(spec_error); assert_json_eq!(actual, expected_step); Ok(()) diff --git a/src/output/measure.rs b/src/output/measure.rs index cb4f42c..8d996c3 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -4,11 +4,11 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::collections::BTreeMap; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; -use serde_json::Map; -use serde_json::Value; +use maplit::{btreemap, convert_args}; use crate::output as tv; use crate::spec; @@ -176,7 +176,7 @@ impl StartedMeasurementSeries { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: Value) -> Result<(), tv::OcptvError> { + pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), @@ -217,17 +217,20 @@ impl StartedMeasurementSeries { /// ``` pub async fn add_measurement_with_metadata( &self, - value: Value, - metadata: Vec<(&str, Value)>, + value: tv::Value, + metadata: Vec<(&str, tv::Value)>, ) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), value: value.clone(), timestamp: self.parent.emitter.timestamp_provider().now(), series_id: self.parent.start.series_id.clone(), - metadata: Some(Map::from_iter( - metadata.iter().map(|(k, v)| (k.to_string(), v.clone())), - )), + metadata: Some( + metadata + .iter() + .map(|(k, v)| (k.to_string(), v.clone())) + .collect(), + ), }; self.parent @@ -245,12 +248,12 @@ impl StartedMeasurementSeries { pub struct Validator { name: Option, validator_type: spec::ValidatorType, - value: Value, - metadata: Option>, + value: tv::Value, + metadata: Option>, } impl Validator { - pub fn builder(validator_type: spec::ValidatorType, value: Value) -> ValidatorBuilder { + pub fn builder(validator_type: spec::ValidatorType, value: tv::Value) -> ValidatorBuilder { ValidatorBuilder::new(validator_type, value) } pub fn to_spec(&self) -> spec::Validator { @@ -267,12 +270,12 @@ impl Validator { pub struct ValidatorBuilder { name: Option, validator_type: spec::ValidatorType, - value: Value, - metadata: Option>, + value: tv::Value, + metadata: Option>, } impl ValidatorBuilder { - fn new(validator_type: spec::ValidatorType, value: Value) -> Self { + fn new(validator_type: spec::ValidatorType, value: tv::Value) -> Self { ValidatorBuilder { validator_type, value: value.clone(), @@ -284,17 +287,15 @@ impl ValidatorBuilder { self.name = Some(value.to_string()); self } - pub fn add_metadata(mut self, key: &str, value: Value) -> ValidatorBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> ValidatorBuilder { self.metadata = match self.metadata { Some(mut metadata) => { metadata.insert(key.to_string(), value.clone()); Some(metadata) } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } + None => Some(convert_args!(btreemap!( + key => value, + ))), }; self } @@ -342,12 +343,12 @@ impl ValidatorBuilder { /// ``` pub struct Measurement { name: String, - value: Value, + value: tv::Value, unit: Option, validators: Option>, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: Option>, } impl Measurement { @@ -361,7 +362,7 @@ impl Measurement { /// /// let measurement = Measurement::new("name", 50.into()); /// ``` - pub fn new(name: &str, value: Value) -> Self { + pub fn new(name: &str, value: tv::Value) -> Self { Measurement { name: name.to_string(), value: value.clone(), @@ -392,7 +393,7 @@ impl Measurement { /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` - pub fn builder(name: &str, value: Value) -> MeasurementBuilder { + pub fn builder(name: &str, value: tv::Value) -> MeasurementBuilder { MeasurementBuilder::new(name, value) } @@ -451,12 +452,12 @@ impl Measurement { /// ``` pub struct MeasurementBuilder { name: String, - value: Value, + value: tv::Value, unit: Option, validators: Option>, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: Option>, } impl MeasurementBuilder { @@ -470,7 +471,7 @@ impl MeasurementBuilder { /// /// let builder = MeasurementBuilder::new("name", 50.into()); /// ``` - pub fn new(name: &str, value: Value) -> Self { + pub fn new(name: &str, value: tv::Value) -> Self { MeasurementBuilder { name: name.to_string(), value: value.clone(), @@ -553,15 +554,15 @@ impl MeasurementBuilder { /// let builder = /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementBuilder { match self.metadata { Some(ref mut metadata) => { metadata.insert(key.to_string(), value.clone()); } None => { - let mut entries = serde_json::Map::new(); - entries.insert(key.to_owned(), value); - self.metadata = Some(entries); + self.metadata = Some(convert_args!(btreemap!( + key => value, + ))); } }; self @@ -613,7 +614,7 @@ pub struct MeasurementSeriesStart { validators: Option>, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: Option>, } impl MeasurementSeriesStart { @@ -662,7 +663,7 @@ pub struct MeasurementSeriesStartBuilder { validators: Option>, hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: Option>, } impl MeasurementSeriesStartBuilder { @@ -704,17 +705,15 @@ impl MeasurementSeriesStartBuilder { self } - pub fn add_metadata(mut self, key: &str, value: Value) -> MeasurementSeriesStartBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesStartBuilder { self.metadata = match self.metadata { Some(mut metadata) => { metadata.insert(key.to_string(), value.clone()); Some(metadata) } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } + None => Some(convert_args!(btreemap!( + key => value, + ))), }; self } @@ -750,7 +749,7 @@ mod tests { #[test] fn test_measurement_as_test_step_descendant_to_artifact() -> Result<()> { let name = "name".to_owned(); - let value = Value::from(50); + let value = tv::Value::from(50); let measurement = Measurement::new(&name, value.clone()); let artifact = measurement.to_artifact(); @@ -773,15 +772,15 @@ mod tests { #[test] fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { let name = "name".to_owned(); - let value = Value::from(50000); + let value = tv::Value::from(50000); let hardware_info = HardwareInfo::builder("id", "name").build(); let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); let meta_key = "key"; - let meta_value = Value::from("value"); - let mut metadata = Map::new(); - metadata.insert(meta_key.to_string(), meta_value.clone()); - metadata.insert(meta_key.to_string(), meta_value.clone()); + let meta_value = tv::Value::from("value"); + let metadata = convert_args!(btreemap!( + meta_key => meta_value.clone(), + )); let subcomponent = Subcomponent::builder("name").build(); @@ -864,10 +863,10 @@ mod tests { validators: Some(vec![validator.to_spec(), validator2.to_spec()]), hardware_info: Some(hw_info.to_spec()), subcomponent: Some(subcomponent.to_spec()), - metadata: Some(serde_json::Map::from_iter([ - ("key".to_string(), "value".into()), - ("key2".to_string(), "value2".into()) - ])), + metadata: Some(convert_args!(btreemap!( + "key" => "value", + "key2" => "value2", + ))), } ); diff --git a/src/output/run.rs b/src/output/run.rs index 350a8d0..170b087 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -4,13 +4,14 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::collections::BTreeMap; use std::env; -use std::sync::atomic; -use std::sync::atomic::Ordering; -use std::sync::Arc; +use std::sync::{ + atomic::{self, Ordering}, + Arc, +}; -use serde_json::Map; -use serde_json::Value; +use maplit::{btreemap, convert_args}; use crate::output as tv; use crate::spec; @@ -32,10 +33,10 @@ pub struct TestRunOutcome { pub struct TestRun { name: String, version: String, - parameters: Map, + parameters: BTreeMap, dut: dut::DutInfo, command_line: String, - metadata: Option>, + metadata: Option>, emitter: Arc, } @@ -156,9 +157,9 @@ pub struct TestRunBuilder { name: String, dut: dut::DutInfo, version: String, - parameters: Map, + parameters: BTreeMap, command_line: String, - metadata: Option>, + metadata: Option>, config: Option, } @@ -168,7 +169,7 @@ impl TestRunBuilder { name: name.to_string(), dut: dut.clone(), version: version.to_string(), - parameters: Map::new(), + parameters: BTreeMap::new(), command_line: env::args().collect::>()[1..].join(" "), metadata: None, config: None, @@ -187,7 +188,7 @@ impl TestRunBuilder { /// .add_parameter("param1", "value1".into()) /// .build(); /// ``` - pub fn add_parameter(mut self, key: &str, value: Value) -> TestRunBuilder { + pub fn add_parameter(mut self, key: &str, value: tv::Value) -> TestRunBuilder { self.parameters.insert(key.to_string(), value.clone()); self } @@ -239,17 +240,15 @@ impl TestRunBuilder { /// .add_metadata("meta1", "value1".into()) /// .build(); /// ``` - pub fn add_metadata(mut self, key: &str, value: Value) -> TestRunBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> TestRunBuilder { self.metadata = match self.metadata { Some(mut metadata) => { metadata.insert(key.to_string(), value.clone()); Some(metadata) } - None => { - let mut metadata = Map::new(); - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } + None => Some(convert_args!(btreemap!( + key => value, + ))), }; self } diff --git a/src/output/step.rs b/src/output/step.rs index d2834d3..31a180b 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -4,7 +4,6 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use serde_json::Value; use std::io; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; @@ -377,7 +376,11 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, name: &str, value: Value) -> Result<(), tv::OcptvError> { + pub async fn add_measurement( + &self, + name: &str, + value: tv::Value, + ) -> Result<(), tv::OcptvError> { let measurement = measure::Measurement::new(name, value); self.step diff --git a/src/spec.rs b/src/spec.rs index b6ce19f..7550ba6 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -4,11 +4,13 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::collections::BTreeMap; + use chrono::DateTime; use serde::Deserialize; use serde::Serialize; -use serde_json::Map; -use serde_json::Value; + +use crate::output as tv; pub const SPEC_VERSION: (i8, i8) = (2, 0); @@ -268,14 +270,14 @@ pub struct TestRunStart { pub command_line: String, #[serde(rename = "parameters")] - pub parameters: Map, + pub parameters: BTreeMap, #[serde(rename = "dutInfo")] pub dut_info: DutInfo, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `dutInfo` spec object. @@ -307,7 +309,7 @@ pub struct DutInfo { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `platformInfo` spec object. @@ -570,7 +572,7 @@ pub struct Measurement { pub name: String, #[serde(rename = "value")] - pub value: Value, + pub value: tv::Value, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "unit")] @@ -590,7 +592,7 @@ pub struct Measurement { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `validator` spec object. @@ -609,11 +611,11 @@ pub struct Validator { pub validator_type: ValidatorType, #[serde(rename = "value")] - pub value: Value, + pub value: tv::Value, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `subcomponent` spec object. @@ -676,7 +678,7 @@ pub struct MeasurementSeriesStart { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `measurementSeriesEnd` spec object. @@ -706,7 +708,7 @@ pub struct MeasurementSeriesElement { pub index: u64, #[serde(rename = "value")] - pub value: Value, + pub value: tv::Value, #[serde(with = "rfc3339_format")] pub timestamp: DateTime, @@ -716,7 +718,7 @@ pub struct MeasurementSeriesElement { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `diagnosis` spec object. @@ -777,7 +779,7 @@ pub struct File { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "metadata")] - pub metadata: Option>, + pub metadata: Option>, } /// Low-level model for the `extension` spec object. From 613bb165648ede5ba94ea83529fb6bd297a2f1aa Mon Sep 17 00:00:00 2001 From: mimir-d Date: Mon, 7 Oct 2024 01:25:32 +0100 Subject: [PATCH 39/96] fix content on Extension spec - remove the hardcoded ExtensionContentType, replace with arbitrary json - this needed adding a new method in step to output Extension artifacts, in order to satisfy coverage Signed-off-by: mimir-d --- src/output/mod.rs | 4 +- src/output/step.rs | 37 +++++++++++++++ src/spec.rs | 17 ++----- tests/output/runner.rs | 104 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 15 deletions(-) diff --git a/src/output/mod.rs b/src/output/mod.rs index be498b8..65c3435 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -40,5 +40,7 @@ pub use serde_json::Value; pub enum OcptvError { #[error("failed to write to output stream")] IoError(#[from] std::io::Error), - // other? + + #[error("failed to format input object")] + Format(Box), // opaque type so we don't leak impl } diff --git a/src/output/step.rs b/src/output/step.rs index 31a180b..c02577e 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -14,6 +14,8 @@ use crate::spec::{self, TestStepArtifactImpl}; use tv::measure::MeasurementSeries; use tv::{emitter, error, log, measure}; +use super::OcptvError; + /// A single test step in the scope of a [`TestRun`]. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts @@ -357,6 +359,41 @@ impl StartedTestStep { Ok(()) } + /// Emits an extension message; + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.add_step("step_name").start().await?; + /// + /// #[derive(serde::Serialize)] + /// struct Ext { i: u32 } + /// + /// step.extension("ext_name", Ext { i: 42 }).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn extension( + &self, + name: &str, + any: S, + ) -> Result<(), tv::OcptvError> { + let ext = TestStepArtifactImpl::Extension(spec::Extension { + name: name.to_owned(), + content: serde_json::to_value(&any).map_err(|e| OcptvError::Format(Box::new(e)))?, + }); + + self.step.emitter.emit(&ext).await?; + Ok(()) + } + /// Emits a Measurement message. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement diff --git a/src/spec.rs b/src/spec.rs index 7550ba6..8ccb114 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -81,19 +81,6 @@ pub enum SubcomponentType { Connector, } -// TODO: this should be better typed -#[derive(Debug, Serialize, PartialEq, Clone)] -pub enum ExtensionContentType { - #[serde(rename = "float")] - Float(f64), - #[serde(rename = "int")] - Int(i64), - #[serde(rename = "bool")] - Bool(bool), - #[serde(rename = "str")] - Str(String), -} - /// Outcome of a diagnosis operation. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json @@ -793,8 +780,10 @@ pub struct Extension { #[serde(rename = "name")] pub name: String, + // note: have to use a json specific here; alternative is to propagate up an E: Serialize, + // which polutes all of the types. Trait Serialize is also not object safe. #[serde(rename = "content")] - pub content: ExtensionContentType, + pub content: serde_json::Value, } #[cfg(test)] diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 178ddbf..8054538 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -14,6 +14,7 @@ use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; +use ocptv::output::OcptvError; use predicates::prelude::*; use serde_json::json; use tokio::sync::Mutex; @@ -1356,6 +1357,109 @@ async fn test_config_builder_with_file() -> Result<()> { Ok(()) } +#[tokio::test] +async fn test_step_with_extension() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "extension": { + "name": "extension", + "content": { + "@type": "TestExtension", + "stringField": "string", + "numberField": 42 + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + #[derive(serde::Serialize)] + struct Ext { + #[serde(rename = "@type")] + r#type: String, + #[serde(rename = "stringField")] + string_field: String, + #[serde(rename = "numberField")] + number_field: u32, + } + + check_output_step(&expected, |step| { + async { + step.extension( + "extension", + Ext { + r#type: "TestExtension".to_owned(), + string_field: "string".to_owned(), + number_field: 42, + }, + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_extension_which_fails() -> Result<()> { + #[derive(thiserror::Error, Debug, PartialEq)] + enum TestError { + #[error("test_error_fail")] + Fail, + } + + fn fail_serialize(_: &u32, _serializer: S) -> Result + where + S: serde::Serializer, + { + Err(serde::ser::Error::custom(TestError::Fail)) + } + + #[derive(serde::Serialize)] + struct Ext { + #[serde(serialize_with = "fail_serialize")] + i: u32, + } + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::builder("run_name", &dut, "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .build(), + ) + .build() + .start() + .await?; + let step = run.add_step("first step").start().await?; + + let result = step.extension("extension", Ext { i: 0 }).await; + + match result { + Err(OcptvError::Format(e)) => { + // `to_string` is the only way to check this error. `serde_json::Error` only + // implements source/cause for io errors, and this is a string + assert_eq!(e.to_string(), "test_error_fail"); + } + _ => panic!("unexpected ocptv error type"), + } + + Ok(()) +} + #[tokio::test] async fn test_testrun_instantiation_with_new() -> Result<()> { let expected = [ From 7b78cf0c00f3dd268b3175be4c9bdc727a406175 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 17:07:20 +0100 Subject: [PATCH 40/96] formalize the timestamp_provider mechanism - initially I thought that the Config object should be passed around (which I'm not yet convinced that this shouldnt be the case), but making an arbitrary choice here to limit the api surface - only expose the timestamp_provider methods from emitters - open to refactoring later, this is not public crate api Signed-off-by: mimir-d --- src/output/config.rs | 16 ++-------------- src/output/emitter.rs | 42 ++++++++++++++++++++++++++++++------------ src/output/step.rs | 17 ++++++++--------- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/output/config.rs b/src/output/config.rs index c765fea..35c0278 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -14,6 +14,7 @@ use crate::output::writer::{self, BufferWriter, FileWriter, StdoutWriter, Writer /// The configuration repository for the TestRun. pub struct Config { + // All fields are readable for any impl inside the crate. pub(crate) timestamp_provider: Box, pub(crate) writer: WriterType, } @@ -46,6 +47,7 @@ impl ConfigBuilder { } } + // TODO: docs for all these pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { self.timestamp_provider = Box::new(ConfiguredTzProvider { tz: timezone }); self @@ -103,17 +105,3 @@ impl TimestampProvider for ConfiguredTzProvider { chrono::Local::now().with_timezone(&self.tz) } } - -pub struct NullTimestampProvider {} - -impl NullTimestampProvider { - // warn: linter is wrong here, this is used in a serde_json::json! block - #[allow(dead_code)] - pub const FORMATTED: &str = "1970-01-01T00:00:00.000Z"; -} - -impl TimestampProvider for NullTimestampProvider { - fn now(&self) -> chrono::DateTime { - chrono::DateTime::from_timestamp_nanos(0).with_timezone(&chrono_tz::UTC) - } -} diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 21a2fa7..0d42aba 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -17,8 +17,7 @@ use crate::output::{ use crate::spec; pub struct JsonEmitter { - // HACK: public for tests, but this should come from config directly to where needed - pub(crate) timestamp_provider: Box, + timestamp_provider: Box, writer: writer::WriterType, seqno: Arc, } @@ -35,21 +34,26 @@ impl JsonEmitter { } } - fn serialize_artifact(&self, object: &spec::RootImpl) -> serde_json::Value { + fn incr_seqno(&self) -> u64 { + self.seqno.fetch_add(1, Ordering::AcqRel) + } + + fn serialize_artifact(&self, object: &spec::RootImpl) -> String { let root = spec::Root { artifact: object.clone(), timestamp: self.timestamp_provider.now(), seqno: self.incr_seqno(), }; - serde_json::json!(root) + + serde_json::json!(root).to_string() } - fn incr_seqno(&self) -> u64 { - self.seqno.fetch_add(1, Ordering::AcqRel) + pub fn timestamp_provider(&self) -> &(dyn config::TimestampProvider + Send + Sync + 'static) { + &*self.timestamp_provider } pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), io::Error> { - let s = self.serialize_artifact(object).to_string(); + let s = self.serialize_artifact(object); match &self.writer { WriterType::File(file) => file.write(&s).await?, @@ -72,6 +76,20 @@ mod tests { use super::*; + pub struct NullTimestampProvider {} + + impl NullTimestampProvider { + // warn: linter is wrong here, this is used in a serde_json::json! block + #[allow(dead_code)] + pub const FORMATTED: &str = "1970-01-01T00:00:00.000Z"; + } + + impl config::TimestampProvider for NullTimestampProvider { + fn now(&self) -> chrono::DateTime { + chrono::DateTime::from_timestamp_nanos(0).with_timezone(&chrono_tz::UTC) + } + } + #[tokio::test] async fn test_emit_using_buffer_writer() -> Result<()> { let expected = json!({ @@ -80,13 +98,13 @@ mod tests { "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 0, - "timestamp": config::NullTimestampProvider::FORMATTED, + "timestamp": NullTimestampProvider::FORMATTED, }); let buffer = Arc::new(Mutex::new(vec![])); let writer = writer::BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new( - Box::new(config::NullTimestampProvider {}), + Box::new(NullTimestampProvider {}), writer::WriterType::Buffer(writer), ); @@ -112,7 +130,7 @@ mod tests { "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 0, - "timestamp": config::NullTimestampProvider::FORMATTED, + "timestamp": NullTimestampProvider::FORMATTED, }); let expected_2 = json!({ "schemaVersion": { @@ -120,13 +138,13 @@ mod tests { "minor": spec::SPEC_VERSION.1, }, "sequenceNumber": 1, - "timestamp": config::NullTimestampProvider::FORMATTED, + "timestamp": NullTimestampProvider::FORMATTED, }); let buffer = Arc::new(Mutex::new(vec![])); let writer = writer::BufferWriter::new(buffer.clone()); let emitter = JsonEmitter::new( - Box::new(config::NullTimestampProvider {}), + Box::new(NullTimestampProvider {}), writer::WriterType::Buffer(writer), ); diff --git a/src/output/step.rs b/src/output/step.rs index c02577e..9455077 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -9,10 +9,9 @@ use std::sync::atomic::{self, Ordering}; use std::sync::Arc; use crate::output as tv; -use crate::spec::TestStepStart; -use crate::spec::{self, TestStepArtifactImpl}; +use crate::spec::{self, TestStepArtifactImpl, TestStepStart}; use tv::measure::MeasurementSeries; -use tv::{emitter, error, log, measure}; +use tv::{config, emitter, error, log, measure}; use super::OcptvError; @@ -31,7 +30,7 @@ impl TestStep { name: name.to_owned(), emitter: Arc::new(StepEmitter { step_id: id.to_owned(), - run_emitter, + emitter: run_emitter, }), } } @@ -528,7 +527,8 @@ impl StartedTestStep { pub struct StepEmitter { step_id: String, - run_emitter: Arc, + // root emitter + emitter: Arc, } impl StepEmitter { @@ -538,13 +538,12 @@ impl StepEmitter { // TODO: can these copies be avoided? artifact: object.clone(), }); - self.run_emitter.emit(&root).await?; + self.emitter.emit(&root).await?; Ok(()) } - // HACK: - pub fn timestamp_provider(&self) -> &dyn tv::config::TimestampProvider { - &*self.run_emitter.timestamp_provider + pub fn timestamp_provider(&self) -> &(dyn config::TimestampProvider + Send + Sync + 'static) { + self.emitter.timestamp_provider() } } From ca523997fc5cf8508f2fcb641175b6c0bec68c0e Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 19:04:13 +0100 Subject: [PATCH 41/96] enable scoped runs - guard this with a feature flag so users optin, since the need to use a boxed future is due to a rust syntax limitation (cannot capture lifetimes in async blocks RPIT), and this implies a stack allocation - re-add `scope` to StartedTestRun Signed-off-by: mimir-d --- Cargo.toml | 4 +++ src/output/run.rs | 80 ++++++++++++++++++++++-------------------- tests/output/runner.rs | 80 +++++++++++++++++++++++------------------- 3 files changed, 88 insertions(+), 76 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4dd9351..8d37fbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,10 +8,14 @@ repository = "https://github.com/opencomputeproject/ocp-diag-core-rust" license = "MIT" edition = "2021" +[features] +boxed-scopes = [] + [dependencies] async-trait = "0.1.83" chrono = "0.4.38" chrono-tz = "0.10.0" +futures = "0.3.30" maplit = "1.0.2" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" diff --git a/src/output/run.rs b/src/output/run.rs index 170b087..c6fb5d6 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#[cfg(feature = "boxed-scopes")] +use futures::future::BoxFuture; use std::collections::BTreeMap; use std::env; use std::sync::{ @@ -111,45 +113,45 @@ impl TestRun { Ok(StartedTestRun::new(self)) } - // disabling this for the moment so we don't publish api that's unusable. - // see: https://github.com/rust-lang/rust/issues/70263 - // - // /// Builds a scope in the [`TestRun`] object, taking care of starting and - // /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`TestRun`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - // /// run.scope(|r| async { - // /// r.add_log(LogSeverity::Info, "First message").await?; - // /// Ok(TestRunOutcome { - // /// status: TestStatus::Complete, - // /// result: TestResult::Pass, - // /// }) - // /// }).await?; - // /// - // /// # Ok::<(), OcptvError>(()) - // /// # }); - // /// ``` - // pub async fn scope(self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // for<'a> F: Fut2<'a, R>, - // { - // let run = self.start().await?; - // let outcome = func(&run).await?; - // run.end(outcome.status, outcome.result).await?; - - // Ok(()) - // } + /// Builds a scope in the [`TestRun`] object, taking care of starting and + /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`TestRun`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use futures::FutureExt; + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// run.scope(|r| { + /// async move { + /// r.add_log(LogSeverity::Info, "First message").await?; + /// Ok(TestRunOutcome { + /// status: TestStatus::Complete, + /// result: TestResult::Pass, + /// }) + /// }.boxed() + /// }).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + #[cfg(feature = "boxed-scopes")] + pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + where + F: FnOnce(&StartedTestRun) -> BoxFuture<'_, Result>, + { + let run = self.start().await?; + let outcome = func(&run).await?; + run.end(outcome.status, outcome.result).await?; + + Ok(()) + } } /// Builder for the [`TestRun`] object. diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 8054538..dd9b9a5 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -3,28 +3,24 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(unused_imports)] -use std::fs; use std::sync::Arc; use anyhow::Result; -use assert_fs::prelude::*; + use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; -use ocptv::output::OcptvError; -use predicates::prelude::*; use serde_json::json; use tokio::sync::Mutex; use ocptv::output as tv; +use ocptv::output::OcptvError; use tv::{ Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, SoftwareInfo, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, - TestRunBuilder, TestRunOutcome, TestStatus, TestStep, TimestampProvider, Validator, - ValidatorType, + TestRunBuilder, TestRunOutcome, TestStatus, TimestampProvider, Validator, ValidatorType, }; const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); @@ -336,39 +332,45 @@ async fn test_testrun_with_error_with_details() -> Result<()> { .await } -// #[tokio::test] -// async fn test_testrun_with_scope() -> Result<()> { -// let expected = [ -// json_schema_version(), -// json_run_default_start(), -// json!({ -// "testRunArtifact": { -// "log": { -// "message": "First message", -// "severity": "INFO" -// } -// }, -// "sequenceNumber": 2 -// }), -// json_run_pass(3), -// ]; +// #[cfg(any(test, feature = "boxed-scopes"))] +#[tokio::test] +async fn test_testrun_with_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "First message", + "severity": "INFO" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; -// check_output(&expected, |run_builder| async { -// let run = run_builder.build(); + check_output(&expected, |run_builder| async { + let run = run_builder.build(); -// run.scope(|r| async { -// r.add_log(LogSeverity::Info, "First message").await?; -// Ok(TestRunOutcome { -// status: TestStatus::Complete, -// result: TestResult::Pass, -// }) -// }) -// .await?; + run.scope(|r| { + async move { + r.add_log(LogSeverity::Info, "First message").await?; -// Ok(()) -// }) -// .await -// } + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) + }) + .await +} #[tokio::test] async fn test_testrun_with_step() -> Result<()> { @@ -1308,6 +1310,10 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R #[cfg(coverage)] #[tokio::test] async fn test_config_builder_with_file() -> Result<()> { + use assert_fs::prelude::*; + use predicates::prelude::*; + use std::fs; + let expected = [ json_schema_version(), json_run_default_start(), From 31d72faa600589c62b68b999ee40f9a8888b8199 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 19:12:40 +0100 Subject: [PATCH 42/96] add `scope` for test step Signed-off-by: mimir-d --- src/output/step.rs | 81 +++++++++++++++++++++++------------------- tests/output/runner.rs | 80 ++++++++++++++++++++++------------------- 2 files changed, 87 insertions(+), 74 deletions(-) diff --git a/src/output/step.rs b/src/output/step.rs index 9455077..35fcd09 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -8,6 +8,9 @@ use std::io; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; +#[cfg(feature = "boxed-scopes")] +use futures::future::BoxFuture; + use crate::output as tv; use crate::spec::{self, TestStepArtifactImpl, TestStepStart}; use tv::measure::MeasurementSeries; @@ -64,43 +67,47 @@ impl TestStep { }) } - // /// Builds a scope in the [`TestStep`] object, taking care of starting and - // /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`TestStep`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - // /// - // /// let step = run.add_step("first step")?; - // /// step.scope(|s| async { - // /// s.add_log( - // /// LogSeverity::Info, - // /// "This is a log message with INFO severity", - // /// ).await?; - // /// Ok(TestStatus::Complete) - // /// }).await?; - // /// - // /// # Ok::<(), OcptvError>(()) - // /// # }); - // /// ``` - // pub async fn scope<'a, F, R>(&'a self, func: F) -> Result<(), emitters::WriterError> - // where - // R: Future>, - // F: std::ops::FnOnce(&'a TestStep) -> R, - // { - // self.start().await?; - // let status = func(self).await?; - // self.end(status).await?; - // Ok(()) - // } + /// Builds a scope in the [`TestStep`] object, taking care of starting and + /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`TestStep`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use futures::FutureExt; + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.add_step("first step"); + /// step.scope(|s| { + /// async move { + /// s.add_log( + /// LogSeverity::Info, + /// "This is a log message with INFO severity", + /// ).await?; + /// Ok(TestStatus::Complete) + /// }.boxed() + /// }).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + #[cfg(feature = "boxed-scopes")] + pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + where + F: FnOnce(&StartedTestStep) -> BoxFuture<'_, Result>, + { + let step = self.start().await?; + let status = func(&step).await?; + step.end(status).await?; + + Ok(()) + } } pub struct StartedTestStep { diff --git a/tests/output/runner.rs b/tests/output/runner.rs index dd9b9a5..780141f 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -332,7 +332,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { .await } -// #[cfg(any(test, feature = "boxed-scopes"))] +#[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_testrun_with_scope() -> Result<()> { let expected = [ @@ -573,43 +573,49 @@ async fn test_testrun_step_error_with_details() -> Result<()> { .await } -// #[tokio::test] -// async fn test_testrun_step_scope_log() -> Result<()> { -// let expected = [ -// json_schema_version(), -// json_run_default_start(), -// json_step_default_start(), -// json!({ -// "sequenceNumber": 3, -// "testStepArtifact": { -// "log": { -// "message": "This is a log message with INFO severity", -// "severity": "INFO" -// } -// } -// }), -// json_step_complete(4), -// json_run_pass(5), -// ]; +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_testrun_step_scope_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; -// check_output_run(&expected, |run| { -// async { -// run.step("first step") -// .start() -// .scope(|s| async { -// s.add_log( -// LogSeverity::Info, -// "This is a log message with INFO severity", -// ) -// .await?; -// Ok(TestStatus::Complete) -// }) -// .await -// } -// .boxed() -// }) -// .await -// } + check_output_run(&expected, |run| { + async { + run.add_step("first step") + .scope(|s| { + async move { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(TestStatus::Complete) + } + .boxed() + }) + .await + } + .boxed() + }) + .await +} #[tokio::test] async fn test_step_with_measurement() -> Result<()> { From 5df7ce9eef24e89a47282aec66a1f0accb764d8e Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 19:36:07 +0100 Subject: [PATCH 43/96] add `scope` for measurement series Signed-off-by: mimir-d --- src/output/measure.rs | 81 ++++++++++--------- src/output/step.rs | 2 +- tests/output/runner.rs | 171 ++++++++++++++++++++++------------------- 3 files changed, 138 insertions(+), 116 deletions(-) diff --git a/src/output/measure.rs b/src/output/measure.rs index 8d996c3..cd80723 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -8,6 +8,8 @@ use std::collections::BTreeMap; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; +#[cfg(feature = "boxed-scopes")] +use futures::future::BoxFuture; use maplit::{btreemap, convert_args}; use crate::output as tv; @@ -73,44 +75,47 @@ impl MeasurementSeries { }) } - // /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - // /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. - // /// After the scope is constructed, additional objects may be added to it. - // /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees - // /// all the messages are emitted between the start and end messages, the order - // /// is respected and no messages is lost. - // /// - // /// # Examples - // /// - // /// ```rust - // /// # tokio_test::block_on(async { - // /// # use ocptv::output::*; - // /// - // /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; - // /// let step = run.add_step("step_name").start().await?; - // /// - // /// let series = step.add_measurement_series("name"); - // /// series.start().await?; - // /// series.scope(|s| async { - // /// s.add_measurement(60.into()).await?; - // /// s.add_measurement(70.into()).await?; - // /// s.add_measurement(80.into()).await?; - // /// Ok(()) - // /// }).await?; - // /// - // /// # Ok::<(), OcptvError>(()) - // /// # }); - // /// ``` - // pub async fn scope<'s, F, R>(&'s self, func: F) -> Result<(), tv::OcptvError> - // where - // R: Future>, - // F: std::ops::FnOnce(&'s MeasurementSeries) -> R, - // { - // self.start().await?; - // func(self).await?; - // self.end().await?; - // Ok(()) - // } + /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and + /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// After the scope is constructed, additional objects may be added to it. + /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees + /// all the messages are emitted between the start and end messages, the order + /// is respected and no messages is lost. + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use futures::FutureExt; + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.add_step("step_name").start().await?; + /// + /// let series = step.add_measurement_series("name"); + /// series.scope(|s| { + /// async move { + /// s.add_measurement(60.into()).await?; + /// s.add_measurement(70.into()).await?; + /// s.add_measurement(80.into()).await?; + /// Ok(()) + /// }.boxed() + /// }).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + #[cfg(feature = "boxed-scopes")] + pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + where + F: FnOnce(&StartedMeasurementSeries) -> BoxFuture<'_, Result<(), tv::OcptvError>>, + { + let series = self.start().await?; + func(&series).await?; + series.end().await?; + + Ok(()) + } } pub struct StartedMeasurementSeries { diff --git a/src/output/step.rs b/src/output/step.rs index 35fcd09..d776fa2 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -70,7 +70,7 @@ impl TestStep { /// Builds a scope in the [`TestStep`] object, taking care of starting and /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usage for the [`TestStep`], since it guarantees + /// This is the preferred usaggste for the [`TestStep`], since it guarantees /// all the messages are emitted between the start and end messages, the order /// is respected and no messages is lost. /// diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 780141f..5ace293 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -1232,83 +1232,100 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R .await } -// #[tokio::test] -// async fn test_step_with_measurement_series_scope() -> Result<()> { -// let expected = [ -// json_schema_version(), -// json_run_default_start(), -// json_step_default_start(), -// json!({ -// "testStepArtifact": { -// "measurementSeriesStart": { -// "measurementSeriesId": "series_0", -// "name": "name" -// } -// }, -// "sequenceNumber": 3 -// }), -// json!({ -// "testStepArtifact": { -// "measurementSeriesElement": { -// "index": 0, -// "measurementSeriesId": "series_0", -// "value": 60 -// } -// }, -// "sequenceNumber": 4 -// }), -// json!({ -// "testStepArtifact": { -// "measurementSeriesElement": { -// "index": 1, -// "measurementSeriesId": "series_0", -// "value": 70 -// } -// }, -// "sequenceNumber": 5 -// }), -// json!({ -// "testStepArtifact": { -// "measurementSeriesElement": { -// "index": 2, -// "measurementSeriesId": "series_0", -// "value": 80 -// } -// }, -// "sequenceNumber": 6 -// }), -// json!({ -// "testStepArtifact": { -// "measurementSeriesEnd": { -// "measurementSeriesId": "series_0", -// "totalCount": 3 -// } -// }, -// "sequenceNumber": 7 -// }), -// json_step_complete(8), -// json_run_pass(9), -// ]; - -// check_output_step(&expected, |step| { -// async { -// let series = step.add_measurement_series("name"); -// series -// .scope(|s| async { -// s.add_measurement(60.into()).await?; -// s.add_measurement(70.into()).await?; -// s.add_measurement(80.into()).await?; - -// Ok(()) -// }) -// .await?; - -// Ok(()) -// } -// .boxed() -// }) -// .await -// } +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_step_with_measurement_series_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "measurementSeriesStart": { + "measurementSeriesId": "series_0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "series_0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "series_0", + "value": 70, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "series_0", + "value": 80, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "measurementSeriesEnd": { + "measurementSeriesId": "series_0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |step| { + async { + let series = step.add_measurement_series("name"); + series + .scope(|s| { + async move { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; + + Ok(()) + } + .boxed() + }) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} // reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot // disable the coverage itself, only run this test when in coverage mode because assert_fs From 688f652bef18e114beae5ccf2c111b367428136f Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 19:39:14 +0100 Subject: [PATCH 44/96] add cargo-hack check - since we introduced feature flags for the first time, use cargo-hack to test all possible combinations of them Signed-off-by: mimir-d --- .github/workflows/check.yaml | 14 +++++++++++++- .github/workflows/test.yaml | 4 +++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index 86ee7ba..ae2b71f 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -50,4 +50,16 @@ jobs: uses: giraffate/clippy-action@v1 with: reporter: 'github-pr-check' - github_token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + github_token: ${{ secrets.GITHUB_TOKEN }} + + hack: + runs-on: ubuntu-latest + name: ubuntu / stable / features + steps: + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: cargo hack + run: cargo hack --feature-powerset --no-dev-deps check diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d677081..0776c8a 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -32,6 +32,7 @@ jobs: toolchain: ${{ matrix.toolchain }} - name: cargo test --locked run: cargo test --locked --all-features + os-check: runs-on: ${{ matrix.os }} name: ${{ matrix.os }} / stable @@ -46,6 +47,7 @@ jobs: uses: dtolnay/rust-toolchain@stable - name: cargo test run: cargo test --locked --all-features --all-targets + coverage: runs-on: ubuntu-latest name: ubuntu / stable / coverage @@ -66,4 +68,4 @@ jobs: with: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} - env_vars: OS,RUSTVER \ No newline at end of file + env_vars: OS,RUSTVER From e10f846c084410480223396ed985c0948cb040f9 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 21:24:26 +0100 Subject: [PATCH 45/96] add some simple examples Signed-off-by: mimir-d --- examples/simple.rs | 106 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 examples/simple.rs diff --git a/examples/simple.rs b/examples/simple.rs new file mode 100644 index 0000000..2fcaf7a --- /dev/null +++ b/examples/simple.rs @@ -0,0 +1,106 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::{ + ocptv_log_debug, ocptv_log_info, + output::{self as tv}, +}; +use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; + +macro_rules! run_demo { + ($name: ident) => { + println!("{}", format!("{:->width$}", "", width = 80)); + println!("{}", stringify!($name)); + println!("{}", format!("{:->width$}", "", width = 80)); + + let _ = $name().await; + println!(); + }; +} + +/// Show that a run/step can be manually started and ended. +/// +/// The scope version should be preferred, as it makes it safer not to miss the end +/// artifacts in case of unhandled exceptions or code misuse. +async fn demo_no_scopes() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + let run = TestRun::builder("with dut", &dut, "1.0") + .build() + .start() + .await?; + + let step = run.add_step("step0").start().await?; + ocptv_log_debug!(step, "Some interesting message.").await?; + step.end(TestStatus::Complete).await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) +} + +/// Show a context-scoped run that automatically exits the whole func +/// because of the marker exception that triggers SKIP outcome. +async fn demo_scope_run_skip() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + TestRun::builder("with dut", &dut, "1.0") + .build() + .scope(|_r| { + async move { + // intentional short return + return Ok(TestRunOutcome { + status: TestStatus::Skip, + result: TestResult::NotApplicable, + }); + } + .boxed() + }) + .await?; + + Ok(()) +} + +/// Show a scoped run with scoped steps, everything starts at "with" time and +/// ends automatically when the block ends (regardless of unhandled exceptions). +async fn demo_scope_step_fail() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + TestRun::builder("with dut", &dut, "1.0") + .build() + .scope(|r| { + async move { + r.add_step("step0") + .scope(|s| { + async move { + ocptv_log_info!(s, "info log").await?; + Ok(TestStatus::Complete) + } + .boxed() + }) + .await?; + + r.add_step("step1") + .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} + +#[tokio::main] +async fn main() { + run_demo!(demo_no_scopes); + run_demo!(demo_scope_run_skip); + run_demo!(demo_scope_step_fail); +} From ff92327d302a49a279ef2a2dfecfcf0f3a173c2d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Tue, 8 Oct 2024 21:56:45 +0100 Subject: [PATCH 46/96] add example with dut and software info - fix the serialization of `spec::Error` where the field `software_infos` (json: `softwareInfoIds`) was incorrectly serialized as a full object, rather than just the ids - export `spec::SoftwareType` because it's needed as public api; make it non_exhaustive so we can add later variants Signed-off-by: mimir-d --- Cargo.lock | 190 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 1 + examples/simple.rs | 37 ++++++++- src/output/mod.rs | 4 +- src/spec.rs | 38 +++++++-- 5 files changed, 262 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9969944..f4b5837 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -132,6 +132,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.6.0" @@ -185,6 +191,7 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-targets", ] @@ -241,6 +248,51 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + [[package]] name = "difflib" version = "0.4.0" @@ -253,6 +305,12 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.9" @@ -278,6 +336,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "futures" version = "0.3.30" @@ -397,6 +461,24 @@ dependencies = [ "walkdir", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "iana-time-zone" version = "0.1.61" @@ -420,6 +502,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "ignore" version = "0.4.23" @@ -436,6 +524,28 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown 0.15.0", + "serde", +] + [[package]] name = "itoa" version = "1.0.11" @@ -496,6 +606,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -529,6 +645,7 @@ dependencies = [ "predicates", "serde", "serde_json", + "serde_with", "thiserror", "tokio", "tokio-test", @@ -609,6 +726,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "predicates" version = "3.1.2" @@ -767,6 +890,36 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "shlex" version = "1.3.0" @@ -788,6 +941,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.79" @@ -838,6 +997,37 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tokio" version = "1.40.0" diff --git a/Cargo.toml b/Cargo.toml index 8d37fbf..d5dc5c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ futures = "0.3.30" maplit = "1.0.2" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" +serde_with = "3.11.0" thiserror = "1.0.64" tokio = { version = "1.40.0", features = [ "rt", diff --git a/examples/simple.rs b/examples/simple.rs index 2fcaf7a..9da914c 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -11,7 +11,7 @@ use ocptv::{ ocptv_log_debug, ocptv_log_info, output::{self as tv}, }; -use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; +use tv::{DutInfo, SoftwareInfo, SoftwareType, TestResult, TestRun, TestRunOutcome, TestStatus}; macro_rules! run_demo { ($name: ident) => { @@ -98,9 +98,44 @@ async fn demo_scope_step_fail() -> Result<()> { Ok(()) } +/// Show outputting an error message, triggered by a specific software component of the DUT. +async fn demo_run_error_with_dut() -> Result<()> { + let swinfo = SoftwareInfo::builder("bmc", "bmc") + .software_type(SoftwareType::Firmware) + .version("2.5") + .build(); + let dut = DutInfo::builder("dut0") + .name("dut0.server.net") + .add_software_info(&swinfo) + .build(); + + TestRun::builder("with dut", &dut, "1.0") + .build() + .scope(|r| { + async move { + r.add_error_with_details( + &tv::Error::builder("power-fail") + .add_software_info(&swinfo) + .build(), + ) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} + #[tokio::main] async fn main() { run_demo!(demo_no_scopes); run_demo!(demo_scope_run_skip); run_demo!(demo_scope_step_fail); + run_demo!(demo_run_error_with_dut); } diff --git a/src/output/mod.rs b/src/output/mod.rs index 65c3435..db1ca3d 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -16,7 +16,9 @@ mod run; mod step; mod writer; -pub use crate::spec::{LogSeverity, TestResult, TestStatus, ValidatorType, SPEC_VERSION}; +pub use crate::spec::{ + LogSeverity, SoftwareType, TestResult, TestStatus, ValidatorType, SPEC_VERSION, +}; pub use config::{Config, ConfigBuilder, TimestampProvider}; pub use dut::{ DutInfo, DutInfoBuilder, HardwareInfo, HardwareInfoBuilder, PlatformInfo, PlatformInfoBuilder, diff --git a/src/spec.rs b/src/spec.rs index 8ccb114..2b22dc1 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use chrono::DateTime; use serde::Deserialize; use serde::Serialize; +use serde_with::serde_as; use crate::output as tv; @@ -18,13 +19,10 @@ mod rfc3339_format { use chrono::DateTime; use chrono::SecondsFormat; use serde::Deserialize; - use serde::Deserializer; - use serde::Serializer; - use serde::{self}; pub fn serialize(date: &DateTime, serializer: S) -> Result where - S: Serializer, + S: serde::Serializer, { let s = date.to_rfc3339_opts(SecondsFormat::Millis, true); serializer.serialize_str(&s) @@ -32,7 +30,7 @@ mod rfc3339_format { pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> where - D: Deserializer<'de>, + D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; let dt = DateTime::parse_from_rfc3339(&s).map_err(serde::de::Error::custom)?; @@ -40,6 +38,26 @@ mod rfc3339_format { } } +mod serialize_ids { + pub trait IdGetter { + fn id(&self) -> &str; + } + + pub struct IdFromGetter; + + impl serde_with::SerializeAs for IdFromGetter + where + T: IdGetter, + { + fn serialize_as(source: &T, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(source.id()) + } + } +} + #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum ValidatorType { @@ -152,6 +170,7 @@ pub enum LogSeverity { /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo/properties/softwareType #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareType")] +#[non_exhaustive] pub enum SoftwareType { #[serde(rename = "UNSPECIFIED")] Unspecified, @@ -342,6 +361,12 @@ pub struct SoftwareInfo { pub computer_system: Option, } +impl serialize_ids::IdGetter for SoftwareInfo { + fn id(&self) -> &str { + &self.id + } +} + /// Low-level model for the `hardwareInfo` spec object. /// Represents information of an enumerated or exercised hardware component of the DUT. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#hardwareinfo @@ -418,6 +443,7 @@ pub struct TestRunEnd { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/error.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error +#[serde_as] #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] pub struct Error { @@ -428,9 +454,9 @@ pub struct Error { #[serde(rename = "message")] pub message: Option, - // TODO: support this field during serialization to print only the id of SoftwareInfo struct #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "softwareInfoIds")] + #[serde_as(as = "Option>")] pub software_infos: Option>, #[serde(skip_serializing_if = "Option::is_none")] From a4d9252dc4742775f97bd96a6987d7e85a6372e9 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 10:50:44 +0100 Subject: [PATCH 47/96] add auto-numbering mechanism poc for dut infos - we should not require users to provide ids for software/hardware infos; refactor the dut api to allow for auto-numbering, while also leaving open the possibility of hardcoded ids - this unfortunately removes the dut builder, as we cannot have builder methods returning the builder itself and a new value (that contains the numering) - note that the only entity that knows the current numbering state is the dut itself, so it implies mutability (alternative would be a global counter, like GUIDs, but that makes everything more cumbersome) - next diff adds this numbering mechanism to the other dut infos Signed-off-by: mimir-d --- examples/simple.rs | 35 ++++-- src/output/dut.rs | 264 +++++++++++++++++++++++++---------------- src/output/error.rs | 50 ++++---- src/output/mod.rs | 5 +- src/output/run.rs | 9 +- src/output/step.rs | 8 +- tests/output/runner.rs | 201 ++++++++++++++++++------------- 7 files changed, 350 insertions(+), 222 deletions(-) diff --git a/examples/simple.rs b/examples/simple.rs index 9da914c..f53e67e 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -5,13 +5,18 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; +#[cfg(feature = "boxed-scopes")] use futures::FutureExt; +#[cfg(feature = "boxed-scopes")] +use ocptv::ocptv_log_info; use ocptv::{ - ocptv_log_debug, ocptv_log_info, + ocptv_log_debug, output::{self as tv}, }; -use tv::{DutInfo, SoftwareInfo, SoftwareType, TestResult, TestRun, TestRunOutcome, TestStatus}; +use tv::{DutInfo, TestResult, TestRun, TestStatus}; +#[cfg(feature = "boxed-scopes")] +use tv::{SoftwareInfo, SoftwareType, TestRunOutcome}; macro_rules! run_demo { ($name: ident) => { @@ -45,6 +50,7 @@ async fn demo_no_scopes() -> Result<()> { /// Show a context-scoped run that automatically exits the whole func /// because of the marker exception that triggers SKIP outcome. +#[cfg(feature = "boxed-scopes")] async fn demo_scope_run_skip() -> Result<()> { let dut = DutInfo::builder("dut0").build(); TestRun::builder("with dut", &dut, "1.0") @@ -66,6 +72,7 @@ async fn demo_scope_run_skip() -> Result<()> { /// Show a scoped run with scoped steps, everything starts at "with" time and /// ends automatically when the block ends (regardless of unhandled exceptions). +#[cfg(feature = "boxed-scopes")] async fn demo_scope_step_fail() -> Result<()> { let dut = DutInfo::builder("dut0").build(); TestRun::builder("with dut", &dut, "1.0") @@ -99,15 +106,15 @@ async fn demo_scope_step_fail() -> Result<()> { } /// Show outputting an error message, triggered by a specific software component of the DUT. +#[cfg(feature = "boxed-scopes")] async fn demo_run_error_with_dut() -> Result<()> { - let swinfo = SoftwareInfo::builder("bmc", "bmc") - .software_type(SoftwareType::Firmware) - .version("2.5") - .build(); - let dut = DutInfo::builder("dut0") - .name("dut0.server.net") - .add_software_info(&swinfo) - .build(); + let mut dut = DutInfo::builder("dut0").name("dut0.server.net").build(); + let sw_info = dut.add_software_info( + SoftwareInfo::builder("bmc") + .software_type(SoftwareType::Firmware) + .version("2.5") + .build(), + ); TestRun::builder("with dut", &dut, "1.0") .build() @@ -115,7 +122,7 @@ async fn demo_run_error_with_dut() -> Result<()> { async move { r.add_error_with_details( &tv::Error::builder("power-fail") - .add_software_info(&swinfo) + .add_software_info(&sw_info) .build(), ) .await?; @@ -135,7 +142,13 @@ async fn demo_run_error_with_dut() -> Result<()> { #[tokio::main] async fn main() { run_demo!(demo_no_scopes); + + #[cfg(feature = "boxed-scopes")] run_demo!(demo_scope_run_skip); + + #[cfg(feature = "boxed-scopes")] run_demo!(demo_scope_step_fail); + + #[cfg(feature = "boxed-scopes")] run_demo!(demo_run_error_with_dut); } diff --git a/src/output/dut.rs b/src/output/dut.rs index d9504cc..8f617f1 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -10,12 +10,20 @@ use std::collections::BTreeMap; use crate::output as tv; use crate::spec; +// TODO: docs +#[derive(Clone, Debug, PartialEq)] +pub enum Ident { + Auto, + Exact(String), +} + +/// TODO: docs #[derive(Default, Debug, Clone, PartialEq)] pub struct DutInfo { id: String, name: Option, platform_infos: Option>, - software_infos: Option>, + software_infos: Vec, hardware_infos: Option>, metadata: Option>, } @@ -29,6 +37,24 @@ impl DutInfo { DutInfoBuilder::new(id).build() } + pub fn add_software_info(&mut self, software_info: SoftwareInfo) -> DutSoftwareInfo { + let id = match &software_info.id { + Ident::Auto => format!("{}_sw_{}", self.id, self.software_infos.len()), + Ident::Exact(v) => v.to_owned(), + }; + + let info = DutSoftwareInfo { + id, + source: software_info, + }; + self.software_infos.push(info.clone()); + info + } + + pub fn software_info(&self, id: &str) -> Option<&DutSoftwareInfo> { + self.software_infos.iter().find(|si| si.id == id) + } + pub(crate) fn to_spec(&self) -> spec::DutInfo { spec::DutInfo { id: self.id.clone(), @@ -37,10 +63,15 @@ impl DutInfo { .platform_infos .clone() .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - software_infos: self - .software_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + software_infos: match self.software_infos.len() { + 0 => None, + _ => Some( + self.software_infos + .iter() + .map(|info| info.to_spec()) + .collect(), + ), + }, hardware_infos: self .hardware_infos .clone() @@ -54,7 +85,6 @@ pub struct DutInfoBuilder { id: String, name: Option, platform_infos: Option>, - software_infos: Option>, hardware_infos: Option>, metadata: Option>, } @@ -65,7 +95,6 @@ impl DutInfoBuilder { id: id.to_string(), name: None, platform_infos: None, - software_infos: None, hardware_infos: None, metadata: None, } @@ -86,16 +115,20 @@ impl DutInfoBuilder { self } - pub fn add_software_info(mut self, software_info: &SoftwareInfo) -> DutInfoBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.clone()); - Some(software_infos) - } - None => Some(vec![software_info.clone()]), - }; - self - } + // pub fn add_software_info(mut self, software_info: SoftwareInfo) -> DutInfoBuilder { + // let id = match &software_info.id { + // Ident::Auto => format!("{}_sw_{}", self.id, self.software_infos.len()), + // Ident::Exact(v) => v.to_owned(), + // }; + + // let info = DutSoftwareInfo { + // id, + // source: software_info, + // }; + // self.software_infos.push(info.clone()); + + // self + // } pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { self.hardware_infos = match self.hardware_infos { @@ -126,7 +159,7 @@ impl DutInfoBuilder { id: self.id, name: self.name, platform_infos: self.platform_infos, - software_infos: self.software_infos, + software_infos: vec![], hardware_infos: self.hardware_infos, metadata: self.metadata, } @@ -373,9 +406,9 @@ impl PlatformInfoBuilder { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone)] pub struct SoftwareInfo { - id: String, + id: tv::Ident, name: String, version: Option, revision: Option, @@ -384,25 +417,41 @@ pub struct SoftwareInfo { } impl SoftwareInfo { - pub fn builder(id: &str, name: &str) -> SoftwareInfoBuilder { - SoftwareInfoBuilder::new(id, name) + pub fn builder(name: &str) -> SoftwareInfoBuilder { + SoftwareInfoBuilder::new(name) } +} + +#[derive(Debug, Clone)] +pub struct DutSoftwareInfo { + id: String, + source: SoftwareInfo, +} + +impl DutSoftwareInfo { + pub(crate) fn to_spec(&self) -> spec::SoftwareInfo { + let src = &self.source; - pub fn to_spec(&self) -> spec::SoftwareInfo { spec::SoftwareInfo { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - software_type: self.software_type.clone(), - computer_system: self.computer_system.clone(), + id: self.id.to_owned(), + name: src.name.clone(), + version: src.version.clone(), + revision: src.revision.clone(), + software_type: src.software_type.clone(), + computer_system: src.computer_system.clone(), } } } +impl PartialEq for DutSoftwareInfo { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + #[derive(Debug)] pub struct SoftwareInfoBuilder { - id: String, + id: tv::Ident, name: String, version: Option, revision: Option, @@ -411,9 +460,9 @@ pub struct SoftwareInfoBuilder { } impl SoftwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { + fn new(name: &str) -> Self { SoftwareInfoBuilder { - id: id.to_string(), + id: Ident::Auto, name: name.to_string(), version: None, revision: None, @@ -421,18 +470,27 @@ impl SoftwareInfoBuilder { computer_system: None, } } + + pub fn id(mut self, value: tv::Ident) -> SoftwareInfoBuilder { + self.id = value; + self + } + pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { self.version = Some(value.to_string()); self } + pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { self.revision = Some(value.to_string()); self } + pub fn software_type(mut self, value: spec::SoftwareType) -> SoftwareInfoBuilder { self.software_type = Some(value); self } + pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { self.computer_system = Some(value.to_string()); self @@ -454,7 +512,7 @@ impl SoftwareInfoBuilder { mod tests { use super::*; use crate::spec; - use anyhow::{bail, Result}; + use anyhow::Result; #[test] fn test_dut_creation_from_builder_with_defaults() -> Result<()> { @@ -463,68 +521,68 @@ mod tests { Ok(()) } - #[test] - fn test_dut_builder() -> Result<()> { - let platform = PlatformInfo::builder("platform_info").build(); - let software = SoftwareInfo::builder("software_id", "name").build(); - let hardware = HardwareInfo::builder("hardware_id", "name").build(); - let dut = DutInfo::builder("1234") - .name("DUT") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_hardware_info(&hardware) - .add_hardware_info(&hardware) - .add_platform_info(&platform) - .add_platform_info(&platform) - .add_software_info(&software) - .add_software_info(&software) - .build(); - - let spec_dut = dut.to_spec(); - - assert_eq!(spec_dut.id, "1234"); - assert_eq!(spec_dut.name, Some("DUT".to_owned())); - - match spec_dut.metadata { - Some(m) => { - assert_eq!(m["key"], "value"); - assert_eq!(m["key2"], "value2"); - } - _ => bail!("metadata is empty"), - } - - match spec_dut.hardware_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "hardware_id"); - } - _ => bail!("hardware_infos is empty"), - }, - _ => bail!("hardware_infos is missing"), - } - - match spec_dut.software_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.id, "software_id"); - } - _ => bail!("software_infos is empty"), - }, - _ => bail!("software_infos is missing"), - } - - match spec_dut.platform_infos { - Some(infos) => match infos.first() { - Some(info) => { - assert_eq!(info.info, "platform_info"); - } - _ => bail!("platform_infos is empty"), - }, - _ => bail!("platform_infos is missing"), - } - - Ok(()) - } + // #[test] + // fn test_dut_builder() -> Result<()> { + // let platform = PlatformInfo::builder("platform_info").build(); + // let software = SoftwareInfo::builder("software_id", "name").build(); + // let hardware = HardwareInfo::builder("hardware_id", "name").build(); + // let dut = DutInfo::builder("1234") + // .name("DUT") + // .add_metadata("key", "value".into()) + // .add_metadata("key2", "value2".into()) + // .add_hardware_info(&hardware) + // .add_hardware_info(&hardware) + // .add_platform_info(&platform) + // .add_platform_info(&platform) + // .add_software_info(&software) + // .add_software_info(&software) + // .build(); + + // let spec_dut = dut.to_spec(); + + // assert_eq!(spec_dut.id, "1234"); + // assert_eq!(spec_dut.name, Some("DUT".to_owned())); + + // match spec_dut.metadata { + // Some(m) => { + // assert_eq!(m["key"], "value"); + // assert_eq!(m["key2"], "value2"); + // } + // _ => bail!("metadata is empty"), + // } + + // match spec_dut.hardware_infos { + // Some(infos) => match infos.first() { + // Some(info) => { + // assert_eq!(info.id, "hardware_id"); + // } + // _ => bail!("hardware_infos is empty"), + // }, + // _ => bail!("hardware_infos is missing"), + // } + + // match spec_dut.software_infos { + // Some(infos) => match infos.first() { + // Some(info) => { + // assert_eq!(info.id, "software_id"); + // } + // _ => bail!("software_infos is empty"), + // }, + // _ => bail!("software_infos is missing"), + // } + + // match spec_dut.platform_infos { + // Some(infos) => match infos.first() { + // Some(info) => { + // assert_eq!(info.info, "platform_info"); + // } + // _ => bail!("platform_infos is empty"), + // }, + // _ => bail!("platform_infos is missing"), + // } + + // Ok(()) + // } #[test] fn test_hardware_info() -> Result<()> { @@ -567,12 +625,16 @@ mod tests { #[test] fn test_software_info() -> Result<()> { - let info = SoftwareInfo::builder("software_id", "name") - .version("version") - .revision("revision") - .software_type(spec::SoftwareType::Application) - .computer_system("system") - .build(); + let mut dut = DutInfo::new("dut0"); + let info = dut.add_software_info( + SoftwareInfo::builder("name") + .id(Ident::Exact("software_id".to_owned())) + .version("version") + .revision("revision") + .software_type(spec::SoftwareType::Application) + .computer_system("system") + .build(), + ); let spec_swinfo = info.to_spec(); diff --git a/src/output/error.rs b/src/output/error.rs index 840ff13..48d71e9 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -47,10 +47,12 @@ impl ErrorBuilder { software_infos: None, } } + pub fn message(mut self, value: &str) -> ErrorBuilder { self.message = Some(value.to_string()); self } + pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { self.source_location = Some(spec::SourceLocation { file: file.to_string(), @@ -58,7 +60,8 @@ impl ErrorBuilder { }); self } - pub fn add_software_info(mut self, software_info: &dut::SoftwareInfo) -> ErrorBuilder { + + pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> ErrorBuilder { self.software_infos = match self.software_infos { Some(mut software_infos) => { software_infos.push(software_info.to_spec()); @@ -89,12 +92,16 @@ mod tests { use crate::output as tv; use crate::spec; use tv::dut; + use tv::Ident; #[test] fn test_error_output_as_test_run_descendant_to_artifact() -> Result<()> { + let mut dut = dut::DutInfo::new("dut0"); + let sw_info = dut.add_software_info(dut::SoftwareInfo::builder("name").build()); + let error = Error::builder("symptom") .message("") - .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .add_software_info(&sw_info) .source("", 1) .build(); @@ -114,9 +121,12 @@ mod tests { #[test] fn test_error_output_as_test_step_descendant_to_artifact() -> Result<()> { + let mut dut = dut::DutInfo::new("dut0"); + let sw_info = dut.add_software_info(dut::SoftwareInfo::builder("name").build()); + let error = Error::builder("symptom") .message("") - .add_software_info(&dut::SoftwareInfo::builder("id", "name").build()) + .add_software_info(&sw_info) .source("", 1) .build(); @@ -135,18 +145,12 @@ mod tests { } #[test] - fn test_error() -> Result<()> { + fn test_error_with_multiple_software() -> Result<()> { let expected_run = json!({ "message": "message", "softwareInfoIds": [ - { - "name": "name", - "softwareInfoId": "software_id", - }, - { - "name": "name", - "softwareInfoId": "software_id", - } + "software_id", + "software_id" ], "sourceLocation": { "file": "file.rs", @@ -157,25 +161,25 @@ mod tests { let expected_step = json!({ "message": "message", "softwareInfoIds": [ - { - "name": "name", - "softwareInfoId": "software_id", - }, - { - "name": "name", - "softwareInfoId": "software_id", - } + "software_id", + "software_id" ], "sourceLocation": {"file":"file.rs","line":1}, "symptom":"symptom" }); - let software = dut::SoftwareInfo::builder("software_id", "name").build(); + let mut dut = dut::DutInfo::new("dut0"); + let sw_info = dut.add_software_info( + dut::SoftwareInfo::builder("name") + .id(Ident::Exact("software_id".to_owned())) + .build(), + ); + let error = ErrorBuilder::new("symptom") .message("message") .source("file.rs", 1) - .add_software_info(&software) - .add_software_info(&software) + .add_software_info(&sw_info) + .add_software_info(&sw_info) .build(); let spec_error = error.to_artifact(); diff --git a/src/output/mod.rs b/src/output/mod.rs index db1ca3d..cd2e838 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -21,8 +21,9 @@ pub use crate::spec::{ }; pub use config::{Config, ConfigBuilder, TimestampProvider}; pub use dut::{ - DutInfo, DutInfoBuilder, HardwareInfo, HardwareInfoBuilder, PlatformInfo, PlatformInfoBuilder, - SoftwareInfo, SoftwareInfoBuilder, Subcomponent, SubcomponentBuilder, + DutInfo, DutInfoBuilder, DutSoftwareInfo, HardwareInfo, HardwareInfoBuilder, Ident, + PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, + SubcomponentBuilder, }; pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; diff --git a/src/output/run.rs b/src/output/run.rs index c6fb5d6..9ce83e9 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -288,6 +288,7 @@ impl StartedTestRun { step_seqno: atomic::AtomicU64::new(0), } } + /// Ends the test run. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend @@ -468,14 +469,18 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let mut dut = DutInfo::new("my_dut"); + /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); + /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; + /// /// run.add_error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .add_software_info(&sw_info) /// .build(), /// ).await?; + /// /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) diff --git a/src/output/step.rs b/src/output/step.rs index d776fa2..84049e7 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -70,7 +70,7 @@ impl TestStep { /// Builds a scope in the [`TestStep`] object, taking care of starting and /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. /// After the scope is constructed, additional objects may be added to it. - /// This is the preferred usaggste for the [`TestStep`], since it guarantees + /// This is the preferred usage for the [`TestStep`], since it guarantees /// all the messages are emitted between the start and end messages, the order /// is respected and no messages is lost. /// @@ -341,14 +341,16 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let mut dut = DutInfo::new("my_dut"); + /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); + /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_error_with_details( /// &Error::builder("symptom") /// .message("Error message") /// .source("file", 1) - /// .add_software_info(&SoftwareInfo::builder("id", "name").build()) + /// .add_software_info(&sw_info) /// .build(), /// ).await?; /// step.end(TestStatus::Complete).await?; diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 5ace293..5b34771 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -17,10 +17,13 @@ use tokio::sync::Mutex; use ocptv::output as tv; use ocptv::output::OcptvError; +#[cfg(feature = "boxed-scopes")] +use tv::TestRunOutcome; use tv::{ - Config, DutInfo, Error, HardwareInfo, Log, LogSeverity, Measurement, MeasurementSeriesStart, - SoftwareInfo, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, - TestRunBuilder, TestRunOutcome, TestStatus, TimestampProvider, Validator, ValidatorType, + Config, DutInfo, Error, HardwareInfo, Ident, Log, LogSeverity, Measurement, + MeasurementSeriesStart, SoftwareInfo, SoftwareType, StartedTestRun, StartedTestStep, + Subcomponent, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, Validator, + ValidatorType, }; const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); @@ -52,7 +55,13 @@ fn json_run_default_start() -> serde_json::Value { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], }, "name": "run_name", "parameters": {}, @@ -108,10 +117,18 @@ fn json_step_complete(seqno: i32) -> serde_json::Value { async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> where R: Future>, - F: FnOnce(TestRunBuilder) -> R, + F: FnOnce(TestRunBuilder, DutInfo) -> R, { let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); + let mut dut = DutInfo::builder("dut_id").build(); + dut.add_software_info( + SoftwareInfo::builder("ubuntu") + .id(Ident::Exact("sw0".to_owned())) // name is important as fixture + .version("22") + .software_type(SoftwareType::System) + .build(), + ); + let run_builder = TestRun::builder("run_name", &dut, "1.0").config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) @@ -120,7 +137,7 @@ where ); // run the main test closure - test_fn(run_builder).await?; + test_fn(run_builder, dut).await?; for (i, entry) in buffer.lock().await.iter().enumerate() { let value = serde_json::from_str::(entry)?; @@ -132,13 +149,13 @@ where async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestRun) -> BoxFuture<'a, Result<(), tv::OcptvError>> + Send, + F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, { - check_output(expected, |run_builder| async { + check_output(expected, |run_builder, dutinfo| async move { let run = run_builder.build(); let run = run.start().await?; - test_fn(&run).await?; + test_fn(&run, dutinfo).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) @@ -148,13 +165,13 @@ where async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestStep) -> BoxFuture<'a, Result<(), tv::OcptvError>>, + F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, { - check_output(expected, |run_builder| async { + check_output(expected, |run_builder, dutinfo| async move { let run = run_builder.build().start().await?; let step = run.add_step("first step").start().await?; - test_fn(&step).await?; + test_fn(&step, dutinfo).await?; step.end(TestStatus::Complete).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -172,7 +189,7 @@ async fn test_testrun_start_and_end() -> Result<()> { json_run_pass(2), ]; - check_output_run(&expected, |_| async { Ok(()) }.boxed()).await + check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await } #[tokio::test] @@ -193,9 +210,9 @@ async fn test_testrun_with_log() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |run| { + check_output_run(&expected, |r, _| { async { - run.add_log( + r.add_log( LogSeverity::Info, "This is a log message with INFO severity", ) @@ -228,9 +245,9 @@ async fn test_testrun_with_log_with_details() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |run| { + check_output_run(&expected, |r, _| { async { - run.add_log_with_details( + r.add_log_with_details( &Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) @@ -260,8 +277,8 @@ async fn test_testrun_with_error() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |run| { - async { run.add_error("symptom").await }.boxed() + check_output_run(&expected, |r, _| { + async { r.add_error("symptom").await }.boxed() }) .await } @@ -284,8 +301,8 @@ async fn test_testrun_with_error_with_message() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |run| { - async { run.add_error_with_msg("symptom", "Error message").await }.boxed() + check_output_run(&expected, |r, _| { + async { r.add_error_with_msg("symptom", "Error message").await }.boxed() }) .await } @@ -299,10 +316,9 @@ async fn test_testrun_with_error_with_details() -> Result<()> { "testRunArtifact": { "error": { "message": "Error message", - "softwareInfoIds": [{ - "name": "name", - "softwareInfoId": "id" - }], + "softwareInfoIds": [ + "sw0" + ], "sourceLocation": { "file": "file", "line": 1 @@ -316,13 +332,13 @@ async fn test_testrun_with_error_with_details() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |run| { - async { - run.add_error_with_details( + check_output_run(&expected, |r, dut| { + async move { + r.add_error_with_details( &Error::builder("symptom") .message("Error message") .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .add_software_info(dut.software_info("sw0").unwrap()) // must exist .build(), ) .await @@ -351,7 +367,7 @@ async fn test_testrun_with_scope() -> Result<()> { json_run_pass(3), ]; - check_output(&expected, |run_builder| async { + check_output(&expected, |run_builder, _| async { let run = run_builder.build(); run.scope(|r| { @@ -382,7 +398,7 @@ async fn test_testrun_with_step() -> Result<()> { json_run_pass(4), ]; - check_output_step(&expected, |_| async { Ok(()) }.boxed()).await + check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await } #[tokio::test] @@ -406,9 +422,9 @@ async fn test_testrun_step_log() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.add_log( + s.add_log( LogSeverity::Info, "This is a log message with INFO severity", ) @@ -446,9 +462,9 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.add_log_with_details( + s.add_log_with_details( &Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) @@ -483,9 +499,9 @@ async fn test_testrun_step_error() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.add_error("symptom").await?; + s.add_error("symptom").await?; Ok(()) } @@ -515,9 +531,9 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.add_error_with_msg("symptom", "Error message").await?; + s.add_error_with_msg("symptom", "Error message").await?; Ok(()) } @@ -537,10 +553,9 @@ async fn test_testrun_step_error_with_details() -> Result<()> { "testStepId": "step_0", "error": { "message": "Error message", - "softwareInfoIds": [{ - "name": "name", - "softwareInfoId": "id" - }], + "softwareInfoIds": [ + "sw0" + ], "sourceLocation": { "file": "file", "line": 1 @@ -555,13 +570,13 @@ async fn test_testrun_step_error_with_details() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { - async { - step.add_error_with_details( + check_output_step(&expected, |s, dut| { + async move { + s.add_error_with_details( &Error::builder("symptom") .message("Error message") .source("file", 1) - .add_software_info(&SoftwareInfo::builder("id", "name").build()) + .add_software_info(dut.software_info("sw0").unwrap()) .build(), ) .await?; @@ -595,9 +610,9 @@ async fn test_testrun_step_scope_log() -> Result<()> { json_run_pass(5), ]; - check_output_run(&expected, |run| { + check_output_run(&expected, |r, _| { async { - run.add_step("first step") + r.add_step("first step") .scope(|s| { async move { s.add_log( @@ -638,9 +653,9 @@ async fn test_step_with_measurement() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.add_measurement("name", 50.into()).await?; + s.add_measurement("name", 50.into()).await?; Ok(()) } @@ -681,7 +696,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { let measurement = Measurement::builder("name", 50.into()) .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -689,7 +704,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { .add_metadata("key", "value".into()) .subcomponent(&Subcomponent::builder("name").build()) .build(); - step.add_measurement_with_details(&measurement).await?; + s.add_measurement_with_details(&measurement).await?; Ok(()) } @@ -730,9 +745,9 @@ async fn test_step_with_measurement_series() -> Result<()> { json_run_pass(6), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; series.end().await?; Ok(()) @@ -796,12 +811,12 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_run_pass(8), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; series.end().await?; - let series_2 = step.add_measurement_series("name").start().await?; + let series_2 = s.add_measurement_series("name").start().await?; series_2.end().await?; Ok(()) @@ -842,9 +857,9 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { json_run_pass(6), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step + let series = s .add_measurement_series_with_details(MeasurementSeriesStart::new( "name", "series_id", @@ -906,9 +921,9 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R json_run_pass(6), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step + let series = s .add_measurement_series_with_details( MeasurementSeriesStart::builder("name", "series_id") .add_metadata("key", "value".into()) @@ -973,9 +988,9 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_run_pass(7), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; series.add_measurement(60.into()).await?; series.end().await?; @@ -1057,9 +1072,9 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { json_run_pass(9), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series.add_measurement(60.into()).await?; series.add_measurement(70.into()).await?; @@ -1121,9 +1136,9 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> json_run_pass(7), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) .await?; @@ -1210,9 +1225,9 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R json_run_pass(9), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name").start().await?; + let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) @@ -1304,9 +1319,9 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { json_run_pass(9), ]; - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - let series = step.add_measurement_series("name"); + let series = s.add_measurement_series("name"); series .scope(|s| { async move { @@ -1339,7 +1354,21 @@ async fn test_config_builder_with_file() -> Result<()> { let expected = [ json_schema_version(), - json_run_default_start(), + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "name": "run_name", + "parameters": {}, + "version": "1.0", + "commandLine": "" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), json!({ "testRunArtifact": { "error": { @@ -1421,9 +1450,9 @@ async fn test_step_with_extension() -> Result<()> { number_field: u32, } - check_output_step(&expected, |step| { + check_output_step(&expected, |s, _| { async { - step.extension( + s.extension( "extension", Ext { r#type: "TestExtension".to_owned(), @@ -1517,7 +1546,13 @@ async fn test_testrun_metadata() -> Result<()> { "testRunArtifact": { "testRunStart": { "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], }, "metadata": {"key": "value"}, "name": "run_name", @@ -1533,7 +1568,7 @@ async fn test_testrun_metadata() -> Result<()> { json_run_pass(2), ]; - check_output(&expected, |run_builder| async { + check_output(&expected, |run_builder, _| async { let run = run_builder .add_metadata("key", "value".into()) .build() @@ -1555,7 +1590,13 @@ async fn test_testrun_builder() -> Result<()> { "testRunStart": { "commandLine": "cmd_line", "dutInfo": { - "dutInfoId": "dut_id" + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], }, "metadata": { "key": "value", @@ -1574,7 +1615,7 @@ async fn test_testrun_builder() -> Result<()> { json_run_pass(2), ]; - check_output(&expected, |run_builder| async { + check_output(&expected, |run_builder, _| async { let run = run_builder .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) From 891caee6679b03f45c140c28d6aef1e18ebee62d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 11:47:09 +0100 Subject: [PATCH 48/96] add auto-numbering to hardware infos - similar to prev commit; fixed tests and necessary refactoring Signed-off-by: mimir-d --- src/output/dut.rs | 401 +++++++++++++++++++++-------------------- src/output/measure.rs | 114 ++++++------ src/output/mod.rs | 4 +- src/output/step.rs | 8 +- src/spec.rs | 12 +- tests/output/runner.rs | 72 +++++--- 6 files changed, 331 insertions(+), 280 deletions(-) diff --git a/src/output/dut.rs b/src/output/dut.rs index 8f617f1..dd7ee4a 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -11,8 +11,9 @@ use crate::output as tv; use crate::spec; // TODO: docs -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Default)] pub enum Ident { + #[default] Auto, Exact(String), } @@ -22,9 +23,11 @@ pub enum Ident { pub struct DutInfo { id: String, name: Option, + platform_infos: Option>, software_infos: Vec, - hardware_infos: Option>, + hardware_infos: Vec, + metadata: Option>, } @@ -37,24 +40,36 @@ impl DutInfo { DutInfoBuilder::new(id).build() } - pub fn add_software_info(&mut self, software_info: SoftwareInfo) -> DutSoftwareInfo { - let id = match &software_info.id { + pub fn add_software_info(&mut self, info: SoftwareInfo) -> DutSoftwareInfo { + let id = match &info.id { Ident::Auto => format!("{}_sw_{}", self.id, self.software_infos.len()), Ident::Exact(v) => v.to_owned(), }; - let info = DutSoftwareInfo { - id, - source: software_info, - }; + let info = DutSoftwareInfo { id, source: info }; self.software_infos.push(info.clone()); info } + pub fn add_hardware_info(&mut self, info: HardwareInfo) -> DutHardwareInfo { + let id = match &info.id { + Ident::Auto => format!("{}_hw_{}", self.id, self.hardware_infos.len()), + Ident::Exact(v) => v.to_owned(), + }; + + let info = DutHardwareInfo { id, source: info }; + self.hardware_infos.push(info.clone()); + info + } + pub fn software_info(&self, id: &str) -> Option<&DutSoftwareInfo> { self.software_infos.iter().find(|si| si.id == id) } + pub fn hardware_info(&self, id: &str) -> Option<&DutHardwareInfo> { + self.hardware_infos.iter().find(|si| si.id == id) + } + pub(crate) fn to_spec(&self) -> spec::DutInfo { spec::DutInfo { id: self.id.clone(), @@ -72,10 +87,15 @@ impl DutInfo { .collect(), ), }, - hardware_infos: self - .hardware_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), + hardware_infos: match self.hardware_infos.len() { + 0 => None, + _ => Some( + self.hardware_infos + .iter() + .map(|info| info.to_spec()) + .collect(), + ), + }, metadata: self.metadata.clone(), } } @@ -85,7 +105,6 @@ pub struct DutInfoBuilder { id: String, name: Option, platform_infos: Option>, - hardware_infos: Option>, metadata: Option>, } @@ -95,7 +114,6 @@ impl DutInfoBuilder { id: id.to_string(), name: None, platform_infos: None, - hardware_infos: None, metadata: None, } } @@ -115,32 +133,6 @@ impl DutInfoBuilder { self } - // pub fn add_software_info(mut self, software_info: SoftwareInfo) -> DutInfoBuilder { - // let id = match &software_info.id { - // Ident::Auto => format!("{}_sw_{}", self.id, self.software_infos.len()), - // Ident::Exact(v) => v.to_owned(), - // }; - - // let info = DutSoftwareInfo { - // id, - // source: software_info, - // }; - // self.software_infos.push(info.clone()); - - // self - // } - - pub fn add_hardware_info(mut self, hardware_info: &HardwareInfo) -> DutInfoBuilder { - self.hardware_infos = match self.hardware_infos { - Some(mut hardware_infos) => { - hardware_infos.push(hardware_info.clone()); - Some(hardware_infos) - } - None => Some(vec![hardware_info.clone()]), - }; - self - } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { self.metadata = match self.metadata { Some(mut metadata) => { @@ -159,144 +151,8 @@ impl DutInfoBuilder { id: self.id, name: self.name, platform_infos: self.platform_infos, - software_infos: vec![], - hardware_infos: self.hardware_infos, metadata: self.metadata, - } - } -} - -#[derive(Debug, Clone, PartialEq)] -pub struct HardwareInfo { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfo { - pub fn builder(id: &str, name: &str) -> HardwareInfoBuilder { - HardwareInfoBuilder::new(id, name) - } - - pub fn to_spec(&self) -> spec::HardwareInfo { - spec::HardwareInfo { - id: self.id.clone(), - name: self.name.clone(), - version: self.version.clone(), - revision: self.revision.clone(), - location: self.location.clone(), - serial_no: self.serial_no.clone(), - part_no: self.part_no.clone(), - manufacturer: self.manufacturer.clone(), - manufacturer_part_no: self.manufacturer_part_no.clone(), - odata_id: self.odata_id.clone(), - computer_system: self.computer_system.clone(), - manager: self.manager.clone(), - } - } - - pub fn id(&self) -> &str { - &self.id - } -} - -#[derive(Debug)] -pub struct HardwareInfoBuilder { - id: String, - name: String, - version: Option, - revision: Option, - location: Option, - serial_no: Option, - part_no: Option, - manufacturer: Option, - manufacturer_part_no: Option, - odata_id: Option, - computer_system: Option, - manager: Option, -} - -impl HardwareInfoBuilder { - fn new(id: &str, name: &str) -> Self { - HardwareInfoBuilder { - id: id.to_string(), - name: name.to_string(), - version: None, - revision: None, - location: None, - serial_no: None, - part_no: None, - manufacturer: None, - manufacturer_part_no: None, - odata_id: None, - computer_system: None, - manager: None, - } - } - pub fn version(mut self, value: &str) -> HardwareInfoBuilder { - self.version = Some(value.to_string()); - self - } - pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { - self.revision = Some(value.to_string()); - self - } - pub fn location(mut self, value: &str) -> HardwareInfoBuilder { - self.location = Some(value.to_string()); - self - } - pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { - self.serial_no = Some(value.to_string()); - self - } - pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.part_no = Some(value.to_string()); - self - } - pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer = Some(value.to_string()); - self - } - pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { - self.manufacturer_part_no = Some(value.to_string()); - self - } - pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { - self.odata_id = Some(value.to_string()); - self - } - pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { - self.computer_system = Some(value.to_string()); - self - } - pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { - self.manager = Some(value.to_string()); - self - } - - pub fn build(self) -> HardwareInfo { - HardwareInfo { - id: self.id, - name: self.name, - version: self.version, - revision: self.revision, - location: self.location, - serial_no: self.serial_no, - part_no: self.part_no, - manufacturer: self.manufacturer, - manufacturer_part_no: self.manufacturer_part_no, - odata_id: self.odata_id, - computer_system: self.computer_system, - manager: self.manager, + ..Default::default() } } } @@ -449,7 +305,7 @@ impl PartialEq for DutSoftwareInfo { } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct SoftwareInfoBuilder { id: tv::Ident, name: String, @@ -464,10 +320,7 @@ impl SoftwareInfoBuilder { SoftwareInfoBuilder { id: Ident::Auto, name: name.to_string(), - version: None, - revision: None, - software_type: None, - computer_system: None, + ..Default::default() } } @@ -508,6 +361,162 @@ impl SoftwareInfoBuilder { } } +#[derive(Debug, Clone)] +pub struct HardwareInfo { + id: Ident, + name: String, + + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + // TODO: missing part_type + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfo { + pub fn builder(name: &str) -> HardwareInfoBuilder { + HardwareInfoBuilder::new(name) + } +} + +#[derive(Debug, Clone)] +pub struct DutHardwareInfo { + id: String, + source: HardwareInfo, +} + +impl DutHardwareInfo { + pub(crate) fn to_spec(&self) -> spec::HardwareInfo { + let src = &self.source; + + spec::HardwareInfo { + id: self.id.clone(), + name: src.name.clone(), + version: src.version.clone(), + revision: src.revision.clone(), + location: src.location.clone(), + serial_no: src.serial_no.clone(), + part_no: src.part_no.clone(), + manufacturer: src.manufacturer.clone(), + manufacturer_part_no: src.manufacturer_part_no.clone(), + odata_id: src.odata_id.clone(), + computer_system: src.computer_system.clone(), + manager: src.manager.clone(), + } + } +} + +impl PartialEq for DutHardwareInfo { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +#[derive(Debug, Default)] +pub struct HardwareInfoBuilder { + id: tv::Ident, + name: String, + + version: Option, + revision: Option, + location: Option, + serial_no: Option, + part_no: Option, + manufacturer: Option, + manufacturer_part_no: Option, + odata_id: Option, + computer_system: Option, + manager: Option, +} + +impl HardwareInfoBuilder { + fn new(name: &str) -> Self { + HardwareInfoBuilder { + id: Ident::Auto, + name: name.to_string(), + ..Default::default() + } + } + + pub fn id(mut self, value: tv::Ident) -> HardwareInfoBuilder { + self.id = value; + self + } + + pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + self.version = Some(value.to_string()); + self + } + + pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + self.revision = Some(value.to_string()); + self + } + + pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + self.location = Some(value.to_string()); + self + } + + pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + self.serial_no = Some(value.to_string()); + self + } + + pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.part_no = Some(value.to_string()); + self + } + + pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer = Some(value.to_string()); + self + } + + pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + self.manufacturer_part_no = Some(value.to_string()); + self + } + + pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + self.odata_id = Some(value.to_string()); + self + } + + pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + self.computer_system = Some(value.to_string()); + self + } + + pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + self.manager = Some(value.to_string()); + self + } + + pub fn build(self) -> HardwareInfo { + HardwareInfo { + id: self.id, + name: self.name, + version: self.version, + revision: self.revision, + location: self.location, + serial_no: self.serial_no, + part_no: self.part_no, + manufacturer: self.manufacturer, + manufacturer_part_no: self.manufacturer_part_no, + odata_id: self.odata_id, + computer_system: self.computer_system, + manager: self.manager, + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -586,18 +595,22 @@ mod tests { #[test] fn test_hardware_info() -> Result<()> { - let info = HardwareInfo::builder("hardware_id", "hardware_name") - .version("version") - .revision("revision") - .location("location") - .serial_no("serial_no") - .part_no("part_no") - .manufacturer("manufacturer") - .manufacturer_part_no("manufacturer_part_no") - .odata_id("odata_id") - .computer_system("computer_system") - .manager("manager") - .build(); + let mut dut = DutInfo::new("dut0"); + let info = dut.add_hardware_info( + HardwareInfo::builder("hardware_name") + .id(Ident::Exact("hardware_id".to_owned())) + .version("version") + .revision("revision") + .location("location") + .serial_no("serial_no") + .part_no("part_no") + .manufacturer("manufacturer") + .manufacturer_part_no("manufacturer_part_no") + .odata_id("odata_id") + .computer_system("computer_system") + .manager("manager") + .build(), + ); let spec_hwinfo = info.to_spec(); diff --git a/src/output/measure.rs b/src/output/measure.rs index cd80723..2f6913a 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -332,27 +332,28 @@ impl ValidatorBuilder { /// ## Create a Measurement object with the `builder` method /// /// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; +/// # use ocptv::output::*; +/// +/// let mut dut = DutInfo::new("dut0"); +/// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) +/// .hardware_info(&hw_info) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` pub struct Measurement { name: String, + value: tv::Value, unit: Option, validators: Option>, - hardware_info: Option, + + hardware_info: Option, subcomponent: Option, + metadata: Option>, } @@ -384,17 +385,15 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::Measurement; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; + /// # use ocptv::output::*; + /// + /// let mut dut = DutInfo::new("dut0"); + /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) + /// .hardware_info(&hw_info) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` @@ -422,10 +421,10 @@ impl Measurement { .validators .clone() .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info_id: self + hardware_info: self .hardware_info .as_ref() - .map(|hardware_info| hardware_info.id().to_owned()), + .map(dut::DutHardwareInfo::to_spec), subcomponent: self .subcomponent .as_ref() @@ -440,28 +439,28 @@ impl Measurement { /// # Examples /// /// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Measurement; -/// use ocptv::output::MeasurementBuilder; -/// use ocptv::output::Subcomponent; -/// use ocptv::output::Validator; -/// use ocptv::output::ValidatorType; -/// use ocptv::output::Value; +/// # use ocptv::output::*; +/// +/// let mut dut = DutInfo::new("dut0"); +/// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let builder = MeasurementBuilder::new("name", 50.into()) -/// .hardware_info(&HardwareInfo::builder("id", "name").build()) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) +/// .hardware_info(&hw_info) /// .subcomponent(&Subcomponent::builder("name").build()); /// let measurement = builder.build(); /// ``` pub struct MeasurementBuilder { name: String, + value: tv::Value, unit: Option, validators: Option>, - hardware_info: Option, + + hardware_info: Option, subcomponent: Option, + metadata: Option>, } @@ -493,12 +492,7 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Validator; - /// use ocptv::output::ValidatorType; - /// use ocptv::output::Value; + /// # use ocptv::output::*; /// /// let builder = MeasurementBuilder::new("name", 50.into()) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); @@ -519,14 +513,15 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; + /// # use ocptv::output::*; + /// + /// let mut dut = DutInfo::new("dut0"); + /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .hardware_info(&HardwareInfo::builder("id", "name").build()); + /// .hardware_info(&hw_info); /// ``` - pub fn hardware_info(mut self, hardware_info: &dut::HardwareInfo) -> MeasurementBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> MeasurementBuilder { self.hardware_info = Some(hardware_info.clone()); self } @@ -613,12 +608,15 @@ impl MeasurementBuilder { } pub struct MeasurementSeriesStart { + series_id: String, name: String, + unit: Option, - series_id: String, validators: Option>, - hardware_info: Option, + + hardware_info: Option, subcomponent: Option, + metadata: Option>, } @@ -651,7 +649,7 @@ impl MeasurementSeriesStart { hardware_info: self .hardware_info .as_ref() - .map(|hardware_info| hardware_info.to_spec()), + .map(dut::DutHardwareInfo::to_spec), subcomponent: self .subcomponent .as_ref() @@ -662,12 +660,15 @@ impl MeasurementSeriesStart { } pub struct MeasurementSeriesStartBuilder { + series_id: String, name: String, + unit: Option, - series_id: String, validators: Option>, - hardware_info: Option, + + hardware_info: Option, subcomponent: Option, + metadata: Option>, } @@ -696,7 +697,7 @@ impl MeasurementSeriesStartBuilder { pub fn hardware_info( mut self, - hardware_info: &dut::HardwareInfo, + hardware_info: &dut::DutHardwareInfo, ) -> MeasurementSeriesStartBuilder { self.hardware_info = Some(hardware_info.clone()); self @@ -765,7 +766,7 @@ mod tests { unit: None, value, validators: None, - hardware_info_id: None, + hardware_info: None, subcomponent: None, metadata: None, } @@ -776,9 +777,11 @@ mod tests { #[test] fn test_measurement_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let mut dut = DutInfo::new("dut0"); + let name = "name".to_owned(); let value = tv::Value::from(50000); - let hardware_info = HardwareInfo::builder("id", "name").build(); + let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); let meta_key = "key"; @@ -791,13 +794,12 @@ mod tests { let unit = "RPM"; let measurement = Measurement::builder(&name, value.clone()) - .hardware_info(&hardware_info) + .unit(unit) .add_validator(&validator) .add_validator(&validator) - .add_metadata(meta_key, meta_value.clone()) - .add_metadata(meta_key, meta_value.clone()) + .hardware_info(&hw_info) .subcomponent(&subcomponent) - .unit(unit) + .add_metadata(meta_key, meta_value.clone()) .build(); let artifact = measurement.to_artifact(); @@ -805,10 +807,10 @@ mod tests { artifact, spec::Measurement { name, - unit: Some(unit.to_string()), value, + unit: Some(unit.to_string()), validators: Some(vec![validator.to_spec(), validator.to_spec()]), - hardware_info_id: Some(hardware_info.to_spec().id.clone()), + hardware_info: Some(hw_info.to_spec()), subcomponent: Some(subcomponent.to_spec()), metadata: Some(metadata), } @@ -842,29 +844,31 @@ mod tests { #[test] fn test_measurement_series_start_builder_to_artifact() -> Result<()> { + let mut dut = DutInfo::new("dut0"); + let name = "name".to_owned(); let series_id = "series_id".to_owned(); let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); - let hw_info = HardwareInfo::builder("id", "name").build(); + let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); let subcomponent = Subcomponent::builder("name").build(); let series = MeasurementSeriesStart::builder(&name, &series_id) .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) .add_validator(&validator) .add_validator(&validator2) .hardware_info(&hw_info) .subcomponent(&subcomponent) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .build(); let artifact = series.to_artifact(); assert_eq!( artifact, spec::MeasurementSeriesStart { + series_id: series_id.to_string(), name, unit: Some("unit".to_string()), - series_id: series_id.to_string(), validators: Some(vec![validator.to_spec(), validator2.to_spec()]), hardware_info: Some(hw_info.to_spec()), subcomponent: Some(subcomponent.to_spec()), diff --git a/src/output/mod.rs b/src/output/mod.rs index cd2e838..3f137bc 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -21,8 +21,8 @@ pub use crate::spec::{ }; pub use config::{Config, ConfigBuilder, TimestampProvider}; pub use dut::{ - DutInfo, DutInfoBuilder, DutSoftwareInfo, HardwareInfo, HardwareInfoBuilder, Ident, - PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, + DutHardwareInfo, DutInfo, DutInfoBuilder, DutSoftwareInfo, HardwareInfo, HardwareInfoBuilder, + Ident, PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, SubcomponentBuilder, }; pub use error::{Error, ErrorBuilder}; diff --git a/src/output/step.rs b/src/output/step.rs index 84049e7..0faacb3 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -449,17 +449,19 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let hwinfo = HardwareInfo::builder("id", "fan").build(); - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let mut dut = DutInfo::new("my_dut"); + /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); + /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; /// let step = run.add_step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) - /// .hardware_info(&hwinfo) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) + /// .hardware_info(&hw_info) /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// step.add_measurement_with_details(&measurement).await?; + /// /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) diff --git a/src/spec.rs b/src/spec.rs index 2b22dc1..5aacee5 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -422,6 +422,12 @@ pub struct HardwareInfo { pub manager: Option, } +impl serialize_ids::IdGetter for HardwareInfo { + fn id(&self) -> &str { + &self.id + } +} + /// Low-level model for the `testRunEnd` spec object. /// End marker signaling the finality of a diagnostic test. /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend @@ -578,6 +584,7 @@ pub struct TestStepEnd { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement +#[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] pub struct Measurement { @@ -597,7 +604,8 @@ pub struct Measurement { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] - pub hardware_info_id: Option, + #[serde_as(as = "Option")] + pub hardware_info: Option, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "subcomponent")] @@ -664,6 +672,7 @@ pub struct Subcomponent { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_start.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart +#[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] pub struct MeasurementSeriesStart { @@ -683,6 +692,7 @@ pub struct MeasurementSeriesStart { #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "hardwareInfoId")] + #[serde_as(as = "Option")] pub hardware_info: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 5b34771..387c499 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -62,6 +62,11 @@ fn json_run_default_start() -> serde_json::Value { "version": "22", "softwareType": "SYSTEM", }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] }, "name": "run_name", "parameters": {}, @@ -128,6 +133,12 @@ where .software_type(SoftwareType::System) .build(), ); + dut.add_hardware_info( + HardwareInfo::builder("fan") + .id(Ident::Exact("hw0".to_owned())) + .location("board0/fan") + .build(), + ); let run_builder = TestRun::builder("run_name", &dut, "1.0").config( Config::builder() @@ -674,19 +685,19 @@ async fn test_step_with_measurement_builder() -> Result<()> { "testStepArtifact": { "testStepId": "step_0", "measurement": { - "hardwareInfoId": "id", - "metadata": { - "key": "value" - }, "name": "name", - "subcomponent": { - "name": "name" - }, + "value": 50, "validators": [{ "type": "EQUAL", "value": 30 }], - "value": 50 + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value" + } } }, "sequenceNumber": 3, @@ -696,12 +707,14 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + let measurement = Measurement::builder("name", 50.into()) - .hardware_info(&HardwareInfo::builder("id", "name").build()) .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) .add_metadata("key", "value".into()) + .hardware_info(hw_info) .subcomponent(&Subcomponent::builder("name").build()) .build(); s.add_measurement_with_details(&measurement).await?; @@ -885,22 +898,19 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "testStepArtifact": { "testStepId": "step_0", "measurementSeriesStart": { - "hardwareInfoId": { - "hardwareInfoId": "id", - "name": "name" - }, "measurementSeriesId": "series_id", - "metadata": { - "key": "value" - }, "name": "name", - "subcomponent": { - "name": "name" - }, "validators": [{ "type": "EQUAL", "value": 30 - }] + }], + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value" + } } }, "sequenceNumber": 3, @@ -921,14 +931,16 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R json_run_pass(6), ]; - check_output_step(&expected, |s, _| { - async { + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + let series = s .add_measurement_series_with_details( MeasurementSeriesStart::builder("name", "series_id") .add_metadata("key", "value".into()) .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(&HardwareInfo::builder("id", "name").build()) + .hardware_info(hw_info) .subcomponent(&Subcomponent::builder("name").build()) .build(), ) @@ -1553,6 +1565,11 @@ async fn test_testrun_metadata() -> Result<()> { "version": "22", "softwareType": "SYSTEM", }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] }, "metadata": {"key": "value"}, "name": "run_name", @@ -1597,6 +1614,11 @@ async fn test_testrun_builder() -> Result<()> { "version": "22", "softwareType": "SYSTEM", }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] }, "metadata": { "key": "value", From a490cbd16ea3b808be552dff3bf781df995e7687 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 13:52:41 +0100 Subject: [PATCH 49/96] make dut platform_infos consistent with the other info types Signed-off-by: mimir-d --- src/output/dut.rs | 206 ++++++++++++++++++++++++---------------------- 1 file changed, 109 insertions(+), 97 deletions(-) diff --git a/src/output/dut.rs b/src/output/dut.rs index dd7ee4a..b36b790 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -24,7 +24,7 @@ pub struct DutInfo { id: String, name: Option, - platform_infos: Option>, + platform_infos: Vec, software_infos: Vec, hardware_infos: Vec, @@ -74,37 +74,19 @@ impl DutInfo { spec::DutInfo { id: self.id.clone(), name: self.name.clone(), - platform_infos: self - .platform_infos - .clone() - .map(|infos| infos.iter().map(|info| info.to_spec()).collect()), - software_infos: match self.software_infos.len() { - 0 => None, - _ => Some( - self.software_infos - .iter() - .map(|info| info.to_spec()) - .collect(), - ), - }, - hardware_infos: match self.hardware_infos.len() { - 0 => None, - _ => Some( - self.hardware_infos - .iter() - .map(|info| info.to_spec()) - .collect(), - ), - }, + platform_infos: self.platform_infos.map_option(PlatformInfo::to_spec), + software_infos: self.software_infos.map_option(DutSoftwareInfo::to_spec), + hardware_infos: self.hardware_infos.map_option(DutHardwareInfo::to_spec), metadata: self.metadata.clone(), } } } +#[derive(Default)] pub struct DutInfoBuilder { id: String, name: Option, - platform_infos: Option>, + platform_infos: Vec, metadata: Option>, } @@ -112,24 +94,17 @@ impl DutInfoBuilder { pub fn new(id: &str) -> DutInfoBuilder { DutInfoBuilder { id: id.to_string(), - name: None, - platform_infos: None, - metadata: None, + ..Default::default() } } + pub fn name(mut self, value: &str) -> DutInfoBuilder { self.name = Some(value.to_string()); self } pub fn add_platform_info(mut self, platform_info: &PlatformInfo) -> DutInfoBuilder { - self.platform_infos = match self.platform_infos { - Some(mut platform_infos) => { - platform_infos.push(platform_info.clone()); - Some(platform_infos) - } - None => Some(vec![platform_info.clone()]), - }; + self.platform_infos.push(platform_info.clone()); self } @@ -517,11 +492,26 @@ impl HardwareInfoBuilder { } } +trait VecExt { + fn map_option(&self, func: F) -> Option> + where + F: Fn(&T) -> U; +} + +impl VecExt for Vec { + fn map_option(&self, func: F) -> Option> + where + F: Fn(&T) -> U, + { + (!self.is_empty()).then_some(self.iter().map(func).collect()) + } +} + #[cfg(test)] mod tests { use super::*; use crate::spec; - use anyhow::Result; + use anyhow::{bail, Result}; #[test] fn test_dut_creation_from_builder_with_defaults() -> Result<()> { @@ -530,68 +520,72 @@ mod tests { Ok(()) } - // #[test] - // fn test_dut_builder() -> Result<()> { - // let platform = PlatformInfo::builder("platform_info").build(); - // let software = SoftwareInfo::builder("software_id", "name").build(); - // let hardware = HardwareInfo::builder("hardware_id", "name").build(); - // let dut = DutInfo::builder("1234") - // .name("DUT") - // .add_metadata("key", "value".into()) - // .add_metadata("key2", "value2".into()) - // .add_hardware_info(&hardware) - // .add_hardware_info(&hardware) - // .add_platform_info(&platform) - // .add_platform_info(&platform) - // .add_software_info(&software) - // .add_software_info(&software) - // .build(); - - // let spec_dut = dut.to_spec(); - - // assert_eq!(spec_dut.id, "1234"); - // assert_eq!(spec_dut.name, Some("DUT".to_owned())); - - // match spec_dut.metadata { - // Some(m) => { - // assert_eq!(m["key"], "value"); - // assert_eq!(m["key2"], "value2"); - // } - // _ => bail!("metadata is empty"), - // } - - // match spec_dut.hardware_infos { - // Some(infos) => match infos.first() { - // Some(info) => { - // assert_eq!(info.id, "hardware_id"); - // } - // _ => bail!("hardware_infos is empty"), - // }, - // _ => bail!("hardware_infos is missing"), - // } - - // match spec_dut.software_infos { - // Some(infos) => match infos.first() { - // Some(info) => { - // assert_eq!(info.id, "software_id"); - // } - // _ => bail!("software_infos is empty"), - // }, - // _ => bail!("software_infos is missing"), - // } - - // match spec_dut.platform_infos { - // Some(infos) => match infos.first() { - // Some(info) => { - // assert_eq!(info.info, "platform_info"); - // } - // _ => bail!("platform_infos is empty"), - // }, - // _ => bail!("platform_infos is missing"), - // } - - // Ok(()) - // } + #[test] + fn test_dut_builder() -> Result<()> { + let mut dut = DutInfo::builder("1234") + .name("dut") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_platform_info(&PlatformInfo::builder("platform_info").build()) + .build(); + + dut.add_software_info( + SoftwareInfo::builder("name") + .id(Ident::Exact("software_id".to_owned())) + .build(), + ); + + dut.add_hardware_info( + HardwareInfo::builder("name") + .id(Ident::Exact("hardware_id".to_owned())) + .build(), + ); + + let spec_dut = dut.to_spec(); + + assert_eq!(spec_dut.id, "1234"); + assert_eq!(spec_dut.name, Some("dut".to_owned())); + + match spec_dut.platform_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.info, "platform_info"); + } + _ => bail!("platform_infos is empty"), + }, + _ => bail!("platform_infos is missing"), + } + + match spec_dut.software_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "software_id"); + } + _ => bail!("software_infos is empty"), + }, + _ => bail!("software_infos is missing"), + } + + match spec_dut.hardware_infos { + Some(infos) => match infos.first() { + Some(info) => { + assert_eq!(info.id, "hardware_id"); + } + _ => bail!("hardware_infos is empty"), + }, + _ => bail!("hardware_infos is missing"), + } + + match spec_dut.metadata { + Some(m) => { + assert_eq!(m["key"], "value"); + assert_eq!(m["key2"], "value2"); + } + _ => bail!("metadata is empty"), + } + + Ok(()) + } #[test] fn test_hardware_info() -> Result<()> { @@ -694,4 +688,22 @@ mod tests { Ok(()) } + + /// 100% coverage test, since there's no way to exclude code + #[test] + fn test_infos_eq() -> Result<()> { + let sw = DutSoftwareInfo { + id: "sw0".to_owned(), + source: SoftwareInfo::builder("sw").build(), + }; + assert_eq!(sw, sw); + + let hw = DutHardwareInfo { + id: "hw0".to_owned(), + source: HardwareInfo::builder("hw").build(), + }; + assert_eq!(hw, hw); + + Ok(()) + } } From e7cc616054d00c21cf231436de282c7b418a85e4 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 17:45:11 +0100 Subject: [PATCH 50/96] add demo with error before start run - this scenario required refactoring the test run api, since an error may be emitted prior to the run start; it also showed that outputting the schema version as part of the run start was incorrect - required refactoring in tests and other applications Signed-off-by: mimir-d --- examples/simple.rs | 41 +++++++----- src/output/dut.rs | 16 +---- src/output/emitter.rs | 32 ++++++--- src/output/error.rs | 26 +++----- src/output/macros.rs | 9 ++- src/output/measure.rs | 15 +++-- src/output/mod.rs | 1 + src/output/run.rs | 143 +++++++++++++++++++++++----------------- src/output/step.rs | 46 ++++++++----- src/output/trait_ext.rs | 20 ++++++ src/output/writer.rs | 4 +- tests/output/macros.rs | 4 +- tests/output/runner.rs | 131 +++++++++++++++++++++++++++++++----- 13 files changed, 323 insertions(+), 165 deletions(-) create mode 100644 src/output/trait_ext.rs diff --git a/examples/simple.rs b/examples/simple.rs index f53e67e..0d75fe5 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -11,7 +11,7 @@ use futures::FutureExt; #[cfg(feature = "boxed-scopes")] use ocptv::ocptv_log_info; use ocptv::{ - ocptv_log_debug, + ocptv_error, ocptv_log_debug, output::{self as tv}, }; use tv::{DutInfo, TestResult, TestRun, TestStatus}; @@ -35,9 +35,9 @@ macro_rules! run_demo { /// artifacts in case of unhandled exceptions or code misuse. async fn demo_no_scopes() -> Result<()> { let dut = DutInfo::builder("dut0").build(); - let run = TestRun::builder("with dut", &dut, "1.0") + let run = TestRun::builder("with dut", "1.0") .build() - .start() + .start(dut) .await?; let step = run.add_step("step0").start().await?; @@ -53,9 +53,9 @@ async fn demo_no_scopes() -> Result<()> { #[cfg(feature = "boxed-scopes")] async fn demo_scope_run_skip() -> Result<()> { let dut = DutInfo::builder("dut0").build(); - TestRun::builder("with dut", &dut, "1.0") + TestRun::builder("with dut", "1.0") .build() - .scope(|_r| { + .scope(dut, |_r| { async move { // intentional short return return Ok(TestRunOutcome { @@ -75,9 +75,9 @@ async fn demo_scope_run_skip() -> Result<()> { #[cfg(feature = "boxed-scopes")] async fn demo_scope_step_fail() -> Result<()> { let dut = DutInfo::builder("dut0").build(); - TestRun::builder("with dut", &dut, "1.0") + TestRun::builder("with dut", "1.0") .build() - .scope(|r| { + .scope(dut, |r| { async move { r.add_step("step0") .scope(|s| { @@ -105,6 +105,16 @@ async fn demo_scope_step_fail() -> Result<()> { Ok(()) } +/// In case of failure to discover DUT hardware before needing to present it at test run +/// start, we can error out right at the beginning since no Diagnosis can be produced. +/// This is a framework failure. +async fn demo_error_while_gathering_duts() -> Result<()> { + let run = TestRun::builder("failed run", "1.0").build(); + ocptv_error!(run, "no-dut", "could not find any valid DUTs").await?; + + Ok(()) +} + /// Show outputting an error message, triggered by a specific software component of the DUT. #[cfg(feature = "boxed-scopes")] async fn demo_run_error_with_dut() -> Result<()> { @@ -116,9 +126,9 @@ async fn demo_run_error_with_dut() -> Result<()> { .build(), ); - TestRun::builder("with dut", &dut, "1.0") + TestRun::builder("with dut", "1.0") .build() - .scope(|r| { + .scope(dut, |r| { async move { r.add_error_with_details( &tv::Error::builder("power-fail") @@ -142,13 +152,12 @@ async fn demo_run_error_with_dut() -> Result<()> { #[tokio::main] async fn main() { run_demo!(demo_no_scopes); + run_demo!(demo_error_while_gathering_duts); #[cfg(feature = "boxed-scopes")] - run_demo!(demo_scope_run_skip); - - #[cfg(feature = "boxed-scopes")] - run_demo!(demo_scope_step_fail); - - #[cfg(feature = "boxed-scopes")] - run_demo!(demo_run_error_with_dut); + { + run_demo!(demo_scope_run_skip); + run_demo!(demo_scope_step_fail); + run_demo!(demo_run_error_with_dut); + } } diff --git a/src/output/dut.rs b/src/output/dut.rs index b36b790..6fc8a2a 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -9,6 +9,7 @@ use std::collections::BTreeMap; use crate::output as tv; use crate::spec; +use tv::trait_ext::VecExt; // TODO: docs #[derive(Clone, Debug, PartialEq, Default)] @@ -492,21 +493,6 @@ impl HardwareInfoBuilder { } } -trait VecExt { - fn map_option(&self, func: F) -> Option> - where - F: Fn(&T) -> U; -} - -impl VecExt for Vec { - fn map_option(&self, func: F) -> Option> - where - F: Fn(&T) -> U, - { - (!self.is_empty()).then_some(self.iter().map(func).collect()) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 0d42aba..93e448f 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -38,9 +38,17 @@ impl JsonEmitter { self.seqno.fetch_add(1, Ordering::AcqRel) } - fn serialize_artifact(&self, object: &spec::RootImpl) -> String { + async fn emit_version(&self) -> Result<(), io::Error> { + let s = self.serialize(&spec::RootImpl::SchemaVersion( + spec::SchemaVersion::default(), + )); + + self.write(s).await + } + + fn serialize(&self, root: &spec::RootImpl) -> String { let root = spec::Root { - artifact: object.clone(), + artifact: root.clone(), timestamp: self.timestamp_provider.now(), seqno: self.incr_seqno(), }; @@ -48,13 +56,7 @@ impl JsonEmitter { serde_json::json!(root).to_string() } - pub fn timestamp_provider(&self) -> &(dyn config::TimestampProvider + Send + Sync + 'static) { - &*self.timestamp_provider - } - - pub async fn emit(&self, object: &spec::RootImpl) -> Result<(), io::Error> { - let s = self.serialize_artifact(object); - + async fn write(&self, s: String) -> Result<(), io::Error> { match &self.writer { WriterType::File(file) => file.write(&s).await?, WriterType::Stdout(stdout) => stdout.write(&s).await.unwrap_infallible(), @@ -65,6 +67,18 @@ impl JsonEmitter { Ok(()) } + + pub fn timestamp_provider(&self) -> &(dyn config::TimestampProvider + Send + Sync + 'static) { + &*self.timestamp_provider + } + + pub async fn emit(&self, root: &spec::RootImpl) -> Result<(), io::Error> { + if self.seqno.load(Ordering::Acquire) == 0 { + self.emit_version().await?; + } + + self.write(self.serialize(root)).await + } } #[cfg(test)] diff --git a/src/output/error.rs b/src/output/error.rs index 48d71e9..ab5fa61 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -6,12 +6,12 @@ use crate::output as tv; use crate::spec; -use tv::dut; +use tv::{dut, trait_ext::VecExt, DutSoftwareInfo}; pub struct Error { symptom: String, message: Option, - software_infos: Option>, + software_infos: Vec, source_location: Option, } @@ -24,17 +24,17 @@ impl Error { spec::Error { symptom: self.symptom.clone(), message: self.message.clone(), - software_infos: self.software_infos.clone(), + software_infos: self.software_infos.map_option(DutSoftwareInfo::to_spec), source_location: self.source_location.clone(), } } } -#[derive(Debug)] +#[derive(Debug, Default)] pub struct ErrorBuilder { symptom: String, message: Option, - software_infos: Option>, + software_infos: Vec, source_location: Option, } @@ -42,9 +42,7 @@ impl ErrorBuilder { fn new(symptom: &str) -> Self { ErrorBuilder { symptom: symptom.to_string(), - message: None, - source_location: None, - software_infos: None, + ..Default::default() } } @@ -62,13 +60,7 @@ impl ErrorBuilder { } pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> ErrorBuilder { - self.software_infos = match self.software_infos { - Some(mut software_infos) => { - software_infos.push(software_info.to_spec()); - Some(software_infos) - } - None => Some(vec![software_info.to_spec()]), - }; + self.software_infos.push(software_info.clone()); self } @@ -111,7 +103,7 @@ mod tests { spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), - software_infos: error.software_infos.clone(), + software_infos: Some(vec![sw_info.to_spec()]), source_location: error.source_location.clone(), } ); @@ -136,7 +128,7 @@ mod tests { spec::Error { symptom: error.symptom.clone(), message: error.message.clone(), - software_infos: error.software_infos.clone(), + software_infos: Some(vec![sw_info.to_spec()]), source_location: error.source_location.clone(), } ); diff --git a/src/output/macros.rs b/src/output/macros.rs index 41587e5..a7c237b 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -27,7 +27,8 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; +/// let dut = DutInfo::new("my_dut"); +/// let test_run = TestRun::new("run_name", "1.0").start(dut).await?; /// ocptv_error!(test_run, "symptom"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -43,7 +44,8 @@ /// /// use ocptv::ocptv_error; /// -/// let test_run = TestRun::new("run_name", "my_dut", "1.0").start().await?; +/// let dut = DutInfo::new("my_dut"); +/// let test_run = TestRun::new("run_name", "1.0").start(dut).await?; /// ocptv_error!(test_run, "symptom", "Error message"); /// test_run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -90,7 +92,8 @@ macro_rules! ocptv_error { /// /// use ocptv::ocptv_log_debug; /// -/// let run = TestRun::new("run_name", "my_dut", "1.0").start().await?; +/// let dut = DutInfo::new("my_dut"); +/// let run = TestRun::new("run_name", "1.0").start(dut).await?; /// ocptv_log_debug!(run, "Log message"); /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// diff --git a/src/output/measure.rs b/src/output/measure.rs index 2f6913a..cda2147 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -53,7 +53,8 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name"); @@ -89,7 +90,8 @@ impl MeasurementSeries { /// # use futures::FutureExt; /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name"); @@ -139,7 +141,8 @@ impl StartedMeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; @@ -172,7 +175,8 @@ impl StartedMeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; @@ -211,7 +215,8 @@ impl StartedMeasurementSeries { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; diff --git a/src/output/mod.rs b/src/output/mod.rs index 3f137bc..e1df9c8 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -14,6 +14,7 @@ mod macros; mod measure; mod run; mod step; +mod trait_ext; mod writer; pub use crate::spec::{ diff --git a/src/output/run.rs b/src/output/run.rs index 9ce83e9..9cc639d 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -36,7 +36,6 @@ pub struct TestRun { name: String, version: String, parameters: BTreeMap, - dut: dut::DutInfo, command_line: String, metadata: Option>, @@ -44,32 +43,30 @@ pub struct TestRun { } impl TestRun { - /// Creates a new [`TestRunBuilder`] object. + /// Creates a new [`TestRun`] object. /// /// # Examples /// /// ```rust /// # use ocptv::output::*; /// - /// let dut = DutInfo::builder("my_dut").build(); - /// let builder = TestRun::builder("run_name", &dut, "1.0"); + /// let run = TestRun::new("diagnostic_name", "1.0"); /// ``` - pub fn builder(name: &str, dut: &dut::DutInfo, version: &str) -> TestRunBuilder { - TestRunBuilder::new(name, dut, version) + pub fn new(name: &str, version: &str) -> TestRun { + TestRunBuilder::new(name, version).build() } - /// Creates a new [`TestRun`] object. + /// Creates a new [`TestRunBuilder`] object. /// /// # Examples /// /// ```rust /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); + /// let builder = TestRun::builder("run_name", "1.0"); /// ``` - pub fn new(name: &str, dut_id: &str, version: &str) -> TestRun { - let dut = dut::DutInfo::new(dut_id); - TestRunBuilder::new(name, &dut, version).build() + pub fn builder(name: &str, version: &str) -> TestRunBuilder { + TestRunBuilder::new(name, version) } /// Starts the test run. @@ -83,20 +80,14 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// run.start().await?; + /// let run = TestRun::new("diagnostic_name", "1.0"); + /// let dut = DutInfo::builder("my_dut").build(); + /// run.start(dut).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn start(self) -> Result { - // TODO: this likely will go into the emitter since it's not the run's job to emit the schema version - self.emitter - .emit(&spec::RootImpl::SchemaVersion( - spec::SchemaVersion::default(), - )) - .await?; - + pub async fn start(self, dut: dut::DutInfo) -> Result { let start = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::TestRunStart(spec::TestRunStart { name: self.name.clone(), @@ -104,7 +95,7 @@ impl TestRun { command_line: self.command_line.clone(), parameters: self.parameters.clone(), metadata: self.metadata.clone(), - dut_info: self.dut.to_spec(), + dut_info: dut.to_spec(), }), }); @@ -127,8 +118,9 @@ impl TestRun { /// # use futures::FutureExt; /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0"); - /// run.scope(|r| { + /// let run = TestRun::new("diagnostic_name", "1.0"); + /// let dut = DutInfo::builder("my_dut").build(); + /// run.scope(dut, |r| { /// async move { /// r.add_log(LogSeverity::Info, "First message").await?; /// Ok(TestRunOutcome { @@ -142,22 +134,64 @@ impl TestRun { /// # }); /// ``` #[cfg(feature = "boxed-scopes")] - pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + pub async fn scope(self, dut: dut::DutInfo, func: F) -> Result<(), tv::OcptvError> where F: FnOnce(&StartedTestRun) -> BoxFuture<'_, Result>, { - let run = self.start().await?; + let run = self.start(dut).await?; let outcome = func(&run).await?; run.end(outcome.status, outcome.result).await?; Ok(()) } + + /// Emits a Error message. + /// + /// This operation is useful in such cases when there is an error before starting the test. + /// (eg. failing to discover a DUT). + /// + /// See: [`StartedTestRun::add_error`] for details and examples. + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { + let error = error::Error::builder(symptom).build(); + + self.add_error_with_details(&error).await?; + Ok(()) + } + + /// Emits a Error message. + /// + /// This operation is useful in such cases when there is an error before starting the test. + /// (eg. failing to discover a DUT). + /// + /// See: [`StartedTestRun::add_error_with_msg`] for details and examples. + pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + let error = error::Error::builder(symptom).message(msg).build(); + + self.add_error_with_details(&error).await?; + Ok(()) + } + + /// Emits a Error message. + /// + /// This operation is useful in such cases when there is an error before starting the test. + /// (eg. failing to discover a DUT). + /// + /// See: [`StartedTestRun::add_error_with_details`] for details and examples. + pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + let artifact = spec::TestRunArtifact { + artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), + }; + self.emitter + .emit(&spec::RootImpl::TestRunArtifact(artifact)) + .await?; + + Ok(()) + } } /// Builder for the [`TestRun`] object. pub struct TestRunBuilder { name: String, - dut: dut::DutInfo, version: String, parameters: BTreeMap, command_line: String, @@ -166,10 +200,9 @@ pub struct TestRunBuilder { } impl TestRunBuilder { - pub fn new(name: &str, dut: &dut::DutInfo, version: &str) -> Self { + pub fn new(name: &str, version: &str) -> Self { Self { name: name.to_string(), - dut: dut.clone(), version: version.to_string(), parameters: BTreeMap::new(), command_line: env::args().collect::>()[1..].join(" "), @@ -185,8 +218,7 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// let run = TestRunBuilder::new("run_name", "1.0") /// .add_parameter("param1", "value1".into()) /// .build(); /// ``` @@ -203,8 +235,7 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// let run = TestRunBuilder::new("run_name", "1.0") /// .command_line("my_diag --arg value") /// .build(); /// ``` @@ -218,10 +249,9 @@ impl TestRunBuilder { /// # Examples /// /// ```rust - /// use ocptv::output::{Config, TestRunBuilder, DutInfo}; + /// # use ocptv::output::*; /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// let run = TestRunBuilder::new("run_name", "1.0") /// .config(Config::builder().build()) /// .build(); /// ``` @@ -237,8 +267,7 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// - /// let dut = DutInfo::builder("dut_id").build(); - /// let run = TestRunBuilder::new("run_name", &dut, "1.0") + /// let run = TestRunBuilder::new("run_name", "1.0") /// .add_metadata("meta1", "value1".into()) /// .build(); /// ``` @@ -261,7 +290,6 @@ impl TestRunBuilder { TestRun { name: self.name, - dut: self.dut, version: self.version, parameters: self.parameters, command_line: self.command_line, @@ -299,7 +327,8 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::builder("my_dut").build(); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) @@ -330,7 +359,8 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::builder("my_dut").build(); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log( /// LogSeverity::Info, /// "This is a log message with INFO severity", @@ -369,7 +399,8 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::builder("my_dut").build(); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log_with_details( /// &Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) @@ -404,7 +435,8 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::builder("my_dut").build(); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_error("symptom").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -414,14 +446,7 @@ impl StartedTestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), - }; - self.run - .emitter - .emit(&spec::RootImpl::TestRunArtifact(artifact)) - .await?; - + self.add_error_with_details(&error).await?; Ok(()) } @@ -437,7 +462,8 @@ impl StartedTestRun { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::builder("my_dut").build(); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_error_with_msg("symptom", "error messasge").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// @@ -447,19 +473,12 @@ impl StartedTestRun { pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - let artifact = spec::TestRunArtifact { - artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), - }; - self.run - .emitter - .emit(&spec::RootImpl::TestRunArtifact(artifact)) - .await?; - + self.add_error_with_details(&error).await?; Ok(()) } /// Emits a Error message. - /// This method acceps a [`objects::Error`] object. + /// This method accepts an [`error::Error`] object. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error /// @@ -471,7 +490,7 @@ impl StartedTestRun { /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); - /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; + /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// /// run.add_error_with_details( /// &Error::builder("symptom") diff --git a/src/output/step.rs b/src/output/step.rs index 0faacb3..162f7c6 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -48,7 +48,8 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// # Ok::<(), OcptvError>(()) @@ -81,7 +82,8 @@ impl TestStep { /// # use futures::FutureExt; /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("first step"); /// step.scope(|s| { @@ -126,7 +128,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.end(TestStatus::Complete).await?; @@ -153,7 +156,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_log( @@ -173,7 +177,8 @@ impl StartedTestStep { /// /// use ocptv::ocptv_log_info; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// ocptv_log_info!(step, "This is a log message with INFO severity").await?; @@ -208,7 +213,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_log_with_details( @@ -242,7 +248,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_error("symptom").await?; @@ -260,7 +267,8 @@ impl StartedTestStep { /// /// use ocptv::ocptv_error; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// ocptv_error!(step, "symptom").await?; @@ -292,7 +300,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_error_with_msg("symptom", "error message").await?; @@ -310,7 +319,8 @@ impl StartedTestStep { /// /// use ocptv::ocptv_error; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// ocptv_error!(step, "symptom", "error message").await?; @@ -343,7 +353,7 @@ impl StartedTestStep { /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); - /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; + /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_error_with_details( @@ -377,7 +387,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// #[derive(serde::Serialize)] @@ -412,7 +423,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; /// step.add_measurement("name", 50.into()).await?; @@ -451,7 +463,7 @@ impl StartedTestStep { /// /// let mut dut = DutInfo::new("my_dut"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); - /// let run = TestRun::builder("diagnostic_name", &dut, "1.0").build().start().await?; + /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) @@ -493,7 +505,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// let series = step.add_measurement_series("name"); /// @@ -520,7 +533,8 @@ impl StartedTestStep { /// # tokio_test::block_on(async { /// # use ocptv::output::*; /// - /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// let series = /// step.add_measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); diff --git a/src/output/trait_ext.rs b/src/output/trait_ext.rs new file mode 100644 index 0000000..b3aae93 --- /dev/null +++ b/src/output/trait_ext.rs @@ -0,0 +1,20 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +pub trait VecExt { + fn map_option(&self, func: F) -> Option> + where + F: Fn(&T) -> U; +} + +impl VecExt for Vec { + fn map_option(&self, func: F) -> Option> + where + F: Fn(&T) -> U, + { + (!self.is_empty()).then_some(self.iter().map(func).collect()) + } +} diff --git a/src/output/writer.rs b/src/output/writer.rs index 2c660da..8c2cca1 100644 --- a/src/output/writer.rs +++ b/src/output/writer.rs @@ -102,13 +102,13 @@ mod tests { #[tokio::test] async fn test_ocptv_error_has_public_source() -> Result<()> { let dut = DutInfo::builder("dut_id").build(); - let run_builder = TestRun::builder("run_name", &dut, "1.0").config( + let run_builder = TestRun::builder("run_name", "1.0").config( Config::builder() .with_custom_output(Box::new(ErrorWriter {})) .build(), ); - let actual = run_builder.build().start().await; + let actual = run_builder.build().start(dut).await; assert!(actual.is_err()); match &actual { diff --git a/tests/output/macros.rs b/tests/output/macros.rs index e1fcf6d..d09145f 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -29,10 +29,10 @@ where let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", "1.0") .config(Config::builder().with_buffer_output(buffer.clone()).build()) .build() - .start() + .start(dut) .await?; func(run).await?; diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 387c499..502c523 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -140,7 +140,7 @@ where .build(), ); - let run_builder = TestRun::builder("run_name", &dut, "1.0").config( + let run_builder = TestRun::builder("run_name", "1.0").config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) .with_timestamp_provider(Box::new(FixedTsProvider {})) @@ -162,11 +162,11 @@ async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Resu where F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, { - check_output(expected, |run_builder, dutinfo| async move { + check_output(expected, |run_builder, dut| async move { let run = run_builder.build(); - let run = run.start().await?; - test_fn(&run, dutinfo).await?; + let run = run.start(dut.clone()).await?; + test_fn(&run, dut).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; Ok(()) @@ -178,11 +178,11 @@ async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Res where F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, { - check_output(expected, |run_builder, dutinfo| async move { - let run = run_builder.build().start().await?; + check_output(expected, |run_builder, dut| async move { + let run = run_builder.build().start(dut.clone()).await?; let step = run.add_step("first step").start().await?; - test_fn(&step, dutinfo).await?; + test_fn(&step, dut).await?; step.end(TestStatus::Complete).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -359,6 +359,100 @@ async fn test_testrun_with_error_with_details() -> Result<()> { .await } +#[tokio::test] +async fn test_testrun_with_error_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error("no-dut").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + "message": "failed to find dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_with_msg("no-dut", "failed to find dut") + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "message": "failed to find dut", + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "no-dut" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_with_details( + &Error::builder("no-dut") + .message("failed to find dut") + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + #[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_testrun_with_scope() -> Result<()> { @@ -378,10 +472,10 @@ async fn test_testrun_with_scope() -> Result<()> { json_run_pass(3), ]; - check_output(&expected, |run_builder, _| async { + check_output(&expected, |run_builder, dut| async { let run = run_builder.build(); - run.scope(|r| { + run.scope(dut, |r| { async move { r.add_log(LogSeverity::Info, "First message").await?; @@ -1399,7 +1493,7 @@ async fn test_config_builder_with_file() -> Result<()> { let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", "1.0") .config( Config::builder() .timezone(chrono_tz::Europe::Rome) @@ -1409,7 +1503,7 @@ async fn test_config_builder_with_file() -> Result<()> { .build(), ) .build() - .start() + .start(dut) .await?; run.add_error_with_msg("symptom", "Error message").await?; @@ -1504,7 +1598,7 @@ async fn test_step_with_extension_which_fails() -> Result<()> { let buffer: Arc>> = Arc::new(Mutex::new(vec![])); let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", &dut, "1.0") + let run = TestRun::builder("run_name", "1.0") .config( Config::builder() .with_buffer_output(Arc::clone(&buffer)) @@ -1512,7 +1606,7 @@ async fn test_step_with_extension_which_fails() -> Result<()> { .build(), ) .build() - .start() + .start(dut) .await?; let step = run.add_step("first step").start().await?; @@ -1539,7 +1633,8 @@ async fn test_testrun_instantiation_with_new() -> Result<()> { ]; let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let run = TestRun::new("run_name", "dut_id", "1.0").start().await?; + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::new("run_name", "1.0").start(dut).await?; run.end(TestStatus::Complete, TestResult::Pass).await?; for (idx, entry) in buffer.lock().await.iter().enumerate() { @@ -1585,11 +1680,11 @@ async fn test_testrun_metadata() -> Result<()> { json_run_pass(2), ]; - check_output(&expected, |run_builder, _| async { + check_output(&expected, |run_builder, dut| async { let run = run_builder .add_metadata("key", "value".into()) .build() - .start() + .start(dut) .await?; run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -1637,14 +1732,14 @@ async fn test_testrun_builder() -> Result<()> { json_run_pass(2), ]; - check_output(&expected, |run_builder, _| async { + check_output(&expected, |run_builder, dut| async { let run = run_builder .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) .add_parameter("key", "value".into()) .command_line("cmd_line") .build() - .start() + .start(dut) .await?; run.end(TestStatus::Complete, TestResult::Pass).await?; From 31a6887ee164f8c0af9c58baecf9daf216242ca6 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 18:09:59 +0100 Subject: [PATCH 51/96] split examples into one main per file - this will be needed in a future CI test that uses the spec json validator against the output of these examples Signed-off-by: mimir-d --- examples/simple.rs | 163 ------------------ examples/simple_error_while_gathering_duts.rs | 22 +++ examples/simple_no_scopes.rs | 31 ++++ examples/simple_run_error_with_dut.rs | 46 +++++ examples/simple_run_skip.rs | 34 ++++ examples/simple_step_fail.rs | 47 +++++ 6 files changed, 180 insertions(+), 163 deletions(-) delete mode 100644 examples/simple.rs create mode 100644 examples/simple_error_while_gathering_duts.rs create mode 100644 examples/simple_no_scopes.rs create mode 100644 examples/simple_run_error_with_dut.rs create mode 100644 examples/simple_run_skip.rs create mode 100644 examples/simple_step_fail.rs diff --git a/examples/simple.rs b/examples/simple.rs deleted file mode 100644 index 0d75fe5..0000000 --- a/examples/simple.rs +++ /dev/null @@ -1,163 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use anyhow::Result; -#[cfg(feature = "boxed-scopes")] -use futures::FutureExt; - -#[cfg(feature = "boxed-scopes")] -use ocptv::ocptv_log_info; -use ocptv::{ - ocptv_error, ocptv_log_debug, - output::{self as tv}, -}; -use tv::{DutInfo, TestResult, TestRun, TestStatus}; -#[cfg(feature = "boxed-scopes")] -use tv::{SoftwareInfo, SoftwareType, TestRunOutcome}; - -macro_rules! run_demo { - ($name: ident) => { - println!("{}", format!("{:->width$}", "", width = 80)); - println!("{}", stringify!($name)); - println!("{}", format!("{:->width$}", "", width = 80)); - - let _ = $name().await; - println!(); - }; -} - -/// Show that a run/step can be manually started and ended. -/// -/// The scope version should be preferred, as it makes it safer not to miss the end -/// artifacts in case of unhandled exceptions or code misuse. -async fn demo_no_scopes() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); - let run = TestRun::builder("with dut", "1.0") - .build() - .start(dut) - .await?; - - let step = run.add_step("step0").start().await?; - ocptv_log_debug!(step, "Some interesting message.").await?; - step.end(TestStatus::Complete).await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) -} - -/// Show a context-scoped run that automatically exits the whole func -/// because of the marker exception that triggers SKIP outcome. -#[cfg(feature = "boxed-scopes")] -async fn demo_scope_run_skip() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); - TestRun::builder("with dut", "1.0") - .build() - .scope(dut, |_r| { - async move { - // intentional short return - return Ok(TestRunOutcome { - status: TestStatus::Skip, - result: TestResult::NotApplicable, - }); - } - .boxed() - }) - .await?; - - Ok(()) -} - -/// Show a scoped run with scoped steps, everything starts at "with" time and -/// ends automatically when the block ends (regardless of unhandled exceptions). -#[cfg(feature = "boxed-scopes")] -async fn demo_scope_step_fail() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); - TestRun::builder("with dut", "1.0") - .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| { - async move { - ocptv_log_info!(s, "info log").await?; - Ok(TestStatus::Complete) - } - .boxed() - }) - .await?; - - r.add_step("step1") - .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) - .await?; - - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Fail, - }) - } - .boxed() - }) - .await?; - - Ok(()) -} - -/// In case of failure to discover DUT hardware before needing to present it at test run -/// start, we can error out right at the beginning since no Diagnosis can be produced. -/// This is a framework failure. -async fn demo_error_while_gathering_duts() -> Result<()> { - let run = TestRun::builder("failed run", "1.0").build(); - ocptv_error!(run, "no-dut", "could not find any valid DUTs").await?; - - Ok(()) -} - -/// Show outputting an error message, triggered by a specific software component of the DUT. -#[cfg(feature = "boxed-scopes")] -async fn demo_run_error_with_dut() -> Result<()> { - let mut dut = DutInfo::builder("dut0").name("dut0.server.net").build(); - let sw_info = dut.add_software_info( - SoftwareInfo::builder("bmc") - .software_type(SoftwareType::Firmware) - .version("2.5") - .build(), - ); - - TestRun::builder("with dut", "1.0") - .build() - .scope(dut, |r| { - async move { - r.add_error_with_details( - &tv::Error::builder("power-fail") - .add_software_info(&sw_info) - .build(), - ) - .await?; - - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Fail, - }) - } - .boxed() - }) - .await?; - - Ok(()) -} - -#[tokio::main] -async fn main() { - run_demo!(demo_no_scopes); - run_demo!(demo_error_while_gathering_duts); - - #[cfg(feature = "boxed-scopes")] - { - run_demo!(demo_scope_run_skip); - run_demo!(demo_scope_step_fail); - run_demo!(demo_run_error_with_dut); - } -} diff --git a/examples/simple_error_while_gathering_duts.rs b/examples/simple_error_while_gathering_duts.rs new file mode 100644 index 0000000..fd6dad8 --- /dev/null +++ b/examples/simple_error_while_gathering_duts.rs @@ -0,0 +1,22 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; + +use ocptv::ocptv_error; +use ocptv::output as tv; +use tv::TestRun; + +/// In case of failure to discover DUT hardware before needing to present it at test run +/// start, we can error out right at the beginning since no Diagnosis can be produced. +/// This is a framework failure. +#[tokio::main] +async fn main() -> Result<()> { + let run = TestRun::builder("error while gathering duts", "1.0").build(); + ocptv_error!(run, "no-dut", "could not find any valid DUTs").await?; + + Ok(()) +} diff --git a/examples/simple_no_scopes.rs b/examples/simple_no_scopes.rs new file mode 100644 index 0000000..eac82e5 --- /dev/null +++ b/examples/simple_no_scopes.rs @@ -0,0 +1,31 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; + +use ocptv::ocptv_log_debug; +use ocptv::output as tv; +use tv::{DutInfo, TestResult, TestRun, TestStatus}; + +/// Show that a run/step can be manually started and ended. +/// +/// The scope version should be preferred, as it makes it safer not to miss the end +/// artifacts in case of unhandled exceptions or code misuse. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + let run = TestRun::builder("no scopes", "1.0") + .build() + .start(dut) + .await?; + + let step = run.add_step("step0").start().await?; + ocptv_log_debug!(step, "Some interesting message.").await?; + step.end(TestStatus::Complete).await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) +} diff --git a/examples/simple_run_error_with_dut.rs b/examples/simple_run_error_with_dut.rs new file mode 100644 index 0000000..6ba979c --- /dev/null +++ b/examples/simple_run_error_with_dut.rs @@ -0,0 +1,46 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use tv::{DutInfo, TestResult, TestRun, TestStatus}; +use tv::{SoftwareInfo, SoftwareType, TestRunOutcome}; + +/// Show outputting an error message, triggered by a specific software component of the DUT. +#[tokio::main] +async fn main() -> Result<()> { + let mut dut = DutInfo::builder("dut0").name("dut0.server.net").build(); + let sw_info = dut.add_software_info( + SoftwareInfo::builder("bmc") + .software_type(SoftwareType::Firmware) + .version("2.5") + .build(), + ); + + TestRun::builder("run error with dut", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_error_with_details( + &tv::Error::builder("power-fail") + .add_software_info(&sw_info) + .build(), + ) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/examples/simple_run_skip.rs b/examples/simple_run_skip.rs new file mode 100644 index 0000000..f9f28f8 --- /dev/null +++ b/examples/simple_run_skip.rs @@ -0,0 +1,34 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use tv::TestRunOutcome; +use tv::{DutInfo, TestResult, TestRun, TestStatus}; + +/// Show a context-scoped run that automatically exits the whole func +/// because of the marker exception that triggers SKIP outcome. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + TestRun::builder("run skip", "1.0") + .build() + .scope(dut, |_r| { + async move { + // intentional short return + return Ok(TestRunOutcome { + status: TestStatus::Skip, + result: TestResult::NotApplicable, + }); + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/examples/simple_step_fail.rs b/examples/simple_step_fail.rs new file mode 100644 index 0000000..80cb4d4 --- /dev/null +++ b/examples/simple_step_fail.rs @@ -0,0 +1,47 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::ocptv_log_info; +use ocptv::output as tv; +use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; + +/// Show a scoped run with scoped steps, everything starts at "with" time and +/// ends automatically when the block ends (regardless of unhandled exceptions). +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + TestRun::builder("step fail", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| { + async move { + ocptv_log_info!(s, "info log").await?; + Ok(TestStatus::Complete) + } + .boxed() + }) + .await?; + + r.add_step("step1") + .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From 703da5de972ca47fc0d115d3cfdb3c9d1f168b14 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Wed, 9 Oct 2024 18:43:30 +0100 Subject: [PATCH 52/96] add simple measurement example Signed-off-by: mimir-d --- ..._duts.rs => error_while_gathering_duts.rs} | 0 ...un_error_with_dut.rs => error_with_dut.rs} | 0 examples/measurement_single.rs | 47 +++++++++++++++++++ 3 files changed, 47 insertions(+) rename examples/{simple_error_while_gathering_duts.rs => error_while_gathering_duts.rs} (100%) rename examples/{simple_run_error_with_dut.rs => error_with_dut.rs} (100%) create mode 100644 examples/measurement_single.rs diff --git a/examples/simple_error_while_gathering_duts.rs b/examples/error_while_gathering_duts.rs similarity index 100% rename from examples/simple_error_while_gathering_duts.rs rename to examples/error_while_gathering_duts.rs diff --git a/examples/simple_run_error_with_dut.rs b/examples/error_with_dut.rs similarity index 100% rename from examples/simple_run_error_with_dut.rs rename to examples/error_with_dut.rs diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs new file mode 100644 index 0000000..03ee25b --- /dev/null +++ b/examples/measurement_single.rs @@ -0,0 +1,47 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; + +use futures::FutureExt; +use ocptv::output as tv; +use tv::{DutInfo, Measurement, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus}; + +async fn run_measure_step(step: &StartedTestStep) -> Result { + step.add_measurement("temperature", 42.5.into()).await?; + step.add_measurement_with_details( + &Measurement::builder("fan_speed", 1200.into()) + .unit("rpm") + .build(), + ) + .await?; + + Ok(TestStatus::Complete) +} + +/// Simple demo with some measurements taken but not referencing DUT hardware. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| run_measure_step(s).boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From 5d7cc0384f9e0986b71519097964e8812be9e897 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 10:59:03 +0100 Subject: [PATCH 53/96] add measurement series example - this required some refactoring to enable users to apply a different measurement timestamp than the artifact emission timestamp (defaults to the same value as `now()`) - replaced the `_with_metadata` flavor with `_with_details`, which contains both this new timestamp spec and the prev metadata - fixed tests and coverage Signed-off-by: mimir-d --- examples/error_with_dut.rs | 2 + examples/measurement_series.rs | 123 ++++++++++++ examples/measurement_single.rs | 3 + examples/simple_run_skip.rs | 3 + examples/simple_step_fail.rs | 3 + src/output/measure.rs | 357 +++++++++++++++++---------------- src/output/mod.rs | 5 +- src/output/run.rs | 2 +- src/output/step.rs | 39 ++-- tests/output/runner.rs | 230 ++++++++++----------- 10 files changed, 445 insertions(+), 322 deletions(-) create mode 100644 examples/measurement_series.rs diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index 6ba979c..bcb10a6 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(warnings)] use anyhow::Result; use futures::FutureExt; @@ -22,6 +23,7 @@ async fn main() -> Result<()> { .build(), ); + #[cfg(feature = "boxed-scopes")] TestRun::builder("run error with dut", "1.0") .build() .scope(dut, |r| { diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs new file mode 100644 index 0000000..ee3647d --- /dev/null +++ b/examples/measurement_series.rs @@ -0,0 +1,123 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +#![allow(warnings)] + +use anyhow::Result; + +use chrono::Duration; +use futures::FutureExt; +use ocptv::output::{self as tv}; +use tv::{ + DutInfo, MeasurementSeriesElemDetails, MeasurementSeriesInfo, StartedTestStep, TestResult, + TestRun, TestRunOutcome, TestStatus, +}; + +async fn step0_measurements(step: &StartedTestStep) -> Result { + let fan_speed = step + .add_measurement_series_with_details( + MeasurementSeriesInfo::builder("fan_speed") + .unit("rpm") + .build(), + ) + .start() + .await?; + + fan_speed.add_measurement(1000.into()).await?; + fan_speed.add_measurement(1200.into()).await?; + fan_speed.add_measurement(1500.into()).await?; + + fan_speed.end().await?; + Ok(TestStatus::Complete) +} + +#[cfg(feature = "boxed-scopes")] +async fn step1_measurements(step: &StartedTestStep) -> Result { + step.add_measurement_series_with_details( + MeasurementSeriesInfo::builder("temp0").unit("C").build(), + ) + .scope(|s| { + async move { + let two_seconds_ago = + chrono::Local::now().with_timezone(&chrono_tz::UTC) - Duration::seconds(2); + s.add_measurement_with_details( + MeasurementSeriesElemDetails::builder(42.into()) + .timestamp(two_seconds_ago) + .build(), + ) + .await?; + + s.add_measurement(43.into()).await?; + Ok(()) + } + .boxed() + }) + .await?; + + Ok(TestStatus::Complete) +} + +async fn step2_measurements(step: &StartedTestStep) -> Result { + let freq0 = step + .add_measurement_series_with_details( + MeasurementSeriesInfo::builder("freq0").unit("hz").build(), + ) + .start() + .await?; + + let freq1 = step + .add_measurement_series_with_details( + MeasurementSeriesInfo::builder("freq0").unit("hz").build(), + ) + .start() + .await?; + + freq0.add_measurement(1.0.into()).await?; + freq1.add_measurement(2.0.into()).await?; + freq0.add_measurement(1.2.into()).await?; + + freq0.end().await?; + freq1.end().await?; + Ok(TestStatus::Complete) +} + +/// Show various patterns of time measurement series. +/// +/// Step0 has a single series, manually ended. +/// Step1 has a single series but using a scope, so series ends automatically. +/// Step2 shows multiple measurement interspersed series, they can be concurrent. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] + TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| step0_measurements(s).boxed()) + .await?; + + #[cfg(feature = "boxed-scopes")] + r.add_step("step1") + .scope(|s| step1_measurements(s).boxed()) + .await?; + + r.add_step("step2") + .scope(|s| step2_measurements(s).boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index 03ee25b..f8ee4ae 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(warnings)] use anyhow::Result; @@ -26,6 +27,8 @@ async fn run_measure_step(step: &StartedTestStep) -> Result Result<()> { let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| { diff --git a/examples/simple_run_skip.rs b/examples/simple_run_skip.rs index f9f28f8..2aa2be3 100644 --- a/examples/simple_run_skip.rs +++ b/examples/simple_run_skip.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(warnings)] use anyhow::Result; use futures::FutureExt; @@ -16,6 +17,8 @@ use tv::{DutInfo, TestResult, TestRun, TestStatus}; #[tokio::main] async fn main() -> Result<()> { let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] TestRun::builder("run skip", "1.0") .build() .scope(dut, |_r| { diff --git a/examples/simple_step_fail.rs b/examples/simple_step_fail.rs index 80cb4d4..54c0901 100644 --- a/examples/simple_step_fail.rs +++ b/examples/simple_step_fail.rs @@ -3,6 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +#![allow(warnings)] use anyhow::Result; use futures::FutureExt; @@ -16,6 +17,8 @@ use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; #[tokio::main] async fn main() -> Result<()> { let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] TestRun::builder("step fail", "1.0") .build() .scope(dut, |r| { diff --git a/src/output/measure.rs b/src/output/measure.rs index cda2147..ac81e34 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -14,35 +14,34 @@ use maplit::{btreemap, convert_args}; use crate::output as tv; use crate::spec; -use tv::{dut, step}; +use tv::{dut, step, Ident}; + +use super::trait_ext::VecExt; /// The measurement series. /// A Measurement Series is a time-series list of measurements. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart pub struct MeasurementSeries { - start: MeasurementSeriesStart, + id: String, + info: MeasurementSeriesInfo, emitter: Arc, } impl MeasurementSeries { - pub(crate) fn new(series_id: &str, name: &str, emitter: Arc) -> Self { + pub(crate) fn new( + series_id: &str, + info: MeasurementSeriesInfo, + emitter: Arc, + ) -> Self { Self { - start: MeasurementSeriesStart::new(name, series_id), + id: series_id.to_owned(), + info, emitter, } } - // TODO: we should allow the user to start a series with details, but still have the series id on - // an auto-generator, since it's more of a spec detail - pub(crate) fn new_with_details( - start: MeasurementSeriesStart, - emitter: Arc, - ) -> Self { - Self { start, emitter } - } - /// Starts the measurement series. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart @@ -64,10 +63,23 @@ impl MeasurementSeries { /// # }); /// ``` pub async fn start(self) -> Result { + let info = &self.info; + + let start = spec::MeasurementSeriesStart { + name: info.name.clone(), + unit: info.unit.clone(), + series_id: self.id.clone(), + validators: info.validators.map_option(Validator::to_spec), + hardware_info: info + .hardware_info + .as_ref() + .map(dut::DutHardwareInfo::to_spec), + subcomponent: info.subcomponent.as_ref().map(dut::Subcomponent::to_spec), + metadata: info.metadata.clone(), + }; + self.emitter - .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart( - self.start.to_artifact(), - )) + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesStart(start)) .await?; Ok(StartedMeasurementSeries { @@ -153,7 +165,7 @@ impl StartedMeasurementSeries { /// ``` pub async fn end(self) -> Result<(), tv::OcptvError> { let end = spec::MeasurementSeriesEnd { - series_id: self.parent.start.series_id.clone(), + series_id: self.parent.id.clone(), total_count: self.seqno.load(Ordering::Acquire), }; @@ -186,26 +198,15 @@ impl StartedMeasurementSeries { /// # }); /// ``` pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError> { - let element = spec::MeasurementSeriesElement { - index: self.incr_seqno(), - value: value.clone(), - timestamp: self.parent.emitter.timestamp_provider().now(), - series_id: self.parent.start.series_id.clone(), - metadata: None, - }; - - self.parent - .emitter - .emit(&spec::TestStepArtifactImpl::MeasurementSeriesElement( - element, - )) - .await?; - - Ok(()) + self.add_measurement_with_details(MeasurementSeriesElemDetails { + value, + ..Default::default() + }) + .await } /// Adds a measurement element to the measurement series. - /// This method accepts additional metadata to add to the element. + /// This method accepts a full set of details for the measurement element. /// /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement /// @@ -220,27 +221,24 @@ impl StartedMeasurementSeries { /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; - /// series.add_measurement_with_metadata(60.into(), vec![("key", "value".into())]).await?; + /// let elem = MeasurementSeriesElemDetails::builder(60.into()).add_metadata("key", "value".into()).build(); + /// series.add_measurement_with_details(elem).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement_with_metadata( + pub async fn add_measurement_with_details( &self, - value: tv::Value, - metadata: Vec<(&str, tv::Value)>, + details: MeasurementSeriesElemDetails, ) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), - value: value.clone(), - timestamp: self.parent.emitter.timestamp_provider().now(), - series_id: self.parent.start.series_id.clone(), - metadata: Some( - metadata - .iter() - .map(|(k, v)| (k.to_string(), v.clone())) - .collect(), - ), + value: details.value, + timestamp: details + .timestamp + .unwrap_or(self.parent.emitter.timestamp_provider().now()), + series_id: self.parent.id.clone(), + metadata: details.metadata, }; self.parent @@ -254,6 +252,63 @@ impl StartedMeasurementSeries { } } +#[derive(Default)] +pub struct MeasurementSeriesElemDetails { + value: tv::Value, + timestamp: Option>, + + metadata: Option>, +} + +impl MeasurementSeriesElemDetails { + pub fn builder(value: tv::Value) -> MeasurementSeriesElemDetailsBuilder { + MeasurementSeriesElemDetailsBuilder::new(value) + } +} + +#[derive(Default)] +pub struct MeasurementSeriesElemDetailsBuilder { + value: tv::Value, + timestamp: Option>, + + metadata: Option>, +} + +impl MeasurementSeriesElemDetailsBuilder { + pub fn new(value: tv::Value) -> Self { + Self { + value, + ..Default::default() + } + } + + pub fn timestamp(mut self, value: chrono::DateTime) -> Self { + self.timestamp = Some(value); + self + } + + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { + self.metadata = match self.metadata { + Some(mut metadata) => { + metadata.insert(key.to_string(), value); + Some(metadata) + } + None => Some(convert_args!(btreemap!( + key => value, + ))), + }; + self + } + + pub fn build(self) -> MeasurementSeriesElemDetails { + MeasurementSeriesElemDetails { + value: self.value, + timestamp: self.timestamp, + metadata: self.metadata, + } + } +} + #[derive(Clone)] pub struct Validator { name: Option, @@ -612,12 +667,12 @@ impl MeasurementBuilder { } } -pub struct MeasurementSeriesStart { - series_id: String, +pub struct MeasurementSeriesInfo { + pub(crate) id: tv::Ident, name: String, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, @@ -625,51 +680,23 @@ pub struct MeasurementSeriesStart { metadata: Option>, } -impl MeasurementSeriesStart { - pub fn new(name: &str, series_id: &str) -> MeasurementSeriesStart { - MeasurementSeriesStart { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } +impl MeasurementSeriesInfo { + pub fn new(name: &str) -> MeasurementSeriesInfo { + MeasurementSeriesInfoBuilder::new(name).build() } - pub fn builder(name: &str, series_id: &str) -> MeasurementSeriesStartBuilder { - MeasurementSeriesStartBuilder::new(name, series_id) - } - - pub fn to_artifact(&self) -> spec::MeasurementSeriesStart { - spec::MeasurementSeriesStart { - name: self.name.clone(), - unit: self.unit.clone(), - series_id: self.series_id.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), - hardware_info: self - .hardware_info - .as_ref() - .map(dut::DutHardwareInfo::to_spec), - subcomponent: self - .subcomponent - .as_ref() - .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), - } + pub fn builder(name: &str) -> MeasurementSeriesInfoBuilder { + MeasurementSeriesInfoBuilder::new(name) } } -pub struct MeasurementSeriesStartBuilder { - series_id: String, +#[derive(Default)] +pub struct MeasurementSeriesInfoBuilder { + id: tv::Ident, name: String, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, @@ -677,33 +704,34 @@ pub struct MeasurementSeriesStartBuilder { metadata: Option>, } -impl MeasurementSeriesStartBuilder { - pub fn new(name: &str, series_id: &str) -> Self { - MeasurementSeriesStartBuilder { +impl MeasurementSeriesInfoBuilder { + pub fn new(name: &str) -> Self { + MeasurementSeriesInfoBuilder { + id: Ident::Auto, name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, + ..Default::default() } } - pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesStartBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; + + pub fn id(mut self, id: tv::Ident) -> MeasurementSeriesInfoBuilder { + self.id = id; + self + } + + pub fn unit(mut self, unit: &str) -> MeasurementSeriesInfoBuilder { + self.unit = Some(unit.to_string()); + self + } + + pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesInfoBuilder { + self.validators.push(validator.clone()); self } pub fn hardware_info( mut self, hardware_info: &dut::DutHardwareInfo, - ) -> MeasurementSeriesStartBuilder { + ) -> MeasurementSeriesInfoBuilder { self.hardware_info = Some(hardware_info.clone()); self } @@ -711,34 +739,29 @@ impl MeasurementSeriesStartBuilder { pub fn subcomponent( mut self, subcomponent: &dut::Subcomponent, - ) -> MeasurementSeriesStartBuilder { + ) -> MeasurementSeriesInfoBuilder { self.subcomponent = Some(subcomponent.clone()); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesStartBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesInfoBuilder { self.metadata = match self.metadata { Some(mut metadata) => { metadata.insert(key.to_string(), value.clone()); Some(metadata) } None => Some(convert_args!(btreemap!( - key => value, + key => value ))), }; self } - pub fn unit(mut self, unit: &str) -> MeasurementSeriesStartBuilder { - self.unit = Some(unit.to_string()); - self - } - - pub fn build(self) -> MeasurementSeriesStart { - MeasurementSeriesStart { + pub fn build(self) -> MeasurementSeriesInfo { + MeasurementSeriesInfo { + id: self.id, name: self.name, unit: self.unit, - series_id: self.series_id, validators: self.validators, hardware_info: self.hardware_info, subcomponent: self.subcomponent, @@ -824,68 +847,46 @@ mod tests { Ok(()) } - #[test] - fn test_measurement_series_start_to_artifact() -> Result<()> { - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let series = MeasurementSeriesStart::new(&name, &series_id); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - spec::MeasurementSeriesStart { - name: name.to_string(), - unit: None, - series_id: series_id.to_string(), - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, - } - ); - - Ok(()) - } - - #[test] - fn test_measurement_series_start_builder_to_artifact() -> Result<()> { - let mut dut = DutInfo::new("dut0"); - - let name = "name".to_owned(); - let series_id = "series_id".to_owned(); - let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); - let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); - let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); - let subcomponent = Subcomponent::builder("name").build(); - let series = MeasurementSeriesStart::builder(&name, &series_id) - .unit("unit") - .add_validator(&validator) - .add_validator(&validator2) - .hardware_info(&hw_info) - .subcomponent(&subcomponent) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .build(); - - let artifact = series.to_artifact(); - assert_eq!( - artifact, - spec::MeasurementSeriesStart { - series_id: series_id.to_string(), - name, - unit: Some("unit".to_string()), - validators: Some(vec![validator.to_spec(), validator2.to_spec()]), - hardware_info: Some(hw_info.to_spec()), - subcomponent: Some(subcomponent.to_spec()), - metadata: Some(convert_args!(btreemap!( - "key" => "value", - "key2" => "value2", - ))), - } - ); - - Ok(()) - } + // #[test] + // fn test_measurement_series_start_builder_to_artifact() -> Result<()> { + // let mut dut = DutInfo::new("dut0"); + + // let name = "name".to_owned(); + // let series_id = "series_id".to_owned(); + // let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); + // let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); + // let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); + // let subcomponent = Subcomponent::builder("name").build(); + // let series = MeasurementSeriesInfo::builder(&name) + // .id(Ident::Exact(series_id.clone())) + // .unit("unit") + // .add_validator(&validator) + // .add_validator(&validator2) + // .hardware_info(&hw_info) + // .subcomponent(&subcomponent) + // .add_metadata("key", "value".into()) + // .add_metadata("key2", "value2".into()) + // .build(); + + // let artifact = series.to_spec(); + // assert_eq!( + // artifact, + // spec::MeasurementSeriesStart { + // series_id: series_id.to_string(), + // name, + // unit: Some("unit".to_string()), + // validators: Some(vec![validator.to_spec(), validator2.to_spec()]), + // hardware_info: Some(hw_info.to_spec()), + // subcomponent: Some(subcomponent.to_spec()), + // metadata: Some(convert_args!(btreemap!( + // "key" => "value", + // "key2" => "value2", + // ))), + // } + // ); + + // Ok(()) + // } #[test] fn test_validator() -> Result<()> { diff --git a/src/output/mod.rs b/src/output/mod.rs index e1df9c8..c527bfe 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -29,8 +29,9 @@ pub use dut::{ pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; pub use measure::{ - Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesStart, - MeasurementSeriesStartBuilder, StartedMeasurementSeries, Validator, ValidatorBuilder, + Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesElemDetails, + MeasurementSeriesInfo, MeasurementSeriesInfoBuilder, StartedMeasurementSeries, Validator, + ValidatorBuilder, }; pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; pub use step::{StartedTestStep, TestStep}; diff --git a/src/output/run.rs b/src/output/run.rs index 9cc639d..e91ed4a 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -520,7 +520,7 @@ impl StartedTestRun { /// Create a new step for this test run. /// TODO: docs + example pub fn add_step(&self, name: &str) -> TestStep { - let step_id = format!("step_{}", self.step_seqno.fetch_add(1, Ordering::AcqRel)); + let step_id = format!("step{}", self.step_seqno.fetch_add(1, Ordering::AcqRel)); TestStep::new(&step_id, name, Arc::clone(&self.run.emitter)) } } diff --git a/src/output/step.rs b/src/output/step.rs index 162f7c6..3c570f8 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -13,10 +13,12 @@ use futures::future::BoxFuture; use crate::output as tv; use crate::spec::{self, TestStepArtifactImpl, TestStepStart}; -use tv::measure::MeasurementSeries; -use tv::{config, emitter, error, log, measure}; - -use super::OcptvError; +use tv::OcptvError; +use tv::{ + config, emitter, error, log, + measure::{self, MeasurementSeries, MeasurementSeriesInfo}, + Ident, +}; /// A single test step in the scope of a [`TestRun`]. /// @@ -64,7 +66,7 @@ impl TestStep { Ok(StartedTestStep { step: self, - measurement_id_seqno: Arc::new(atomic::AtomicU64::new(0)), + measurement_seqno: Arc::new(atomic::AtomicU64::new(0)), }) } @@ -114,7 +116,7 @@ impl TestStep { pub struct StartedTestStep { step: TestStep, - measurement_id_seqno: Arc, + measurement_seqno: Arc, } impl StartedTestStep { @@ -514,12 +516,7 @@ impl StartedTestStep { /// # }); /// ``` pub fn add_measurement_series(&self, name: &str) -> MeasurementSeries { - let series_id: String = format!( - "series_{}", - self.measurement_id_seqno.fetch_add(1, Ordering::AcqRel) - ); - - MeasurementSeries::new(&series_id, name, Arc::clone(&self.step.emitter)) + self.add_measurement_series_with_details(MeasurementSeriesInfo::new(name)) } /// Create a Measurement Series (a time-series list of measurements). @@ -537,16 +534,28 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// let series = - /// step.add_measurement_series_with_details(MeasurementSeriesStart::new("name", "series_id")); + /// step.add_measurement_series_with_details(MeasurementSeriesInfo::new("name")); /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` pub fn add_measurement_series_with_details( &self, - start: measure::MeasurementSeriesStart, + info: measure::MeasurementSeriesInfo, ) -> MeasurementSeries { - MeasurementSeries::new_with_details(start, Arc::clone(&self.step.emitter)) + // spec says this identifier is unique in the scope of the test run, so create it from + // the step identifier and a counter + // ref: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/README.md#measurementseriesstart + let series_id = match &info.id { + Ident::Auto => format!( + "{}_series{}", + self.step.emitter.step_id, + self.measurement_seqno.fetch_add(1, Ordering::AcqRel) + ), + Ident::Exact(value) => value.to_owned(), + }; + + MeasurementSeries::new(&series_id, info, Arc::clone(&self.step.emitter)) } } diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 502c523..9a958c0 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -12,6 +12,7 @@ use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; +use ocptv::output::MeasurementSeriesElemDetails; use serde_json::json; use tokio::sync::Mutex; @@ -21,7 +22,7 @@ use ocptv::output::OcptvError; use tv::TestRunOutcome; use tv::{ Config, DutInfo, Error, HardwareInfo, Ident, Log, LogSeverity, Measurement, - MeasurementSeriesStart, SoftwareInfo, SoftwareType, StartedTestRun, StartedTestStep, + MeasurementSeriesInfo, SoftwareInfo, SoftwareType, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, Validator, ValidatorType, }; @@ -96,7 +97,7 @@ fn json_step_default_start() -> serde_json::Value { // seqno for the default test run start is always 2 json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "testStepStart": { "name": "first step" } @@ -109,7 +110,7 @@ fn json_step_default_start() -> serde_json::Value { fn json_step_complete(seqno: i32) -> serde_json::Value { json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "testStepEnd": { "status": "COMPLETE" } @@ -514,7 +515,7 @@ async fn test_testrun_step_log() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO" @@ -550,7 +551,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO", @@ -592,7 +593,7 @@ async fn test_testrun_step_error() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "error": { "symptom": "symptom" } @@ -623,7 +624,7 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "error": { "message": "Error message", "symptom": "symptom" @@ -655,7 +656,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "error": { "message": "Error message", "softwareInfoIds": [ @@ -702,7 +703,7 @@ async fn test_testrun_step_scope_log() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "log": { "message": "This is a log message with INFO severity", "severity": "INFO" @@ -745,7 +746,7 @@ async fn test_step_with_measurement() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurement": { "name": "name", "value": 50 @@ -777,7 +778,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurement": { "name": "name", "value": 50, @@ -790,7 +791,8 @@ async fn test_step_with_measurement_builder() -> Result<()> { "name": "name" }, "metadata": { - "key": "value" + "key": "value", + "key2": "value2" } } }, @@ -808,6 +810,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { let measurement = Measurement::builder("name", 50.into()) .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .hardware_info(hw_info) .subcomponent(&Subcomponent::builder("name").build()) .build(); @@ -828,9 +831,9 @@ async fn test_step_with_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -839,9 +842,9 @@ async fn test_step_with_measurement_series() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 0 } }, @@ -872,9 +875,9 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -883,9 +886,9 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 0 } }, @@ -894,9 +897,9 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "step0_series1", "name": "name" } }, @@ -905,9 +908,9 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_1", + "measurementSeriesId": "step0_series1", "totalCount": 0 } }, @@ -941,59 +944,11 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", - "measurementSeriesStart": { - "measurementSeriesId": "series_id", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step_0", - "measurementSeriesEnd": { - "measurementSeriesId": "series_id", "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s - .add_measurement_series_with_details(MeasurementSeriesStart::new( - "name", - "series_id", - )) - .start() - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_with_details_and_start_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { "measurementSeriesId": "series_id", "name": "name", + "unit": "unit", "validators": [{ "type": "EQUAL", "value": 30 @@ -1003,7 +958,8 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R "name": "name" }, "metadata": { - "key": "value" + "key": "value", + "key2": "value2" } } }, @@ -1012,7 +968,7 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { "measurementSeriesId": "series_id", "totalCount": 0 @@ -1031,8 +987,11 @@ async fn test_step_with_measurement_series_with_details_and_start_builder() -> R let series = s .add_measurement_series_with_details( - MeasurementSeriesStart::builder("name", "series_id") + MeasurementSeriesInfo::builder("name") + .id(Ident::Exact("series_id".to_owned())) + .unit("unit") .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) .hardware_info(hw_info) .subcomponent(&Subcomponent::builder("name").build()) @@ -1057,9 +1016,9 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -1068,10 +1027,10 @@ async fn test_step_with_measurement_series_element() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 60, "timestamp": DATETIME_FORMATTED } @@ -1081,9 +1040,9 @@ async fn test_step_with_measurement_series_element() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 1 } }, @@ -1115,9 +1074,9 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -1126,10 +1085,10 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 60, "timestamp": DATETIME_FORMATTED } @@ -1139,10 +1098,10 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 70, "timestamp": DATETIME_FORMATTED } @@ -1152,10 +1111,10 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 80, "timestamp": DATETIME_FORMATTED } @@ -1165,9 +1124,9 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 3 } }, @@ -1195,16 +1154,16 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { } #[tokio::test] -async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> { +async fn test_step_with_measurement_series_element_with_details() -> Result<()> { let expected = [ json_schema_version(), json_run_default_start(), json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -1213,12 +1172,13 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "metadata": { - "key": "value" + "key": "value", + "key2": "value2" }, "value": 60, "timestamp": DATETIME_FORMATTED, @@ -1229,9 +1189,9 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 1 } }, @@ -1246,7 +1206,13 @@ async fn test_step_with_measurement_series_element_with_metadata() -> Result<()> async { let series = s.add_measurement_series("name").start().await?; series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(60.into()) + .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .build(), + ) .await?; series.end().await?; @@ -1265,9 +1231,9 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -1276,10 +1242,10 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "metadata": {"key": "value"}, "value": 60, "timestamp": DATETIME_FORMATTED, @@ -1290,10 +1256,10 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "metadata": {"key2": "value2"}, "value": 70, "timestamp": DATETIME_FORMATTED, @@ -1304,10 +1270,10 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "metadata": {"key3": "value3"}, "value": 80, "timestamp": DATETIME_FORMATTED, @@ -1318,9 +1284,9 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 3 } }, @@ -1336,13 +1302,25 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series - .add_measurement_with_metadata(60.into(), vec![("key", "value".into())]) + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(60.into()) + .add_metadata("key", "value".into()) + .build(), + ) .await?; series - .add_measurement_with_metadata(70.into(), vec![("key2", "value2".into())]) + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(70.into()) + .add_metadata("key2", "value2".into()) + .build(), + ) .await?; series - .add_measurement_with_metadata(80.into(), vec![("key3", "value3".into())]) + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(80.into()) + .add_metadata("key3", "value3".into()) + .build(), + ) .await?; series.end().await?; @@ -1362,9 +1340,9 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesStart": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "name": "name" } }, @@ -1373,10 +1351,10 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 0, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 60, "timestamp": DATETIME_FORMATTED } @@ -1386,10 +1364,10 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 1, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 70, "timestamp": DATETIME_FORMATTED } @@ -1399,10 +1377,10 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesElement": { "index": 2, - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "value": 80, "timestamp": DATETIME_FORMATTED } @@ -1412,9 +1390,9 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { }), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "measurementSeriesEnd": { - "measurementSeriesId": "series_0", + "measurementSeriesId": "step0_series0", "totalCount": 3 } }, @@ -1529,7 +1507,7 @@ async fn test_step_with_extension() -> Result<()> { json_step_default_start(), json!({ "testStepArtifact": { - "testStepId": "step_0", + "testStepId": "step0", "extension": { "name": "extension", "content": { From 04e939edd16621e23f98f691cc35b7f51a1a4ebb Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 11:11:29 +0100 Subject: [PATCH 54/96] add extensions example - rename api method for consistency with the others (needed verb) Signed-off-by: mimir-d --- examples/extensions.rs | 67 ++++++++++++++++++++++++++++++++++++++++++ src/output/step.rs | 4 +-- tests/output/runner.rs | 4 +-- 3 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 examples/extensions.rs diff --git a/examples/extensions.rs b/examples/extensions.rs new file mode 100644 index 0000000..9b23fa5 --- /dev/null +++ b/examples/extensions.rs @@ -0,0 +1,67 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +#![allow(warnings)] + +use anyhow::Result; +use futures::FutureExt; +use serde::Serialize; + +use ocptv::output as tv; +use tv::{DutInfo, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus}; + +#[derive(Serialize)] +enum ExtensionType { + Example, +} + +#[derive(Serialize)] +struct ComplexExtension { + #[serde(rename = "@type")] + ext_type: ExtensionType, + + field: String, + subtypes: Vec, +} + +async fn step0(s: &StartedTestStep) -> Result { + s.add_extension("simple", "extension_identifier").await?; + + s.add_extension( + "complex", + ComplexExtension { + ext_type: ExtensionType::Example, + field: "demo".to_owned(), + subtypes: vec![1, 42], + }, + ) + .await?; + + Ok(TestStatus::Complete) +} + +/// Showcase how to output a vendor specific test step extension. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] + TestRun::builder("extensions", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0").scope(|s| step0(s).boxed()).await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/src/output/step.rs b/src/output/step.rs index 3c570f8..8c2ddad 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -396,12 +396,12 @@ impl StartedTestStep { /// #[derive(serde::Serialize)] /// struct Ext { i: u32 } /// - /// step.extension("ext_name", Ext { i: 42 }).await?; + /// step.add_extension("ext_name", Ext { i: 42 }).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn extension( + pub async fn add_extension( &self, name: &str, any: S, diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 9a958c0..c93abf7 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -1536,7 +1536,7 @@ async fn test_step_with_extension() -> Result<()> { check_output_step(&expected, |s, _| { async { - s.extension( + s.add_extension( "extension", Ext { r#type: "TestExtension".to_owned(), @@ -1588,7 +1588,7 @@ async fn test_step_with_extension_which_fails() -> Result<()> { .await?; let step = run.add_step("first step").start().await?; - let result = step.extension("extension", Ext { i: 0 }).await; + let result = step.add_extension("extension", Ext { i: 0 }).await; match result { Err(OcptvError::Format(e)) => { From 4c72ae2b0e088bff6da59bc9633067dc228f0134 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 11:33:21 +0100 Subject: [PATCH 55/96] add custom writer example Signed-off-by: mimir-d --- examples/custom_writer.rs | 66 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 examples/custom_writer.rs diff --git a/examples/custom_writer.rs b/examples/custom_writer.rs new file mode 100644 index 0000000..510ac25 --- /dev/null +++ b/examples/custom_writer.rs @@ -0,0 +1,66 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +#![allow(warnings)] + +use anyhow::Result; +use async_trait::async_trait; +use futures::FutureExt; +use std::io; +use tokio::sync::mpsc; + +use ocptv::ocptv_log_debug; +use ocptv::output as tv; +use tv::{Config, DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus, Writer}; + +struct Channel { + tx: mpsc::Sender, +} + +#[async_trait] +impl Writer for Channel { + async fn write(&self, s: &str) -> Result<(), io::Error> { + self.tx.send(s.to_owned()).await.map_err(io::Error::other)?; + Ok(()) + } +} + +#[tokio::main] +async fn main() -> Result<()> { + #[cfg(feature = "boxed-scopes")] + { + let (tx, mut rx) = mpsc::channel::(1); + let task = tokio::spawn(async move { + while let Some(s) = rx.recv().await { + println!("{}", s); + } + }); + + let config = Config::builder() + .with_custom_output(Box::new(Channel { tx })) + .build(); + + let dut = DutInfo::builder("dut0").build(); + + TestRun::builder("extensions", "1.0") + .config(config) + .build() + .scope(dut, |r| { + async move { + ocptv_log_debug!(r, "log debug").await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + task.await?; + } + Ok(()) +} From 31f6c8a836b81c6513f8631fc9d3a9d4acb3f1d5 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 12:56:00 +0100 Subject: [PATCH 56/96] add example with measurement validators - fix typos in ValidatorType Signed-off-by: mimir-d --- examples/measurement_validators.rs | 80 ++++++++++++++++++++++++++++++ src/spec.rs | 6 +-- 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 examples/measurement_validators.rs diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs new file mode 100644 index 0000000..b036227 --- /dev/null +++ b/examples/measurement_validators.rs @@ -0,0 +1,80 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// #![allow(warnings)] + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output::{self as tv, MeasurementSeriesInfo}; +use tv::{ + DutInfo, Measurement, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus, + Validator, ValidatorType, +}; + +async fn run_measure_step(step: &StartedTestStep) -> Result { + step.add_measurement_with_details( + &Measurement::builder("temp", 40.into()) + .add_validator( + &Validator::builder(ValidatorType::GreaterThan, 30.into()) + .name("gt_30") + .build(), + ) + .build(), + ) + .await?; + + step.add_measurement_series_with_details( + MeasurementSeriesInfo::builder("fan_speed") + .unit("rpm") + .add_validator(&Validator::builder(ValidatorType::LessThanOrEqual, 3000.into()).build()) + .build(), + ) + .scope(|s| { + async move { + s.add_measurement(1000.into()).await?; + + Ok(()) + } + .boxed() + }) + .await?; + + step.add_measurement_with_details( + &Measurement::builder("fan_speed", 1200.into()) + .unit("rpm") + .build(), + ) + .await?; + + Ok(TestStatus::Complete) +} + +/// Showcase a measurement item and series, both using validators to document +/// what the diagnostic package actually validated. +#[tokio::main] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + + #[cfg(feature = "boxed-scopes")] + TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| run_measure_step(s).boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/src/spec.rs b/src/spec.rs index 5aacee5..8c39ff1 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -68,11 +68,11 @@ pub enum ValidatorType { #[serde(rename = "LESS_THAN")] LessThan, #[serde(rename = "LESS_THAN_OR_EQUAL")] - LessThenOrEqual, + LessThanOrEqual, #[serde(rename = "GREATER_THAN")] - GreaterThen, + GreaterThan, #[serde(rename = "GREATER_THAN_OR_EQUAL")] - GreaterThenOrEqual, + GreaterThanOrEqual, #[serde(rename = "REGEX_MATCH")] RegexMatch, #[serde(rename = "REGEX_NO_MATCH")] From 5d1f8faeeefe69de517a420e729fdd0f7d14e243 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 13:08:21 +0100 Subject: [PATCH 57/96] add example for measurement with subcomponents - export missing SubcomponentType, non_exhaustive Signed-off-by: mimir-d --- examples/measurement_subcomponent.rs | 112 +++++++++++++++++++++++++++ src/output/dut.rs | 16 +++- src/output/mod.rs | 4 +- src/spec.rs | 1 + 4 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 examples/measurement_subcomponent.rs diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs new file mode 100644 index 0000000..58447e0 --- /dev/null +++ b/examples/measurement_subcomponent.rs @@ -0,0 +1,112 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. +// #![allow(warnings)] + +use anyhow::Result; + +use futures::FutureExt; +use ocptv::output as tv; +use tv::{ + DutHardwareInfo, DutInfo, HardwareInfo, Measurement, MeasurementSeriesInfo, PlatformInfo, + SoftwareInfo, StartedTestStep, SubcomponentType, TestResult, TestRun, TestRunOutcome, + TestStatus, +}; + +async fn run_measure_step( + step: &StartedTestStep, + ram0: DutHardwareInfo, +) -> Result { + step.add_measurement_with_details( + &Measurement::builder("temp0", 100.5.into()) + .unit("F") + .hardware_info(&ram0) + .subcomponent(&tv::Subcomponent::builder("chip0").build()) + .build(), + ) + .await?; + + let chip1_temp = step.add_measurement_series_with_details( + MeasurementSeriesInfo::builder("temp1") + .unit("C") + .hardware_info(&ram0) + .subcomponent( + &tv::Subcomponent::builder("chip1") + .location("U11") + .version("1") + .revision("1") + .subcomponent_type(SubcomponentType::Unspecified) + .build(), + ) + .build(), + ); + + chip1_temp + .scope(|s| { + async move { + s.add_measurement(79.into()).await?; + + Ok(()) + } + .boxed() + }) + .await?; + + Ok(TestStatus::Complete) +} + +/// This is a more comprehensive example with a DUT having both hardware and software +/// components specified. The measurements reference the hardware items. +#[tokio::main] +async fn main() -> Result<()> { + let mut dut = DutInfo::builder("dut0") + .name("host0.example.com") + .add_platform_info(&PlatformInfo::new("memory-optimized")) + .build(); + + dut.add_software_info( + SoftwareInfo::builder("bmc0") + .software_type(tv::SoftwareType::Firmware) + .version("10") + .revision("11") + .computer_system("primary_node") + .build(), + ); + + let ram0 = dut.add_hardware_info( + HardwareInfo::builder("ram0") + .version("1") + .revision("2") + .location("MB/DIMM_A1") + .serial_no("HMA2022029281901") + .part_no("P03052-091") + .manufacturer("hynix") + .manufacturer_part_no("HMA84GR7AFR4N-VK") + .odata_id("/redfish/v1/Systems/System.Embedded.1/Memory/DIMMSLOTA1") + .computer_system("primary_node") + .manager("bmc0") + .build(), + ); + + #[cfg(feature = "boxed-scopes")] + TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| run_measure_step(s, ram0).boxed()) + .await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} diff --git a/src/output/dut.rs b/src/output/dut.rs index 6fc8a2a..ed8777a 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -210,6 +210,12 @@ pub struct PlatformInfo { } impl PlatformInfo { + pub fn new(info: &str) -> Self { + Self { + info: info.to_owned(), + } + } + pub fn builder(info: &str) -> PlatformInfoBuilder { PlatformInfoBuilder::new(info) } @@ -645,9 +651,15 @@ mod tests { } #[test] - fn test_platform_info() -> Result<()> { - let info = PlatformInfo::builder("info").build(); + fn test_platform_info_new() -> Result<()> { + let info = PlatformInfo::new("info"); + assert_eq!(info.to_spec().info, "info"); + Ok(()) + } + #[test] + fn test_platform_info_builder() -> Result<()> { + let info = PlatformInfo::builder("info").build(); assert_eq!(info.to_spec().info, "info"); Ok(()) } diff --git a/src/output/mod.rs b/src/output/mod.rs index c527bfe..29f857b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -18,13 +18,13 @@ mod trait_ext; mod writer; pub use crate::spec::{ - LogSeverity, SoftwareType, TestResult, TestStatus, ValidatorType, SPEC_VERSION, + LogSeverity, SoftwareType, TestResult, TestStatus, ValidatorType, SubcomponentType, SPEC_VERSION, }; pub use config::{Config, ConfigBuilder, TimestampProvider}; pub use dut::{ DutHardwareInfo, DutInfo, DutInfoBuilder, DutSoftwareInfo, HardwareInfo, HardwareInfoBuilder, Ident, PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, - SubcomponentBuilder, + SubcomponentBuilder }; pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; diff --git a/src/spec.rs b/src/spec.rs index 8c39ff1..8fcdd0e 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -84,6 +84,7 @@ pub enum ValidatorType { } #[derive(Debug, Serialize, Clone, PartialEq)] +#[non_exhaustive] pub enum SubcomponentType { #[serde(rename = "UNSPECIFIED")] Unspecified, From f9b6dd75b0c8754d9eb34d960c92967e37ddf320 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 13:19:52 +0100 Subject: [PATCH 58/96] prettify examples; remove cfg attr Signed-off-by: mimir-d --- Cargo.toml | 36 ++++++++++++++ examples/custom_writer.rs | 67 ++++++++++++-------------- examples/error_while_gathering_duts.rs | 3 +- examples/error_with_dut.rs | 12 ++--- examples/extensions.rs | 12 ++--- examples/measurement_series.rs | 36 +++++++------- examples/measurement_single.rs | 16 +++--- examples/measurement_subcomponent.rs | 27 +++++------ examples/measurement_validators.rs | 28 +++++------ examples/simple_no_scopes.rs | 6 +-- examples/simple_run_skip.rs | 11 ++--- examples/simple_step_fail.rs | 10 ++-- 12 files changed, 140 insertions(+), 124 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d5dc5c7..5326df2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,3 +43,39 @@ tokio-test = "0.4.4" unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage,coverage_nightly)', ] } + +[[example]] +name = "custom_writer" +required-features = ["boxed-scopes"] + +[[example]] +name = "error_with_dut" +required-features = ["boxed-scopes"] + +[[example]] +name = "extensions" +required-features = ["boxed-scopes"] + +[[example]] +name = "measurement_series" +required-features = ["boxed-scopes"] + +[[example]] +name = "measurement_single" +required-features = ["boxed-scopes"] + +[[example]] +name = "measurement_subcomponent" +required-features = ["boxed-scopes"] + +[[example]] +name = "measurement_validators" +required-features = ["boxed-scopes"] + +[[example]] +name = "simple_run_skip" +required-features = ["boxed-scopes"] + +[[example]] +name = "simple_step_fail" +required-features = ["boxed-scopes"] diff --git a/examples/custom_writer.rs b/examples/custom_writer.rs index 510ac25..bc2dee4 100644 --- a/examples/custom_writer.rs +++ b/examples/custom_writer.rs @@ -3,24 +3,24 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] + +use std::io; use anyhow::Result; use async_trait::async_trait; use futures::FutureExt; -use std::io; use tokio::sync::mpsc; use ocptv::ocptv_log_debug; use ocptv::output as tv; -use tv::{Config, DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus, Writer}; +use tv::{TestResult, TestStatus}; struct Channel { tx: mpsc::Sender, } #[async_trait] -impl Writer for Channel { +impl tv::Writer for Channel { async fn write(&self, s: &str) -> Result<(), io::Error> { self.tx.send(s.to_owned()).await.map_err(io::Error::other)?; Ok(()) @@ -29,38 +29,35 @@ impl Writer for Channel { #[tokio::main] async fn main() -> Result<()> { - #[cfg(feature = "boxed-scopes")] - { - let (tx, mut rx) = mpsc::channel::(1); - let task = tokio::spawn(async move { - while let Some(s) = rx.recv().await { - println!("{}", s); + let (tx, mut rx) = mpsc::channel::(1); + let task = tokio::spawn(async move { + while let Some(s) = rx.recv().await { + println!("{}", s); + } + }); + + let config = tv::Config::builder() + .with_custom_output(Box::new(Channel { tx })) + .build(); + + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("extensions", "1.0") + .config(config) + .build() + .scope(dut, |r| { + async move { + ocptv_log_debug!(r, "log debug").await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) } - }); - - let config = Config::builder() - .with_custom_output(Box::new(Channel { tx })) - .build(); + .boxed() + }) + .await?; - let dut = DutInfo::builder("dut0").build(); - - TestRun::builder("extensions", "1.0") - .config(config) - .build() - .scope(dut, |r| { - async move { - ocptv_log_debug!(r, "log debug").await?; - - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() - }) - .await?; - - task.await?; - } + task.await?; Ok(()) } diff --git a/examples/error_while_gathering_duts.rs b/examples/error_while_gathering_duts.rs index fd6dad8..d5cf42e 100644 --- a/examples/error_while_gathering_duts.rs +++ b/examples/error_while_gathering_duts.rs @@ -8,14 +8,13 @@ use anyhow::Result; use ocptv::ocptv_error; use ocptv::output as tv; -use tv::TestRun; /// In case of failure to discover DUT hardware before needing to present it at test run /// start, we can error out right at the beginning since no Diagnosis can be produced. /// This is a framework failure. #[tokio::main] async fn main() -> Result<()> { - let run = TestRun::builder("error while gathering duts", "1.0").build(); + let run = tv::TestRun::builder("error while gathering duts", "1.0").build(); ocptv_error!(run, "no-dut", "could not find any valid DUTs").await?; Ok(()) diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index bcb10a6..652e0cf 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -3,28 +3,26 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; -use tv::{DutInfo, TestResult, TestRun, TestStatus}; -use tv::{SoftwareInfo, SoftwareType, TestRunOutcome}; +use tv::{SoftwareType, TestResult, TestStatus}; /// Show outputting an error message, triggered by a specific software component of the DUT. #[tokio::main] async fn main() -> Result<()> { - let mut dut = DutInfo::builder("dut0").name("dut0.server.net").build(); + let mut dut = tv::DutInfo::builder("dut0").name("dut0.server.net").build(); let sw_info = dut.add_software_info( - SoftwareInfo::builder("bmc") + tv::SoftwareInfo::builder("bmc") .software_type(SoftwareType::Firmware) .version("2.5") .build(), ); #[cfg(feature = "boxed-scopes")] - TestRun::builder("run error with dut", "1.0") + tv::TestRun::builder("run error with dut", "1.0") .build() .scope(dut, |r| { async move { @@ -35,7 +33,7 @@ async fn main() -> Result<()> { ) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Fail, }) diff --git a/examples/extensions.rs b/examples/extensions.rs index 9b23fa5..1b46a3f 100644 --- a/examples/extensions.rs +++ b/examples/extensions.rs @@ -3,14 +3,13 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; use futures::FutureExt; use serde::Serialize; use ocptv::output as tv; -use tv::{DutInfo, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus}; +use tv::{TestResult, TestStatus}; #[derive(Serialize)] enum ExtensionType { @@ -26,7 +25,7 @@ struct ComplexExtension { subtypes: Vec, } -async fn step0(s: &StartedTestStep) -> Result { +async fn step0(s: &tv::StartedTestStep) -> Result { s.add_extension("simple", "extension_identifier").await?; s.add_extension( @@ -45,16 +44,15 @@ async fn step0(s: &StartedTestStep) -> Result { /// Showcase how to output a vendor specific test step extension. #[tokio::main] async fn main() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("extensions", "1.0") + tv::TestRun::builder("extensions", "1.0") .build() .scope(dut, |r| { async move { r.add_step("step0").scope(|s| step0(s).boxed()).await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Pass, }) diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index ee3647d..cfb046a 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -6,19 +6,16 @@ #![allow(warnings)] use anyhow::Result; - use chrono::Duration; use futures::FutureExt; + use ocptv::output::{self as tv}; -use tv::{ - DutInfo, MeasurementSeriesElemDetails, MeasurementSeriesInfo, StartedTestStep, TestResult, - TestRun, TestRunOutcome, TestStatus, -}; +use tv::{TestResult, TestStatus}; -async fn step0_measurements(step: &StartedTestStep) -> Result { +async fn step0_measurements(step: &tv::StartedTestStep) -> Result { let fan_speed = step .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("fan_speed") + tv::MeasurementSeriesInfo::builder("fan_speed") .unit("rpm") .build(), ) @@ -34,16 +31,18 @@ async fn step0_measurements(step: &StartedTestStep) -> Result Result { +async fn step1_measurements(step: &tv::StartedTestStep) -> Result { step.add_measurement_series_with_details( - MeasurementSeriesInfo::builder("temp0").unit("C").build(), + tv::MeasurementSeriesInfo::builder("temp0") + .unit("C") + .build(), ) .scope(|s| { async move { let two_seconds_ago = chrono::Local::now().with_timezone(&chrono_tz::UTC) - Duration::seconds(2); s.add_measurement_with_details( - MeasurementSeriesElemDetails::builder(42.into()) + tv::MeasurementSeriesElemDetails::builder(42.into()) .timestamp(two_seconds_ago) .build(), ) @@ -59,17 +58,21 @@ async fn step1_measurements(step: &StartedTestStep) -> Result Result { +async fn step2_measurements(step: &tv::StartedTestStep) -> Result { let freq0 = step .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("freq0").unit("hz").build(), + tv::MeasurementSeriesInfo::builder("freq0") + .unit("hz") + .build(), ) .start() .await?; let freq1 = step .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("freq0").unit("hz").build(), + tv::MeasurementSeriesInfo::builder("freq0") + .unit("hz") + .build(), ) .start() .await?; @@ -90,10 +93,9 @@ async fn step2_measurements(step: &StartedTestStep) -> Result Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("simple measurement", "1.0") + tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| { async move { @@ -110,7 +112,7 @@ async fn main() -> Result<()> { .scope(|s| step2_measurements(s).boxed()) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Pass, }) diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index f8ee4ae..962bbd4 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -3,18 +3,17 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; - use futures::FutureExt; + use ocptv::output as tv; -use tv::{DutInfo, Measurement, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus}; +use tv::{TestResult, TestStatus}; -async fn run_measure_step(step: &StartedTestStep) -> Result { +async fn run_measure_step(step: &tv::StartedTestStep) -> Result { step.add_measurement("temperature", 42.5.into()).await?; step.add_measurement_with_details( - &Measurement::builder("fan_speed", 1200.into()) + &tv::Measurement::builder("fan_speed", 1200.into()) .unit("rpm") .build(), ) @@ -26,10 +25,9 @@ async fn run_measure_step(step: &StartedTestStep) -> Result Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("simple measurement", "1.0") + tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| { async move { @@ -37,7 +35,7 @@ async fn main() -> Result<()> { .scope(|s| run_measure_step(s).boxed()) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Pass, }) diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 58447e0..515f5c4 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -9,18 +9,14 @@ use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; -use tv::{ - DutHardwareInfo, DutInfo, HardwareInfo, Measurement, MeasurementSeriesInfo, PlatformInfo, - SoftwareInfo, StartedTestStep, SubcomponentType, TestResult, TestRun, TestRunOutcome, - TestStatus, -}; +use tv::{SubcomponentType, TestResult, TestStatus}; async fn run_measure_step( - step: &StartedTestStep, - ram0: DutHardwareInfo, + step: &tv::StartedTestStep, + ram0: tv::DutHardwareInfo, ) -> Result { step.add_measurement_with_details( - &Measurement::builder("temp0", 100.5.into()) + &tv::Measurement::builder("temp0", 100.5.into()) .unit("F") .hardware_info(&ram0) .subcomponent(&tv::Subcomponent::builder("chip0").build()) @@ -29,7 +25,7 @@ async fn run_measure_step( .await?; let chip1_temp = step.add_measurement_series_with_details( - MeasurementSeriesInfo::builder("temp1") + tv::MeasurementSeriesInfo::builder("temp1") .unit("C") .hardware_info(&ram0) .subcomponent( @@ -61,13 +57,13 @@ async fn run_measure_step( /// components specified. The measurements reference the hardware items. #[tokio::main] async fn main() -> Result<()> { - let mut dut = DutInfo::builder("dut0") + let mut dut = tv::DutInfo::builder("dut0") .name("host0.example.com") - .add_platform_info(&PlatformInfo::new("memory-optimized")) + .add_platform_info(&tv::PlatformInfo::new("memory-optimized")) .build(); dut.add_software_info( - SoftwareInfo::builder("bmc0") + tv::SoftwareInfo::builder("bmc0") .software_type(tv::SoftwareType::Firmware) .version("10") .revision("11") @@ -76,7 +72,7 @@ async fn main() -> Result<()> { ); let ram0 = dut.add_hardware_info( - HardwareInfo::builder("ram0") + tv::HardwareInfo::builder("ram0") .version("1") .revision("2") .location("MB/DIMM_A1") @@ -90,8 +86,7 @@ async fn main() -> Result<()> { .build(), ); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("simple measurement", "1.0") + tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| { async move { @@ -99,7 +94,7 @@ async fn main() -> Result<()> { .scope(|s| run_measure_step(s, ram0).boxed()) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Pass, }) diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index b036227..54202c6 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -8,17 +8,14 @@ use anyhow::Result; use futures::FutureExt; -use ocptv::output::{self as tv, MeasurementSeriesInfo}; -use tv::{ - DutInfo, Measurement, StartedTestStep, TestResult, TestRun, TestRunOutcome, TestStatus, - Validator, ValidatorType, -}; +use ocptv::output as tv; +use tv::{TestResult, TestStatus, ValidatorType}; -async fn run_measure_step(step: &StartedTestStep) -> Result { +async fn run_measure_step(step: &tv::StartedTestStep) -> Result { step.add_measurement_with_details( - &Measurement::builder("temp", 40.into()) + &tv::Measurement::builder("temp", 40.into()) .add_validator( - &Validator::builder(ValidatorType::GreaterThan, 30.into()) + &tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) .name("gt_30") .build(), ) @@ -27,9 +24,11 @@ async fn run_measure_step(step: &StartedTestStep) -> Result Result Result Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("simple measurement", "1.0") + tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| { async move { @@ -67,7 +65,7 @@ async fn main() -> Result<()> { .scope(|s| run_measure_step(s).boxed()) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Pass, }) diff --git a/examples/simple_no_scopes.rs b/examples/simple_no_scopes.rs index eac82e5..187214b 100644 --- a/examples/simple_no_scopes.rs +++ b/examples/simple_no_scopes.rs @@ -8,7 +8,7 @@ use anyhow::Result; use ocptv::ocptv_log_debug; use ocptv::output as tv; -use tv::{DutInfo, TestResult, TestRun, TestStatus}; +use tv::{TestResult, TestStatus}; /// Show that a run/step can be manually started and ended. /// @@ -16,8 +16,8 @@ use tv::{DutInfo, TestResult, TestRun, TestStatus}; /// artifacts in case of unhandled exceptions or code misuse. #[tokio::main] async fn main() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); - let run = TestRun::builder("no scopes", "1.0") + let dut = tv::DutInfo::builder("dut0").build(); + let run = tv::TestRun::builder("no scopes", "1.0") .build() .start(dut) .await?; diff --git a/examples/simple_run_skip.rs b/examples/simple_run_skip.rs index 2aa2be3..c8b5d6d 100644 --- a/examples/simple_run_skip.rs +++ b/examples/simple_run_skip.rs @@ -3,28 +3,25 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; -use tv::TestRunOutcome; -use tv::{DutInfo, TestResult, TestRun, TestStatus}; +use tv::{TestResult, TestStatus}; /// Show a context-scoped run that automatically exits the whole func /// because of the marker exception that triggers SKIP outcome. #[tokio::main] async fn main() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("run skip", "1.0") + tv::TestRun::builder("run skip", "1.0") .build() .scope(dut, |_r| { async move { // intentional short return - return Ok(TestRunOutcome { + return Ok(tv::TestRunOutcome { status: TestStatus::Skip, result: TestResult::NotApplicable, }); diff --git a/examples/simple_step_fail.rs b/examples/simple_step_fail.rs index 54c0901..a0e9c75 100644 --- a/examples/simple_step_fail.rs +++ b/examples/simple_step_fail.rs @@ -3,23 +3,21 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; use futures::FutureExt; use ocptv::ocptv_log_info; use ocptv::output as tv; -use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; +use tv::{TestResult, TestStatus}; /// Show a scoped run with scoped steps, everything starts at "with" time and /// ends automatically when the block ends (regardless of unhandled exceptions). #[tokio::main] async fn main() -> Result<()> { - let dut = DutInfo::builder("dut0").build(); + let dut = tv::DutInfo::builder("dut0").build(); - #[cfg(feature = "boxed-scopes")] - TestRun::builder("step fail", "1.0") + tv::TestRun::builder("step fail", "1.0") .build() .scope(dut, |r| { async move { @@ -37,7 +35,7 @@ async fn main() -> Result<()> { .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) .await?; - Ok(TestRunOutcome { + Ok(tv::TestRunOutcome { status: TestStatus::Complete, result: TestResult::Fail, }) From ebed78c80853dd529d71574df6d15dddedbbe04c Mon Sep 17 00:00:00 2001 From: mimir-d Date: Thu, 10 Oct 2024 13:37:21 +0100 Subject: [PATCH 59/96] misc cleanup Signed-off-by: mimir-d --- src/output/emitter.rs | 2 +- src/output/measure.rs | 45 ++++--------------------------------------- src/output/mod.rs | 5 +++-- src/output/step.rs | 2 ++ 4 files changed, 10 insertions(+), 44 deletions(-) diff --git a/src/output/emitter.rs b/src/output/emitter.rs index 93e448f..8913e13 100644 --- a/src/output/emitter.rs +++ b/src/output/emitter.rs @@ -23,7 +23,7 @@ pub struct JsonEmitter { } impl JsonEmitter { - pub(crate) fn new( + pub fn new( timestamp_provider: Box, writer: writer::WriterType, ) -> Self { diff --git a/src/output/measure.rs b/src/output/measure.rs index ac81e34..4661945 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -30,6 +30,8 @@ pub struct MeasurementSeries { } impl MeasurementSeries { + // note: this object is crate public but users should only construct + // instances through the `StartedTestStep.add_measurement_series_*` apis pub(crate) fn new( series_id: &str, info: MeasurementSeriesInfo, @@ -668,6 +670,8 @@ impl MeasurementBuilder { } pub struct MeasurementSeriesInfo { + // note: this object is crate public and we need access to this field + // when making a new series in `StartedTestStep.add_measurement_series*` pub(crate) id: tv::Ident, name: String, @@ -847,47 +851,6 @@ mod tests { Ok(()) } - // #[test] - // fn test_measurement_series_start_builder_to_artifact() -> Result<()> { - // let mut dut = DutInfo::new("dut0"); - - // let name = "name".to_owned(); - // let series_id = "series_id".to_owned(); - // let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); - // let validator2 = Validator::builder(spec::ValidatorType::GreaterThen, 10.into()).build(); - // let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); - // let subcomponent = Subcomponent::builder("name").build(); - // let series = MeasurementSeriesInfo::builder(&name) - // .id(Ident::Exact(series_id.clone())) - // .unit("unit") - // .add_validator(&validator) - // .add_validator(&validator2) - // .hardware_info(&hw_info) - // .subcomponent(&subcomponent) - // .add_metadata("key", "value".into()) - // .add_metadata("key2", "value2".into()) - // .build(); - - // let artifact = series.to_spec(); - // assert_eq!( - // artifact, - // spec::MeasurementSeriesStart { - // series_id: series_id.to_string(), - // name, - // unit: Some("unit".to_string()), - // validators: Some(vec![validator.to_spec(), validator2.to_spec()]), - // hardware_info: Some(hw_info.to_spec()), - // subcomponent: Some(subcomponent.to_spec()), - // metadata: Some(convert_args!(btreemap!( - // "key" => "value", - // "key2" => "value2", - // ))), - // } - // ); - - // Ok(()) - // } - #[test] fn test_validator() -> Result<()> { let validator = Validator::builder(ValidatorType::Equal, 30.into()) diff --git a/src/output/mod.rs b/src/output/mod.rs index 29f857b..0e2d10b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -18,13 +18,14 @@ mod trait_ext; mod writer; pub use crate::spec::{ - LogSeverity, SoftwareType, TestResult, TestStatus, ValidatorType, SubcomponentType, SPEC_VERSION, + LogSeverity, SoftwareType, SubcomponentType, TestResult, TestStatus, ValidatorType, + SPEC_VERSION, }; pub use config::{Config, ConfigBuilder, TimestampProvider}; pub use dut::{ DutHardwareInfo, DutInfo, DutInfoBuilder, DutSoftwareInfo, HardwareInfo, HardwareInfoBuilder, Ident, PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, - SubcomponentBuilder + SubcomponentBuilder, }; pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; diff --git a/src/output/step.rs b/src/output/step.rs index 8c2ddad..dd65a79 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -30,6 +30,8 @@ pub struct TestStep { } impl TestStep { + // note: this object is crate public but users should only construct + // instances through the `StartedTestRun.add_step` api pub(crate) fn new(id: &str, name: &str, run_emitter: Arc) -> Self { TestStep { name: name.to_owned(), From 502c9eecedc3844c8751e0179795c5500fada622 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 10:49:15 +0000 Subject: [PATCH 60/96] Add Diagnosis API Signed-off-by: Giovanni Colapinto --- src/output/diagnosis.rs | 327 ++++++++++++++++++++++++++++++++++++++++ src/output/macros.rs | 52 +++++++ src/output/mod.rs | 6 +- src/output/step.rs | 77 +++++++++- src/spec.rs | 6 +- tests/output/macros.rs | 62 +++++++- tests/output/runner.rs | 75 +++++++++ 7 files changed, 599 insertions(+), 6 deletions(-) create mode 100644 src/output/diagnosis.rs diff --git a/src/output/diagnosis.rs b/src/output/diagnosis.rs new file mode 100644 index 0000000..515a94c --- /dev/null +++ b/src/output/diagnosis.rs @@ -0,0 +1,327 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use crate::output as tv; +use crate::spec; +use tv::dut; + +/// This structure represents a Diagnosis message. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis +/// +/// Information about the source file and line number are not automatically added. +/// Add them using the builder or the macros octptv_diagnosis_* +/// +/// # Examples +/// +/// ## Create a Diagnosis object with the `new` method +/// +/// ``` +/// use ocptv::output::Diagnosis; +/// use ocptv::output::DiagnosisType; +/// +/// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); +/// ``` +/// +/// ## Create a Diagnosis object with the `builder` method +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Diagnosis; +/// use ocptv::output::DiagnosisType; +/// use ocptv::output::Subcomponent; +/// +/// let diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .message("message") +/// .subcomponent(&Subcomponent::builder("name").build()) +/// .source("file.rs", 1) +/// .build(); +/// ``` +pub struct Diagnosis { + verdict: String, + diagnosis_type: spec::DiagnosisType, + message: Option, + hardware_info: Option, + subcomponent: Option, + source_location: Option, +} + +impl Diagnosis { + /// Builds a new Diagnosis object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Diagnosis; + /// use ocptv::output::DiagnosisType; + /// + /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); + /// ``` + pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { + Diagnosis { + verdict: verdict.to_string(), + diagnosis_type, + message: None, + hardware_info: None, + subcomponent: None, + source_location: None, + } + } + + /// Builds a new Diagnosis object using [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::Diagnosis; + /// use ocptv::output::DiagnosisType; + /// use ocptv::output::Subcomponent; + /// + /// let Diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()) + /// .message("message") + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .source("file.rs", 1) + /// .build(); + /// ``` + pub fn builder(verdict: &str, diagnosis_type: spec::DiagnosisType) -> DiagnosisBuilder { + DiagnosisBuilder::new(verdict, diagnosis_type) + } + + /// Creates an artifact from a Diagnosis object. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Diagnosis; + /// use ocptv::output::DiagnosisType; + /// + /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); + /// let _ = diagnosis.to_artifact(); + /// ``` + pub fn to_artifact(&self) -> spec::Diagnosis { + spec::Diagnosis { + verdict: self.verdict.clone(), + diagnosis_type: self.diagnosis_type.clone(), + message: self.message.clone(), + hardware_info_id: self + .hardware_info + .as_ref() + .map(|hardware_info| hardware_info.id().to_owned()), + subcomponent: self + .subcomponent + .as_ref() + .map(|subcomponent| subcomponent.to_spec()), + source_location: self.source_location.clone(), + } + } +} + +/// This structure builds a [`Diagnosis`] object. +/// +/// # Examples +/// +/// ``` +/// use ocptv::output::HardwareInfo; +/// use ocptv::output::Diagnosis; +/// use ocptv::output::DiagnosisType; +/// use ocptv::output::Subcomponent; +/// +/// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) +/// .hardware_info(&HardwareInfo::builder("id", "name").build()) +/// .message("message") +/// .subcomponent(&Subcomponent::builder("name").build()) +/// .source("file.rs", 1); +/// let diagnosis = builder.build(); +/// ``` +pub struct DiagnosisBuilder { + verdict: String, + diagnosis_type: spec::DiagnosisType, + message: Option, + hardware_info: Option, + subcomponent: Option, + source_location: Option, +} + +impl DiagnosisBuilder { + /// Creates a new DiagnosisBuilder. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); + /// ``` + pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { + DiagnosisBuilder { + verdict: verdict.to_string(), + diagnosis_type, + message: None, + hardware_info: None, + subcomponent: None, + source_location: None, + } + } + + /// Add a message to a [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// .message("message"); + /// ``` + pub fn message(mut self, message: &str) -> DiagnosisBuilder { + self.message = Some(message.to_string()); + self + } + + /// Add a [`HardwareInfo`] to a [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::HardwareInfo; + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// .hardware_info(&HardwareInfo::builder("id", "name").build()); + /// ``` + pub fn hardware_info(mut self, hardware_info: &dut::HardwareInfo) -> DiagnosisBuilder { + self.hardware_info = Some(hardware_info.clone()); + self + } + + /// Add a [`Subcomponent`] to a [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::Subcomponent; + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// .subcomponent(&Subcomponent::builder("name").build()); + /// ``` + pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> DiagnosisBuilder { + self.subcomponent = Some(subcomponent.clone()); + self + } + + /// Add a [`SourceLocation`] to a [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// .source("file.rs", 1); + /// ``` + pub fn source(mut self, file: &str, line: i32) -> DiagnosisBuilder { + self.source_location = Some(spec::SourceLocation { + file: file.to_string(), + line, + }); + self + } + + /// Builds a [`Diagnosis`] object from a [`DiagnosisBuilder`]. + /// + /// # Examples + /// + /// ``` + /// use ocptv::output::DiagnosisBuilder; + /// use ocptv::output::DiagnosisType; + /// + /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); + /// let diagnosis = builder.build(); + /// ``` + pub fn build(self) -> Diagnosis { + Diagnosis { + verdict: self.verdict, + diagnosis_type: self.diagnosis_type, + message: self.message, + hardware_info: self.hardware_info, + subcomponent: self.subcomponent, + source_location: self.source_location, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output as tv; + use crate::spec; + use anyhow::Result; + use tv::dut::*; + + #[test] + fn test_diagnosis_as_test_step_descendant_to_artifact() -> Result<()> { + let verdict = "verdict".to_owned(); + let diagnosis_type = spec::DiagnosisType::Pass; + let diagnosis = Diagnosis::new(&verdict, diagnosis_type.clone()); + + let artifact = diagnosis.to_artifact(); + + assert_eq!( + artifact, + spec::Diagnosis { + verdict: verdict.to_string(), + diagnosis_type, + message: None, + hardware_info_id: None, + subcomponent: None, + source_location: None, + } + ); + + Ok(()) + } + + #[test] + fn test_diagnosis_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let verdict = "verdict".to_owned(); + let diagnosis_type = spec::DiagnosisType::Pass; + let hardware_info = HardwareInfo::builder("id", "name").build(); + let subcomponent = Subcomponent::builder("name").build(); + let file = "file.rs".to_owned(); + let line = 1; + let message = "message".to_owned(); + + let diagnosis = Diagnosis::builder(&verdict, diagnosis_type.clone()) + .hardware_info(&hardware_info) + .message(&message) + .subcomponent(&subcomponent) + .source(&file, line) + .build(); + + let artifact = diagnosis.to_artifact(); + assert_eq!( + artifact, + spec::Diagnosis { + verdict, + diagnosis_type, + hardware_info_id: Some(hardware_info.to_spec().id.clone()), + subcomponent: Some(subcomponent.to_spec()), + message: Some(message), + source_location: Some(spec::SourceLocation { file, line }) + } + ); + + Ok(()) + } +} diff --git a/src/output/macros.rs b/src/output/macros.rs index 41587e5..d6ce872 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -119,3 +119,55 @@ ocptv_log!(ocptv_log_info, Info); ocptv_log!(ocptv_log_warning, Warning); ocptv_log!(ocptv_log_error, Error); ocptv_log!(ocptv_log_fatal, Fatal); + +/// The following macros emit an artifact of type Diagnosis. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis +/// +/// Equivalent to the crate::output::StartedTestStep::diagnosis_with_details method. +/// +/// They accept verdict as only parameter. +/// Information about the source file and line number is automatically added. +/// +/// There is one macro for each DiagnosisType variant: Pass, Fail, Unknown. +/// +/// # Examples +/// +/// ## DEBUG +/// +/// ```rust +/// # tokio_test::block_on(async { +/// # use ocptv::output::*; +/// +/// use ocptv::ocptv_diagnosis_pass; +/// +/// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; +/// let step = run.add_step("step_name").start().await?; +/// ocptv_diagnosis_pass!(step, "verdict"); +/// step.end(TestStatus::Complete).await?; +/// run.end(TestStatus::Complete, TestResult::Pass).await?; +/// +/// # Ok::<(), OcptvError>(()) +/// # }); +/// ``` + +macro_rules! ocptv_diagnosis { + ($name:ident, $diagnosis_type:path) => { + #[macro_export] + macro_rules! $name { + ($artifact:expr, $verdict:expr) => { + $artifact.diagnosis_with_details( + &$crate::output::Diagnosis::builder($verdict, $diagnosis_type) + .source(file!(), line!() as i32) + .build(), + ) + }; + } + }; +} + +ocptv_diagnosis!(ocptv_diagnosis_pass, ocptv::output::DiagnosisType::Pass); +ocptv_diagnosis!(ocptv_diagnosis_fail, ocptv::output::DiagnosisType::Fail); +ocptv_diagnosis!( + ocptv_diagnosis_unknown, + ocptv::output::DiagnosisType::Unknown +); diff --git a/src/output/mod.rs b/src/output/mod.rs index 65c3435..d99baa7 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -6,6 +6,7 @@ #![deny(warnings)] mod config; +mod diagnosis; mod dut; mod emitter; mod error; @@ -16,8 +17,11 @@ mod run; mod step; mod writer; -pub use crate::spec::{LogSeverity, TestResult, TestStatus, ValidatorType, SPEC_VERSION}; +pub use crate::spec::{ + DiagnosisType, LogSeverity, SourceLocation, TestResult, TestStatus, ValidatorType, SPEC_VERSION, +}; pub use config::{Config, ConfigBuilder, TimestampProvider}; +pub use diagnosis::{Diagnosis, DiagnosisBuilder}; pub use dut::{ DutInfo, DutInfoBuilder, HardwareInfo, HardwareInfoBuilder, PlatformInfo, PlatformInfoBuilder, SoftwareInfo, SoftwareInfoBuilder, Subcomponent, SubcomponentBuilder, diff --git a/src/output/step.rs b/src/output/step.rs index d776fa2..4c30297 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -14,7 +14,7 @@ use futures::future::BoxFuture; use crate::output as tv; use crate::spec::{self, TestStepArtifactImpl, TestStepStart}; use tv::measure::MeasurementSeries; -use tv::{config, emitter, error, log, measure}; +use tv::{config, diagnosis, emitter, error, log, measure}; use super::OcptvError; @@ -530,6 +530,81 @@ impl StartedTestStep { ) -> MeasurementSeries { MeasurementSeries::new_with_details(start, Arc::clone(&self.step.emitter)) } + + /// Emits a Diagnosis message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// + /// let step = run.add_step("step_name").start().await?; + /// step.diagnosis("verdict", DiagnosisType::Pass).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn diagnosis( + &self, + verdict: &str, + diagnosis_type: spec::DiagnosisType, + ) -> Result<(), tv::OcptvError> { + let diagnosis = diagnosis::Diagnosis::new(verdict, diagnosis_type); + + self.step + .emitter + .emit(&TestStepArtifactImpl::Diagnosis(diagnosis.to_artifact())) + .await?; + + Ok(()) + } + + /// Emits a Diagnosis message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let hwinfo = HardwareInfo::builder("id", "fan").build(); + /// let run = TestRun::new("diagnostic_name", "my_dut", "1.0").start().await?; + /// let step = run.add_step("step_name").start().await?; + /// + /// let diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) + /// .hardware_info(&hwinfo) + /// .message("message") + /// .subcomponent(&Subcomponent::builder("name").build()) + /// .source("file.rs", 1) + /// .build(); + /// step.diagnosis_with_details(&diagnosis).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn diagnosis_with_details( + &self, + diagnosis: &diagnosis::Diagnosis, + ) -> Result<(), tv::OcptvError> { + self.step + .emitter + .emit(&&spec::TestStepArtifactImpl::Diagnosis( + diagnosis.to_artifact(), + )) + .await?; + + Ok(()) + } } pub struct StepEmitter { diff --git a/src/spec.rs b/src/spec.rs index 8ccb114..9ee6159 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -727,11 +727,11 @@ pub struct Diagnosis { pub message: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "validators")] - pub hardware_info: Option, + #[serde(rename = "hardwareInfoId")] + pub hardware_info_id: Option, #[serde(skip_serializing_if = "Option::is_none")] - #[serde(rename = "subComponent")] + #[serde(rename = "subcomponent")] pub subcomponent: Option, #[serde(skip_serializing_if = "Option::is_none")] diff --git a/tests/output/macros.rs b/tests/output/macros.rs index e1fcf6d..d270ed6 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -10,6 +10,9 @@ use std::sync::Arc; use anyhow::anyhow; use anyhow::Result; use assert_json_diff::assert_json_include; +use ocptv::ocptv_diagnosis_fail; +use ocptv::ocptv_diagnosis_pass; +use ocptv::ocptv_diagnosis_unknown; use serde_json::json; use tokio::sync::Mutex; @@ -84,7 +87,7 @@ where Ok(()) }) .await?; - + println!("----------------------------{}", actual); let source = actual .get("testStepArtifact") .ok_or(anyhow!("testRunArtifact key does not exist"))? @@ -364,3 +367,60 @@ async fn test_ocptv_log_fatal_in_step() -> Result<()> { }) .await } + +#[tokio::test] +async fn test_ocptv_diagnosis_pass_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "diagnosis": { + "verdict": "verdict", + "type": "PASS", + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "diagnosis", |step| async move { + ocptv_diagnosis_pass!(step, "verdict").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_diagnosis_fail_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "diagnosis": { + "verdict": "verdict", + "type": "FAIL", + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "diagnosis", |step| async move { + ocptv_diagnosis_fail!(step, "verdict").await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_ocptv_diagnosis_unknown_in_step() -> Result<()> { + let expected = json!({ + "testStepArtifact": { + "diagnosis": { + "verdict": "verdict", + "type": "UNKNOWN", + } + }, + "sequenceNumber": 3 + }); + + check_output_step(&expected, "diagnosis", |step| async move { + ocptv_diagnosis_unknown!(step, "verdict").await?; + Ok(()) + }) + .await +} diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 5ace293..e889c22 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -12,6 +12,7 @@ use assert_json_diff::{assert_json_eq, assert_json_include}; use futures::future::BoxFuture; use futures::future::Future; use futures::FutureExt; +use ocptv::output::Diagnosis; use serde_json::json; use tokio::sync::Mutex; @@ -1232,6 +1233,80 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R .await } +#[tokio::test] +async fn test_step_with_diagnosis() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "diagnosis": { + "verdict": "verdict", + "type": "PASS" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |step| { + async { + step.diagnosis("verdict", tv::DiagnosisType::Pass).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_diagnosis_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step_0", + "diagnosis": { + "hardwareInfoId": "id", + "verdict": "verdict", + "type": "PASS", + "subcomponent": { + "name": "name" + }, + "message": "message" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |step| { + async { + let diagnosis = Diagnosis::builder("verdict", tv::DiagnosisType::Pass) + .hardware_info(&HardwareInfo::builder("id", "name").build()) + .subcomponent(&Subcomponent::builder("name").build()) + .message("message") + .build(); + step.diagnosis_with_details(&diagnosis).await?; + + Ok(()) + } + .boxed() + }) + .await +} + #[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_step_with_measurement_series_scope() -> Result<()> { From 2b7eb9823bcee9386200e91d955d271e09929943 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 11:00:58 +0000 Subject: [PATCH 61/96] Fix linting issues Signed-off-by: Giovanni Colapinto --- src/output/step.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/output/step.rs b/src/output/step.rs index 4c30297..8486391 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -598,7 +598,7 @@ impl StartedTestStep { ) -> Result<(), tv::OcptvError> { self.step .emitter - .emit(&&spec::TestStepArtifactImpl::Diagnosis( + .emit(&spec::TestStepArtifactImpl::Diagnosis( diagnosis.to_artifact(), )) .await?; From 66551bff93627ea962d767f42a8d68e8f277375e Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 13:35:19 +0000 Subject: [PATCH 62/96] Fixes based on review comments Signed-off-by: Giovanni Colapinto --- src/output/diagnosis.rs | 66 +++++++++++++---------------------------- src/spec.rs | 3 +- tests/output/macros.rs | 10 +++---- 3 files changed, 28 insertions(+), 51 deletions(-) diff --git a/src/output/diagnosis.rs b/src/output/diagnosis.rs index 515a94c..f14a2a3 100644 --- a/src/output/diagnosis.rs +++ b/src/output/diagnosis.rs @@ -19,8 +19,7 @@ use tv::dut; /// ## Create a Diagnosis object with the `new` method /// /// ``` -/// use ocptv::output::Diagnosis; -/// use ocptv::output::DiagnosisType; +/// # use ocptv::output::*; /// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// ``` @@ -28,10 +27,7 @@ use tv::dut; /// ## Create a Diagnosis object with the `builder` method /// /// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Diagnosis; -/// use ocptv::output::DiagnosisType; -/// use ocptv::output::Subcomponent; +/// # use ocptv::output::*; /// /// let diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -40,6 +36,7 @@ use tv::dut; /// .source("file.rs", 1) /// .build(); /// ``` +#[derive(Default)] pub struct Diagnosis { verdict: String, diagnosis_type: spec::DiagnosisType, @@ -55,19 +52,15 @@ impl Diagnosis { /// # Examples /// /// ``` - /// use ocptv::output::Diagnosis; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// ``` pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { Diagnosis { - verdict: verdict.to_string(), + verdict: verdict.to_owned(), diagnosis_type, - message: None, - hardware_info: None, - subcomponent: None, - source_location: None, + ..Default::default() } } @@ -76,10 +69,7 @@ impl Diagnosis { /// # Examples /// /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::Diagnosis; - /// use ocptv::output::DiagnosisType; - /// use ocptv::output::Subcomponent; + /// # use ocptv::output::*; /// /// let Diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -97,8 +87,7 @@ impl Diagnosis { /// # Examples /// /// ``` - /// use ocptv::output::Diagnosis; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// let _ = diagnosis.to_artifact(); @@ -126,10 +115,7 @@ impl Diagnosis { /// # Examples /// /// ``` -/// use ocptv::output::HardwareInfo; -/// use ocptv::output::Diagnosis; -/// use ocptv::output::DiagnosisType; -/// use ocptv::output::Subcomponent; +/// # use ocptv::output::*; /// /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .hardware_info(&HardwareInfo::builder("id", "name").build()) @@ -138,6 +124,7 @@ impl Diagnosis { /// .source("file.rs", 1); /// let diagnosis = builder.build(); /// ``` +#[derive(Default)] pub struct DiagnosisBuilder { verdict: String, diagnosis_type: spec::DiagnosisType, @@ -153,19 +140,15 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); /// ``` pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { DiagnosisBuilder { - verdict: verdict.to_string(), + verdict: verdict.to_owned(), diagnosis_type, - message: None, - hardware_info: None, - subcomponent: None, - source_location: None, + ..Default::default() } } @@ -174,14 +157,13 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .message("message"); /// ``` pub fn message(mut self, message: &str) -> DiagnosisBuilder { - self.message = Some(message.to_string()); + self.message = Some(message.to_owned()); self } @@ -190,9 +172,7 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::HardwareInfo; - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .hardware_info(&HardwareInfo::builder("id", "name").build()); @@ -207,9 +187,7 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::Subcomponent; - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .subcomponent(&Subcomponent::builder("name").build()); @@ -224,15 +202,14 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .source("file.rs", 1); /// ``` pub fn source(mut self, file: &str, line: i32) -> DiagnosisBuilder { self.source_location = Some(spec::SourceLocation { - file: file.to_string(), + file: file.to_owned(), line, }); self @@ -243,8 +220,7 @@ impl DiagnosisBuilder { /// # Examples /// /// ``` - /// use ocptv::output::DiagnosisBuilder; - /// use ocptv::output::DiagnosisType; + /// # use ocptv::output::*; /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); /// let diagnosis = builder.build(); @@ -280,7 +256,7 @@ mod tests { assert_eq!( artifact, spec::Diagnosis { - verdict: verdict.to_string(), + verdict: verdict.to_owned(), diagnosis_type, message: None, hardware_info_id: None, diff --git a/src/spec.rs b/src/spec.rs index 9ee6159..33399a3 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -85,9 +85,10 @@ pub enum SubcomponentType { /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype /// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json /// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis/$defs/type -#[derive(Debug, Serialize, PartialEq, Clone)] +#[derive(Debug, Serialize, PartialEq, Clone, Default)] pub enum DiagnosisType { #[serde(rename = "PASS")] + #[default] Pass, #[serde(rename = "FAIL")] Fail, diff --git a/tests/output/macros.rs b/tests/output/macros.rs index d270ed6..f4ff4e7 100644 --- a/tests/output/macros.rs +++ b/tests/output/macros.rs @@ -10,15 +10,15 @@ use std::sync::Arc; use anyhow::anyhow; use anyhow::Result; use assert_json_diff::assert_json_include; -use ocptv::ocptv_diagnosis_fail; -use ocptv::ocptv_diagnosis_pass; -use ocptv::ocptv_diagnosis_unknown; use serde_json::json; use tokio::sync::Mutex; use ocptv::ocptv_error; use ocptv::output as tv; -use ocptv::{ocptv_log_debug, ocptv_log_error, ocptv_log_fatal, ocptv_log_info, ocptv_log_warning}; +use ocptv::{ + ocptv_diagnosis_fail, ocptv_diagnosis_pass, ocptv_diagnosis_unknown, ocptv_log_debug, + ocptv_log_error, ocptv_log_fatal, ocptv_log_info, ocptv_log_warning, +}; use tv::{Config, DutInfo, StartedTestRun, StartedTestStep, TestRun}; async fn check_output( @@ -87,7 +87,7 @@ where Ok(()) }) .await?; - println!("----------------------------{}", actual); + let source = actual .get("testStepArtifact") .ok_or(anyhow!("testRunArtifact key does not exist"))? From 522cb8b292c1744ea07eaad5c16228c91b28a30f Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 13:18:23 +0000 Subject: [PATCH 63/96] Add file module Signed-off-by: mimir-d --- src/output/file.rs | 289 +++++++++++++++++++++++++++++++++++++++++ src/output/mod.rs | 2 + src/output/step.rs | 73 ++++++++++- tests/output/runner.rs | 78 ++++++++++- 4 files changed, 440 insertions(+), 2 deletions(-) create mode 100644 src/output/file.rs diff --git a/src/output/file.rs b/src/output/file.rs new file mode 100644 index 0000000..39e85c6 --- /dev/null +++ b/src/output/file.rs @@ -0,0 +1,289 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::collections::BTreeMap; + +use crate::output as tv; +use crate::spec; +use maplit::{btreemap, convert_args}; + +/// This structure represents a File message. +/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file +/// +/// # Examples +/// +/// ## Create a File object with the `new` method +/// +/// ``` +/// # use ocptv::output::*; +/// +/// let file = File::new("name", "/path/to/file", false); +/// ``` +/// +/// ## Create a File object with the `builder` method +/// +/// ``` +/// # use ocptv::output::*; +/// +/// let file = File::builder("name", "/path/to/file", false) +/// .description("description") +/// .content_type("text/plain") +/// .add_metadata("key", "value".into()) +/// .build(); +/// ``` +#[derive(Default)] +pub struct File { + name: String, + uri: String, + is_snapshot: bool, + description: Option, + content_type: Option, + metadata: Option>, +} + +impl File { + /// Builds a new File object. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let file = File::new("name", "/path/to/file", false); + /// ``` + pub fn new(name: &str, uri: &str, is_snapshot: bool) -> Self { + File { + name: name.to_owned(), + uri: uri.to_owned(), + is_snapshot, + ..Default::default() + } + } + + /// Builds a new File object using [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let file = File::builder("name", "/path/to/file", false) + /// .description("description") + /// .content_type("text/plain") + /// .add_metadata("key", "value".into()) + /// .build(); + /// ``` + pub fn builder(name: &str, uri: &str, is_snapshot: bool) -> FileBuilder { + FileBuilder::new(name, uri, is_snapshot) + } + + /// Creates an artifact from a File object. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let file = File::new("name", "/path/to/file", false); + /// let _ = file.to_artifact(); + /// ``` + pub fn to_artifact(&self) -> spec::File { + spec::File { + name: self.name.clone(), + uri: self.uri.clone(), + is_snapshot: self.is_snapshot, + description: self.description.clone(), + content_type: self.content_type.clone(), + metadata: self.metadata.clone(), + } + } +} + +/// This structure builds a [`File`] object. +/// +/// # Examples +/// +/// ``` +/// # use ocptv::output::*; +/// +/// let builder = File::builder("name", "/path/to/file", false) +/// .description("description") +/// .content_type("text/plain") +/// .add_metadata("key", "value".into()); +/// let file = builder.build(); +/// ``` +#[derive(Default)] +pub struct FileBuilder { + name: String, + uri: String, + is_snapshot: bool, + description: Option, + content_type: Option, + metadata: Option>, +} + +impl FileBuilder { + /// Creates a new FileBuilder. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let builder = FileBuilder::new("name", "/path/to/file", false); + /// ``` + pub fn new(name: &str, uri: &str, is_snapshot: bool) -> Self { + FileBuilder { + name: name.to_string(), + uri: uri.to_string(), + is_snapshot, + ..Default::default() + } + } + + /// Add a description to a [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// .description("description"); + /// ``` + pub fn description(mut self, description: &str) -> FileBuilder { + self.description = Some(description.to_owned()); + self + } + + /// Add a content_type to a [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// .content_type("text/plain"); + /// ``` + pub fn content_type(mut self, content_type: &str) -> FileBuilder { + self.content_type = Some(content_type.to_owned()); + self + } + + /// Add custom metadata to a [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// .add_metadata("key", "value".into()); + /// ``` + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> FileBuilder { + match self.metadata { + Some(ref mut metadata) => { + metadata.insert(key.to_string(), value.clone()); + } + None => { + self.metadata = Some(convert_args!(btreemap!( + key => value, + ))); + } + }; + self + } + + /// Builds a [`File`] object from a [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let builder = FileBuilder::new("name", "/path/to/file", false); + /// let file = builder.build(); + /// ``` + pub fn build(self) -> File { + File { + name: self.name, + uri: self.uri, + is_snapshot: self.is_snapshot, + description: self.description, + content_type: self.content_type, + metadata: self.metadata, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::output as tv; + use crate::spec; + use anyhow::Result; + + #[test] + fn test_file_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let uri = "uri".to_owned(); + let is_snapshot = false; + let file = File::new(&name, &uri, is_snapshot); + + let artifact = file.to_artifact(); + + assert_eq!( + artifact, + spec::File { + name, + uri, + is_snapshot, + description: None, + content_type: None, + metadata: None, + } + ); + + Ok(()) + } + + #[test] + fn test_file_builder_as_test_step_descendant_to_artifact() -> Result<()> { + let name = "name".to_owned(); + let uri = "uri".to_owned(); + let is_snapshot = false; + let description = "description".to_owned(); + let content_type = "content_type".to_owned(); + let meta_key = "key"; + let meta_value = tv::Value::from("value"); + let metadata = convert_args!(btreemap!( + meta_key => meta_value.clone(), + )); + + let file = File::builder(&name, &uri, is_snapshot) + .description(&description) + .content_type(&content_type) + .add_metadata(meta_key, meta_value.clone()) + .add_metadata(meta_key, meta_value.clone()) + .build(); + + let artifact = file.to_artifact(); + assert_eq!( + artifact, + spec::File { + name, + uri, + is_snapshot, + description: Some(description), + content_type: Some(content_type), + metadata: Some(metadata), + } + ); + + Ok(()) + } +} diff --git a/src/output/mod.rs b/src/output/mod.rs index a785466..276f6bb 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -10,6 +10,7 @@ mod diagnosis; mod dut; mod emitter; mod error; +mod file; mod log; mod macros; mod measure; @@ -30,6 +31,7 @@ pub use dut::{ SubcomponentBuilder, }; pub use error::{Error, ErrorBuilder}; +pub use file::{File, FileBuilder}; pub use log::{Log, LogBuilder}; pub use measure::{ Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesElemDetails, diff --git a/src/output/step.rs b/src/output/step.rs index 9669125..d552027 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -14,7 +14,7 @@ use futures::future::BoxFuture; use crate::output as tv; use crate::spec::{self, TestStepArtifactImpl}; use tv::OcptvError; -use tv::{config, diagnosis, emitter, error, log, measure, Ident}; +use tv::{config, diagnosis, emitter, error, file, log, measure, Ident}; /// A single test step in the scope of a [`TestRun`]. /// @@ -633,6 +633,77 @@ impl StartedTestStep { Ok(()) } + + /// Emits a File message. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// + /// let step = run.add_step("step_name").start().await?; + /// step.file("name", "/path/to/file", false).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn file( + &self, + name: &str, + uri: &str, + is_snapshot: bool, + ) -> Result<(), tv::OcptvError> { + let file = file::File::new(name, uri, is_snapshot); + + self.step + .emitter + .emit(&TestStepArtifactImpl::File(file.to_artifact())) + .await?; + + Ok(()) + } + + /// Emits a File message. + /// This method accepts a [`objects::Error`] object. + /// + /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// let step = run.add_step("step_name").start().await?; + /// + /// let file = File::builder("name", "/path/to/file", false) + /// .description("description") + /// .content_type("text/plain") + /// .add_metadata("key", "value".into()) + /// .build(); + /// step.file_with_details(&file).await?; + /// step.end(TestStatus::Complete).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn file_with_details(&self, file: &file::File) -> Result<(), tv::OcptvError> { + self.step + .emitter + .emit(&spec::TestStepArtifactImpl::File(file.to_artifact())) + .await?; + + Ok(()) + } } pub struct StepEmitter { diff --git a/tests/output/runner.rs b/tests/output/runner.rs index cd8df6c..91423b5 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -20,7 +20,7 @@ use ocptv::output::OcptvError; #[cfg(feature = "boxed-scopes")] use tv::TestRunOutcome; use tv::{ - Config, Diagnosis, DutInfo, Error, HardwareInfo, Ident, Log, LogSeverity, Measurement, + Config, Diagnosis, DutInfo, Error, File, HardwareInfo, Ident, Log, LogSeverity, Measurement, MeasurementSeriesElemDetails, MeasurementSeriesInfo, SoftwareInfo, SoftwareType, StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, Validator, ValidatorType, @@ -1404,6 +1404,82 @@ async fn test_step_with_diagnosis_builder() -> Result<()> { .await } +#[tokio::test] +async fn test_step_with_file() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "file": { + "name": "name", + "uri": "uri", + "isSnapshot": false + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async move { + s.file("name", "uri", false).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_file_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "file": { + "name": "name", + "uri": "uri", + "isSnapshot": false, + "contentType": "text/plain", + "description": "description", + "metadata": { + "key": "value" + }, + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async move { + let file = File::builder("name", "uri", false) + .content_type("text/plain") + .description("description") + .add_metadata("key", "value".into()) + .build(); + s.file_with_details(&file).await?; + + Ok(()) + } + .boxed() + }) + .await +} + #[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_step_with_measurement_series_scope() -> Result<()> { From be64c0eba73d75dfca8da95c6c37f1ed847dac2a Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 15:09:54 +0000 Subject: [PATCH 64/96] Replace String with Uri for File struct Signed-off-by: mimir-d --- Cargo.lock | 160 ++++++++++++++++++++++++++++------------- Cargo.toml | 1 + src/output/file.rs | 95 +++++++++++++++--------- src/output/mod.rs | 3 +- src/output/step.rs | 17 +++-- tests/output/runner.rs | 14 ++-- 6 files changed, 191 insertions(+), 99 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4b5837..21353b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.24.1" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] @@ -168,9 +168,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" [[package]] name = "cc" -version = "1.1.24" +version = "1.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" +checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1" dependencies = [ "shlex", ] @@ -342,11 +342,20 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -359,9 +368,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -369,15 +378,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -386,15 +395,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", @@ -403,21 +412,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -433,9 +442,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.31.0" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" @@ -508,6 +517,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "ignore" version = "0.4.23" @@ -554,9 +573,9 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "0cb94a0ffd3f3ee755c20f7d8752f45cac88605a4dcf808abcff72873296ec7b" dependencies = [ "wasm-bindgen", ] @@ -623,9 +642,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.4" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] @@ -650,16 +669,14 @@ dependencies = [ "tokio", "tokio-test", "unwrap-infallible", + "url", ] [[package]] name = "once_cell" -version = "1.20.1" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1" -dependencies = [ - "portable-atomic", -] +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "parse-zoneinfo" @@ -670,6 +687,12 @@ dependencies = [ "regex", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "phf" version = "0.11.2" @@ -720,12 +743,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" -[[package]] -name = "portable-atomic" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2" - [[package]] name = "powerfmt" version = "0.2.0" @@ -764,9 +781,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ "unicode-ident", ] @@ -1028,6 +1045,21 @@ dependencies = [ "time-core", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -1075,18 +1107,44 @@ dependencies = [ "tokio-stream", ] +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + [[package]] name = "unwrap-infallible" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "151ac09978d3c2862c4e39b557f4eceee2cc72150bc4cb4f16abf061b6e381fb" +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "walkdir" version = "2.5.0" @@ -1099,9 +1157,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "ef073ced962d62984fb38a36e5fdc1a2b23c9e0e1fa0689bb97afa4202ef6887" dependencies = [ "cfg-if", "once_cell", @@ -1110,9 +1168,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "c4bfab14ef75323f4eb75fa52ee0a3fb59611977fd3240da19b2cf36ff85030e" dependencies = [ "bumpalo", "log", @@ -1125,9 +1183,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "a7bec9830f60924d9ceb3ef99d55c155be8afa76954edffbb5936ff4509474e7" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1135,9 +1193,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "4c74f6e152a76a2ad448e223b0fc0b6b5747649c3d769cc6bf45737bf97d0ed6" dependencies = [ "proc-macro2", "quote", @@ -1148,9 +1206,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "a42f6c679374623f295a8623adfe63d9284091245c3504bde47c17a3ce2777d9" [[package]] name = "winapi-util" diff --git a/Cargo.toml b/Cargo.toml index 5326df2..6f250b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ tokio = { version = "1.40.0", features = [ "sync", ] } unwrap-infallible = "0.1.5" +url = "2.5.2" [dev-dependencies] anyhow = "1.0.89" diff --git a/src/output/file.rs b/src/output/file.rs index 39e85c6..701c8bd 100644 --- a/src/output/file.rs +++ b/src/output/file.rs @@ -20,7 +20,8 @@ use maplit::{btreemap, convert_args}; /// ``` /// # use ocptv::output::*; /// -/// let file = File::new("name", "/path/to/file", false); +/// let uri = Uri::parse("file:///tmp/foo").unwrap(); +/// let file = File::new("name", uri); /// ``` /// /// ## Create a File object with the `builder` method @@ -28,16 +29,17 @@ use maplit::{btreemap, convert_args}; /// ``` /// # use ocptv::output::*; /// -/// let file = File::builder("name", "/path/to/file", false) +/// let uri = Uri::parse("file:///tmp/foo").unwrap(); +/// let file = File::builder("name", uri) +/// .is_snapshot(true) /// .description("description") /// .content_type("text/plain") /// .add_metadata("key", "value".into()) /// .build(); /// ``` -#[derive(Default)] pub struct File { name: String, - uri: String, + uri: tv::Uri, is_snapshot: bool, description: Option, content_type: Option, @@ -52,14 +54,17 @@ impl File { /// ``` /// # use ocptv::output::*; /// - /// let file = File::new("name", "/path/to/file", false); + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let file = File::new("name", uri); /// ``` - pub fn new(name: &str, uri: &str, is_snapshot: bool) -> Self { + pub fn new(name: &str, uri: tv::Uri) -> Self { File { name: name.to_owned(), - uri: uri.to_owned(), - is_snapshot, - ..Default::default() + uri, + is_snapshot: false, + description: None, + content_type: None, + metadata: None, } } @@ -70,14 +75,15 @@ impl File { /// ``` /// # use ocptv::output::*; /// - /// let file = File::builder("name", "/path/to/file", false) + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let file = File::builder("name", uri) /// .description("description") /// .content_type("text/plain") /// .add_metadata("key", "value".into()) /// .build(); /// ``` - pub fn builder(name: &str, uri: &str, is_snapshot: bool) -> FileBuilder { - FileBuilder::new(name, uri, is_snapshot) + pub fn builder(name: &str, uri: tv::Uri) -> FileBuilder { + FileBuilder::new(name, uri) } /// Creates an artifact from a File object. @@ -87,13 +93,14 @@ impl File { /// ``` /// # use ocptv::output::*; /// - /// let file = File::new("name", "/path/to/file", false); + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let file = File::new("name", uri); /// let _ = file.to_artifact(); /// ``` pub fn to_artifact(&self) -> spec::File { spec::File { name: self.name.clone(), - uri: self.uri.clone(), + uri: self.uri.as_str().to_owned(), is_snapshot: self.is_snapshot, description: self.description.clone(), content_type: self.content_type.clone(), @@ -109,16 +116,16 @@ impl File { /// ``` /// # use ocptv::output::*; /// -/// let builder = File::builder("name", "/path/to/file", false) +/// let uri = Uri::parse("file:///tmp/foo").unwrap(); +/// let builder = File::builder("name", uri) /// .description("description") /// .content_type("text/plain") /// .add_metadata("key", "value".into()); /// let file = builder.build(); /// ``` -#[derive(Default)] pub struct FileBuilder { name: String, - uri: String, + uri: tv::Uri, is_snapshot: bool, description: Option, content_type: Option, @@ -133,17 +140,36 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// - /// let builder = FileBuilder::new("name", "/path/to/file", false); + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri); /// ``` - pub fn new(name: &str, uri: &str, is_snapshot: bool) -> Self { + pub fn new(name: &str, uri: tv::Uri) -> Self { FileBuilder { name: name.to_string(), - uri: uri.to_string(), - is_snapshot, - ..Default::default() + uri, + is_snapshot: false, + description: None, + content_type: None, + metadata: None, } } + /// Set the is_snapshot attribute in a [`FileBuilder`]. + /// + /// # Examples + /// + /// ``` + /// # use ocptv::output::*; + /// + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri) + /// .is_snapshot(true); + /// ``` + pub fn is_snapshot(mut self, value: bool) -> FileBuilder { + self.is_snapshot = value; + self + } + /// Add a description to a [`FileBuilder`]. /// /// # Examples @@ -151,7 +177,8 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// - /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri) /// .description("description"); /// ``` pub fn description(mut self, description: &str) -> FileBuilder { @@ -166,7 +193,8 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// - /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri) /// .content_type("text/plain"); /// ``` pub fn content_type(mut self, content_type: &str) -> FileBuilder { @@ -181,7 +209,8 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// - /// let builder = FileBuilder::new("name", "/path/to/file", false) + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri) /// .add_metadata("key", "value".into()); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> FileBuilder { @@ -205,7 +234,8 @@ impl FileBuilder { /// ``` /// # use ocptv::output::*; /// - /// let builder = FileBuilder::new("name", "/path/to/file", false); + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let builder = FileBuilder::new("name", uri); /// let file = builder.build(); /// ``` pub fn build(self) -> File { @@ -230,9 +260,9 @@ mod tests { #[test] fn test_file_as_test_step_descendant_to_artifact() -> Result<()> { let name = "name".to_owned(); - let uri = "uri".to_owned(); + let uri = tv::Uri::parse("file:///tmp/foo")?; let is_snapshot = false; - let file = File::new(&name, &uri, is_snapshot); + let file = File::new(&name, uri.clone()); let artifact = file.to_artifact(); @@ -240,7 +270,7 @@ mod tests { artifact, spec::File { name, - uri, + uri: uri.as_str().to_owned(), is_snapshot, description: None, content_type: None, @@ -254,7 +284,7 @@ mod tests { #[test] fn test_file_builder_as_test_step_descendant_to_artifact() -> Result<()> { let name = "name".to_owned(); - let uri = "uri".to_owned(); + let uri = tv::Uri::parse("file:///tmp/foo")?; let is_snapshot = false; let description = "description".to_owned(); let content_type = "content_type".to_owned(); @@ -264,7 +294,8 @@ mod tests { meta_key => meta_value.clone(), )); - let file = File::builder(&name, &uri, is_snapshot) + let file = File::builder(&name, uri.clone()) + .is_snapshot(is_snapshot) .description(&description) .content_type(&content_type) .add_metadata(meta_key, meta_value.clone()) @@ -276,7 +307,7 @@ mod tests { artifact, spec::File { name, - uri, + uri: uri.as_str().to_owned(), is_snapshot, description: Some(description), content_type: Some(content_type), diff --git a/src/output/mod.rs b/src/output/mod.rs index 276f6bb..7687b2b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -42,8 +42,9 @@ pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; pub use step::{StartedTestStep, TestStep}; pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; -// re-export this as a public type we present +// re-export these as a public types we present pub use serde_json::Value; +pub use url::Url as Uri; #[derive(Debug, thiserror::Error)] #[non_exhaustive] diff --git a/src/output/step.rs b/src/output/step.rs index d552027..0b611bb 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -648,19 +648,15 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.file("name", "/path/to/file", false).await?; + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// step.file("name", uri).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn file( - &self, - name: &str, - uri: &str, - is_snapshot: bool, - ) -> Result<(), tv::OcptvError> { - let file = file::File::new(name, uri, is_snapshot); + pub async fn file(&self, name: &str, uri: tv::Uri) -> Result<(), tv::OcptvError> { + let file = file::File::new(name, uri); self.step .emitter @@ -683,9 +679,12 @@ impl StartedTestStep { /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// /// let step = run.add_step("step_name").start().await?; /// - /// let file = File::builder("name", "/path/to/file", false) + /// let uri = Uri::parse("file:///tmp/foo").unwrap(); + /// let file = File::builder("name", uri) /// .description("description") /// .content_type("text/plain") /// .add_metadata("key", "value".into()) diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 91423b5..54a8180 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -1406,6 +1406,7 @@ async fn test_step_with_diagnosis_builder() -> Result<()> { #[tokio::test] async fn test_step_with_file() -> Result<()> { + let uri = tv::Uri::parse("file:///tmp/foo")?; let expected = [ json_schema_version(), json_run_default_start(), @@ -1415,7 +1416,7 @@ async fn test_step_with_file() -> Result<()> { "testStepId": "step0", "file": { "name": "name", - "uri": "uri", + "uri": uri.clone().as_str().to_owned(), "isSnapshot": false } }, @@ -1427,8 +1428,8 @@ async fn test_step_with_file() -> Result<()> { ]; check_output_step(&expected, |s, _| { - async move { - s.file("name", "uri", false).await?; + async { + s.file("name", uri).await?; Ok(()) } @@ -1439,6 +1440,7 @@ async fn test_step_with_file() -> Result<()> { #[tokio::test] async fn test_step_with_file_builder() -> Result<()> { + let uri = tv::Uri::parse("file:///tmp/foo")?; let expected = [ json_schema_version(), json_run_default_start(), @@ -1448,7 +1450,7 @@ async fn test_step_with_file_builder() -> Result<()> { "testStepId": "step0", "file": { "name": "name", - "uri": "uri", + "uri": uri.clone().as_str().to_owned(), "isSnapshot": false, "contentType": "text/plain", "description": "description", @@ -1465,8 +1467,8 @@ async fn test_step_with_file_builder() -> Result<()> { ]; check_output_step(&expected, |s, _| { - async move { - let file = File::builder("name", "uri", false) + async { + let file = File::builder("name", uri) .content_type("text/plain") .description("description") .add_metadata("key", "value".into()) From 3632d1154373024f914d73469ced576621c1d551 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Thu, 10 Oct 2024 15:49:54 +0000 Subject: [PATCH 65/96] Replace String with Mime in File.content_type Signed-off-by: mimir-d --- Cargo.lock | 7 +++++++ Cargo.toml | 1 + src/output/file.rs | 30 ++++++++++++++++++------------ src/output/mod.rs | 1 + src/output/step.rs | 3 ++- tests/output/runner.rs | 4 +++- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 21353b6..dbe433f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -610,6 +610,12 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.8.0" @@ -661,6 +667,7 @@ dependencies = [ "chrono-tz", "futures", "maplit", + "mime", "predicates", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 6f250b0..57bf0ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ chrono = "0.4.38" chrono-tz = "0.10.0" futures = "0.3.30" maplit = "1.0.2" +mime = "0.3.17" serde = { version = "1.0.210", features = ["derive"] } serde_json = "1.0.128" serde_with = "3.11.0" diff --git a/src/output/file.rs b/src/output/file.rs index 701c8bd..bfee8de 100644 --- a/src/output/file.rs +++ b/src/output/file.rs @@ -28,12 +28,13 @@ use maplit::{btreemap, convert_args}; /// /// ``` /// # use ocptv::output::*; +/// # use std::str::FromStr; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .is_snapshot(true) /// .description("description") -/// .content_type("text/plain") +/// .content_type(Mime::from_str("text/plain").unwrap()) /// .add_metadata("key", "value".into()) /// .build(); /// ``` @@ -42,7 +43,7 @@ pub struct File { uri: tv::Uri, is_snapshot: bool, description: Option, - content_type: Option, + content_type: Option, metadata: Option>, } @@ -74,11 +75,12 @@ impl File { /// /// ``` /// # use ocptv::output::*; + /// # use std::str::FromStr; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .description("description") - /// .content_type("text/plain") + /// .content_type(Mime::from_str("text/plain").unwrap()) /// .add_metadata("key", "value".into()) /// .build(); /// ``` @@ -103,7 +105,7 @@ impl File { uri: self.uri.as_str().to_owned(), is_snapshot: self.is_snapshot, description: self.description.clone(), - content_type: self.content_type.clone(), + content_type: self.content_type.as_ref().map(|ct| ct.to_string()), metadata: self.metadata.clone(), } } @@ -115,11 +117,12 @@ impl File { /// /// ``` /// # use ocptv::output::*; +/// # use std::str::FromStr; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = File::builder("name", uri) /// .description("description") -/// .content_type("text/plain") +/// .content_type(Mime::from_str("text/plain").unwrap()) /// .add_metadata("key", "value".into()); /// let file = builder.build(); /// ``` @@ -128,7 +131,7 @@ pub struct FileBuilder { uri: tv::Uri, is_snapshot: bool, description: Option, - content_type: Option, + content_type: Option, metadata: Option>, } @@ -192,13 +195,14 @@ impl FileBuilder { /// /// ``` /// # use ocptv::output::*; + /// # use std::str::FromStr; /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = FileBuilder::new("name", uri) - /// .content_type("text/plain"); + /// .content_type(Mime::from_str("text/plain").unwrap()); /// ``` - pub fn content_type(mut self, content_type: &str) -> FileBuilder { - self.content_type = Some(content_type.to_owned()); + pub fn content_type(mut self, content_type: tv::Mime) -> FileBuilder { + self.content_type = Some(content_type); self } @@ -252,6 +256,8 @@ impl FileBuilder { #[cfg(test)] mod tests { + use std::str::FromStr; + use super::*; use crate::output as tv; use crate::spec; @@ -287,7 +293,7 @@ mod tests { let uri = tv::Uri::parse("file:///tmp/foo")?; let is_snapshot = false; let description = "description".to_owned(); - let content_type = "content_type".to_owned(); + let content_type = tv::Mime::from_str("text/plain")?; let meta_key = "key"; let meta_value = tv::Value::from("value"); let metadata = convert_args!(btreemap!( @@ -297,7 +303,7 @@ mod tests { let file = File::builder(&name, uri.clone()) .is_snapshot(is_snapshot) .description(&description) - .content_type(&content_type) + .content_type(content_type.clone()) .add_metadata(meta_key, meta_value.clone()) .add_metadata(meta_key, meta_value.clone()) .build(); @@ -310,7 +316,7 @@ mod tests { uri: uri.as_str().to_owned(), is_snapshot, description: Some(description), - content_type: Some(content_type), + content_type: Some(content_type.to_string()), metadata: Some(metadata), } ); diff --git a/src/output/mod.rs b/src/output/mod.rs index 7687b2b..9bdb455 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -43,6 +43,7 @@ pub use step::{StartedTestStep, TestStep}; pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export these as a public types we present +pub use mime::Mime; pub use serde_json::Value; pub use url::Url as Uri; diff --git a/src/output/step.rs b/src/output/step.rs index 0b611bb..cd8100a 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -676,6 +676,7 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; + /// # use std::str::FromStr; /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; @@ -686,7 +687,7 @@ impl StartedTestStep { /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .description("description") - /// .content_type("text/plain") + /// .content_type(Mime::from_str("text/plain").unwrap()) /// .add_metadata("key", "value".into()) /// .build(); /// step.file_with_details(&file).await?; diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 54a8180..465c08a 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -26,6 +26,8 @@ use tv::{ TimestampProvider, Validator, ValidatorType, }; +use std::str::FromStr; + const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; struct FixedTsProvider {} @@ -1469,7 +1471,7 @@ async fn test_step_with_file_builder() -> Result<()> { check_output_step(&expected, |s, _| { async { let file = File::builder("name", uri) - .content_type("text/plain") + .content_type(tv::Mime::from_str("text/plain").unwrap()) .description("description") .add_metadata("key", "value".into()) .build(); From 9e2da56efa9d861a14fcc30080c36062528d50d6 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 01:58:37 +0100 Subject: [PATCH 66/96] fix doc formatting - fix formatting and add placeholders for missing docs - add an action to check that docs.rs will build correctly Signed-off-by: mimir-d --- .github/workflows/check.yaml | 12 ++ src/output/config.rs | 4 +- src/output/diagnosis.rs | 20 +-- src/output/dut.rs | 13 +- src/output/error.rs | 2 + src/output/log.rs | 2 + src/output/macros.rs | 130 +++++++++--------- src/output/measure.rs | 63 ++++----- src/output/mod.rs | 1 + src/output/run.rs | 46 +++---- src/output/step.rs | 76 ++++------- src/output/writer.rs | 4 + src/spec.rs | 257 +++++++++++++++++++++++------------ 13 files changed, 344 insertions(+), 286 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index ae2b71f..cd816e3 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -63,3 +63,15 @@ jobs: uses: taiki-e/install-action@cargo-hack - name: cargo hack run: cargo hack --feature-powerset --no-dev-deps check + + doc: + runs-on: ubuntu-latest + name: nightly / doc + steps: + - uses: actions/checkout@v4 + - name: Install nightly + uses: dtolnay/rust-toolchain@nightly + - name: Install cargo-docs-rs + uses: dtolnay/install@cargo-docs-rs + - name: cargo docs-rs + run: cargo docs-rs diff --git a/src/output/config.rs b/src/output/config.rs index 35c0278..984ba91 100644 --- a/src/output/config.rs +++ b/src/output/config.rs @@ -25,7 +25,6 @@ impl Config { /// # Examples /// ```rust /// # use ocptv::output::*; - /// /// let builder = Config::builder(); /// ``` pub fn builder() -> ConfigBuilder { @@ -47,7 +46,7 @@ impl ConfigBuilder { } } - // TODO: docs for all these + /// TODO: docs for all these pub fn timezone(mut self, timezone: chrono_tz::Tz) -> Self { self.timestamp_provider = Box::new(ConfiguredTzProvider { tz: timezone }); self @@ -92,6 +91,7 @@ impl ConfigBuilder { } } +/// TODO: docs pub trait TimestampProvider { fn now(&self) -> chrono::DateTime; } diff --git a/src/output/diagnosis.rs b/src/output/diagnosis.rs index f9b59ed..89c0507 100644 --- a/src/output/diagnosis.rs +++ b/src/output/diagnosis.rs @@ -9,7 +9,8 @@ use crate::spec; use tv::dut; /// This structure represents a Diagnosis message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis +/// +/// ref: /// /// Information about the source file and line number are not automatically added. /// Add them using the builder or the macros octptv_diagnosis_* @@ -20,7 +21,6 @@ use tv::dut; /// /// ``` /// # use ocptv::output::*; -/// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// ``` /// @@ -28,7 +28,6 @@ use tv::dut; /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -73,7 +72,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -94,7 +92,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; - /// /// let diagnosis = Diagnosis::new("verdict", DiagnosisType::Pass); /// let _ = diagnosis.to_artifact(); /// ``` @@ -122,7 +119,6 @@ impl Diagnosis { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -152,7 +148,6 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); /// ``` pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { @@ -169,7 +164,6 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .message("message"); /// ``` @@ -178,13 +172,12 @@ impl DiagnosisBuilder { self } - /// Add a [`HardwareInfo`] to a [`DiagnosisBuilder`]. + /// Add a [`dut::HardwareInfo`] to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -196,13 +189,12 @@ impl DiagnosisBuilder { self } - /// Add a [`Subcomponent`] to a [`DiagnosisBuilder`]. + /// Add a [`dut::Subcomponent`] to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .subcomponent(&Subcomponent::builder("name").build()); /// ``` @@ -211,13 +203,12 @@ impl DiagnosisBuilder { self } - /// Add a [`SourceLocation`] to a [`DiagnosisBuilder`]. + /// Add a source location to a [`DiagnosisBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) /// .source("file.rs", 1); /// ``` @@ -235,7 +226,6 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); /// let diagnosis = builder.build(); /// ``` diff --git a/src/output/dut.rs b/src/output/dut.rs index ed8777a..cf5607d 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -11,7 +11,7 @@ use crate::output as tv; use crate::spec; use tv::trait_ext::VecExt; -// TODO: docs +/// TODO: docs #[derive(Clone, Debug, PartialEq, Default)] pub enum Ident { #[default] @@ -83,6 +83,7 @@ impl DutInfo { } } +/// TODO: docs #[derive(Default)] pub struct DutInfoBuilder { id: String, @@ -133,6 +134,7 @@ impl DutInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct Subcomponent { subcomponent_type: Option, @@ -157,6 +159,7 @@ impl Subcomponent { } } +/// TODO: docs #[derive(Debug)] pub struct SubcomponentBuilder { subcomponent_type: Option, @@ -204,6 +207,7 @@ impl SubcomponentBuilder { } } +/// TODO: docs #[derive(Debug, Clone, PartialEq)] pub struct PlatformInfo { info: String, @@ -227,6 +231,7 @@ impl PlatformInfo { } } +/// TODO: docs #[derive(Debug)] pub struct PlatformInfoBuilder { info: String, @@ -244,6 +249,7 @@ impl PlatformInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct SoftwareInfo { id: tv::Ident, @@ -260,6 +266,7 @@ impl SoftwareInfo { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct DutSoftwareInfo { id: String, @@ -287,6 +294,7 @@ impl PartialEq for DutSoftwareInfo { } } +/// TODO: docs #[derive(Debug, Default)] pub struct SoftwareInfoBuilder { id: tv::Ident, @@ -343,6 +351,7 @@ impl SoftwareInfoBuilder { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct HardwareInfo { id: Ident, @@ -367,6 +376,7 @@ impl HardwareInfo { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct DutHardwareInfo { id: String, @@ -400,6 +410,7 @@ impl PartialEq for DutHardwareInfo { } } +/// TODO: docs #[derive(Debug, Default)] pub struct HardwareInfoBuilder { id: tv::Ident, diff --git a/src/output/error.rs b/src/output/error.rs index ab5fa61..e7a3e72 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -8,6 +8,7 @@ use crate::output as tv; use crate::spec; use tv::{dut, trait_ext::VecExt, DutSoftwareInfo}; +/// TODO: docs pub struct Error { symptom: String, message: Option, @@ -30,6 +31,7 @@ impl Error { } } +/// TODO: docs #[derive(Debug, Default)] pub struct ErrorBuilder { symptom: String, diff --git a/src/output/log.rs b/src/output/log.rs index baf8635..dec9d4a 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -6,6 +6,7 @@ use crate::spec; +/// TODO: docs pub struct Log { severity: spec::LogSeverity, message: String, @@ -26,6 +27,7 @@ impl Log { } } +/// TODO: docs #[derive(Debug)] pub struct LogBuilder { severity: spec::LogSeverity, diff --git a/src/output/macros.rs b/src/output/macros.rs index ba4196c..d56acc0 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -9,10 +9,11 @@ //! This module contains a set of macros which are exported from the ocptv //! library. -/// Emits an artifact of type Error. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error +/// Emit an artifact of type Error. /// -/// Equivalent to the crate::runner::TestRun::error_with_details method. +/// ref: +/// +/// Equivalent to the [`$crate::StartedTestRun::error_with_details`] method. /// /// It accepts both a symptom and a message, or just a symptom. /// Information about the source file and line number is automatically added. @@ -24,7 +25,6 @@ /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; -/// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -72,37 +72,36 @@ macro_rules! ocptv_error { }; } -/// The following macros emit an artifact of type Log. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log -/// -/// Equivalent to the crate::runner::TestRun::log_with_details method. -/// -/// They accept message as only parameter. -/// Information about the source file and line number is automatically added. -/// -/// There is one macro for each severity level: DEBUG, INFO, WARNING, ERROR, and FATAL. -/// -/// # Examples -/// -/// ## DEBUG -/// -/// ```rust -/// # tokio_test::block_on(async { -/// # use ocptv::output::*; -/// -/// use ocptv::ocptv_log_debug; -/// -/// let dut = DutInfo::new("my_dut"); -/// let run = TestRun::new("run_name", "1.0").start(dut).await?; -/// ocptv_log_debug!(run, "Log message"); -/// run.end(TestStatus::Complete, TestResult::Pass).await?; -/// -/// # Ok::<(), OcptvError>(()) -/// # }); -/// ``` - macro_rules! ocptv_log { ($name:ident, $severity:ident) => { + /// Emit an artifact of type Log. + /// + /// ref: + /// + /// Equivalent to the [`$crate::StartedTestRun::log_with_details`] method. + /// + /// They accept message as only parameter. + /// Information about the source file and line number is automatically added. + /// + /// There is one macro for each severity level: DEBUG, INFO, WARNING, ERROR, and FATAL. + /// + /// # Examples + /// + /// ## DEBUG + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// use ocptv::ocptv_log_debug; + /// + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("run_name", "1.0").start(dut).await?; + /// ocptv_log_debug!(run, "Log message"); + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` #[macro_export] macro_rules! $name { ($artifact:expr, $msg:expr) => { @@ -123,41 +122,40 @@ ocptv_log!(ocptv_log_warning, Warning); ocptv_log!(ocptv_log_error, Error); ocptv_log!(ocptv_log_fatal, Fatal); -/// The following macros emit an artifact of type Diagnosis. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis -/// -/// Equivalent to the crate::output::StartedTestStep::diagnosis_with_details method. -/// -/// They accept verdict as only parameter. -/// Information about the source file and line number is automatically added. -/// -/// There is one macro for each DiagnosisType variant: Pass, Fail, Unknown. -/// -/// # Examples -/// -/// ## DEBUG -/// -/// ```rust -/// # tokio_test::block_on(async { -/// # use ocptv::output::*; -/// -/// use ocptv::ocptv_diagnosis_pass; -/// -/// let dut = DutInfo::new("my dut"); -/// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; -/// -/// let step = run.add_step("step_name").start().await?; -/// ocptv_diagnosis_pass!(step, "verdict"); -/// step.end(TestStatus::Complete).await?; -/// -/// run.end(TestStatus::Complete, TestResult::Pass).await?; -/// -/// # Ok::<(), OcptvError>(()) -/// # }); -/// ``` - macro_rules! ocptv_diagnosis { ($name:ident, $diagnosis_type:path) => { + /// Emit an artifact of type Diagnosis. + /// + /// ref: + /// + /// Equivalent to the [`$crate::StartedTestStep::diagnosis_with_details`] method. + /// + /// They accept verdict as only parameter. + /// Information about the source file and line number is automatically added. + /// + /// There is one macro for each DiagnosisType variant: Pass, Fail, Unknown. + /// + /// # Examples + /// + /// ## DEBUG + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// use ocptv::ocptv_diagnosis_pass; + /// + /// let dut = DutInfo::new("my dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// + /// let step = run.add_step("step_name").start().await?; + /// ocptv_diagnosis_pass!(step, "verdict"); + /// step.end(TestStatus::Complete).await?; + /// + /// run.end(TestStatus::Complete, TestResult::Pass).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` #[macro_export] macro_rules! $name { ($artifact:expr, $verdict:expr) => { diff --git a/src/output/measure.rs b/src/output/measure.rs index 4661945..6394ef6 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -21,7 +21,7 @@ use super::trait_ext::VecExt; /// The measurement series. /// A Measurement Series is a time-series list of measurements. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart +/// ref: pub struct MeasurementSeries { id: String, info: MeasurementSeriesInfo, @@ -46,14 +46,13 @@ impl MeasurementSeries { /// Starts the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -91,7 +90,7 @@ impl MeasurementSeries { } /// Builds a scope in the [`MeasurementSeries`] object, taking care of starting and - /// ending it. View [`MeasurementSeries::start`] and [`MeasurementSeries::end`] methods. + /// ending it. View [`MeasurementSeries::start`] and [`StartedMeasurementSeries::end`] methods. /// After the scope is constructed, additional objects may be added to it. /// This is the preferred usage for the [`MeasurementSeries`], since it guarantees /// all the messages are emitted between the start and end messages, the order @@ -103,7 +102,6 @@ impl MeasurementSeries { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -134,6 +132,7 @@ impl MeasurementSeries { } } +/// TODO: docs pub struct StartedMeasurementSeries { parent: MeasurementSeries, @@ -147,14 +146,13 @@ impl StartedMeasurementSeries { /// Ends the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -181,14 +179,13 @@ impl StartedMeasurementSeries { /// Adds a measurement element to the measurement series. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -210,14 +207,13 @@ impl StartedMeasurementSeries { /// Adds a measurement element to the measurement series. /// This method accepts a full set of details for the measurement element. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -254,6 +250,7 @@ impl StartedMeasurementSeries { } } +/// TODO: docs #[derive(Default)] pub struct MeasurementSeriesElemDetails { value: tv::Value, @@ -268,6 +265,7 @@ impl MeasurementSeriesElemDetails { } } +/// TODO: docs #[derive(Default)] pub struct MeasurementSeriesElemDetailsBuilder { value: tv::Value, @@ -311,6 +309,7 @@ impl MeasurementSeriesElemDetailsBuilder { } } +/// TODO: docs #[derive(Clone)] pub struct Validator { name: Option, @@ -333,6 +332,7 @@ impl Validator { } } +/// TODO: docs #[derive(Debug)] pub struct ValidatorBuilder { name: Option, @@ -378,16 +378,14 @@ impl ValidatorBuilder { } /// This structure represents a Measurement message. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement +/// ref: /// /// # Examples /// /// ## Create a Measurement object with the `new` method /// /// ``` -/// use ocptv::output::Measurement; -/// use ocptv::output::Value; -/// +/// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// ``` /// @@ -395,7 +393,6 @@ impl ValidatorBuilder { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -425,9 +422,7 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// ``` pub fn new(name: &str, value: tv::Value) -> Self { @@ -468,9 +463,7 @@ impl Measurement { /// # Examples /// /// ``` - /// use ocptv::output::Measurement; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let measurement = Measurement::new("name", 50.into()); /// let _ = measurement.to_artifact(); /// ``` @@ -502,7 +495,6 @@ impl Measurement { /// /// ``` /// # use ocptv::output::*; -/// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -532,9 +524,7 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let builder = MeasurementBuilder::new("name", 50.into()); /// ``` pub fn new(name: &str, value: tv::Value) -> Self { @@ -555,7 +545,6 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// /// let builder = MeasurementBuilder::new("name", 50.into()) /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); /// ``` @@ -570,13 +559,12 @@ impl MeasurementBuilder { self } - /// Add a [`HardwareInfo`] to a [`MeasurementBuilder`]. + /// Add a [`tv::HardwareInfo`] to a [`MeasurementBuilder`]. /// /// # Examples /// /// ``` /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// @@ -588,15 +576,12 @@ impl MeasurementBuilder { self } - /// Add a [`Subcomponent`] to a [`MeasurementBuilder`]. + /// Add a [`tv::Subcomponent`] to a [`MeasurementBuilder`]. /// /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Subcomponent; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let builder = MeasurementBuilder::new("name", 50.into()) /// .subcomponent(&Subcomponent::builder("name").build()); /// ``` @@ -610,9 +595,7 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let builder = /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); /// ``` @@ -635,9 +618,7 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// + /// # use ocptv::output::*; /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); /// ``` pub fn unit(mut self, unit: &str) -> MeasurementBuilder { @@ -669,6 +650,7 @@ impl MeasurementBuilder { } } +/// TODO: docs pub struct MeasurementSeriesInfo { // note: this object is crate public and we need access to this field // when making a new series in `StartedTestStep.add_measurement_series*` @@ -694,6 +676,7 @@ impl MeasurementSeriesInfo { } } +/// TODO: docs #[derive(Default)] pub struct MeasurementSeriesInfoBuilder { id: tv::Ident, diff --git a/src/output/mod.rs b/src/output/mod.rs index a785466..25970f2 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -43,6 +43,7 @@ pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export this as a public type we present pub use serde_json::Value; +/// TODO: docs #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum OcptvError { diff --git a/src/output/run.rs b/src/output/run.rs index e91ed4a..2cc4b49 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -49,7 +49,6 @@ impl TestRun { /// /// ```rust /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// ``` pub fn new(name: &str, version: &str) -> TestRun { @@ -62,7 +61,6 @@ impl TestRun { /// /// ```rust /// # use ocptv::output::*; - /// /// let builder = TestRun::builder("run_name", "1.0"); /// ``` pub fn builder(name: &str, version: &str) -> TestRunBuilder { @@ -71,15 +69,13 @@ impl TestRun { /// Starts the test run. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// let dut = DutInfo::builder("my_dut").build(); /// run.start(dut).await?; @@ -117,7 +113,6 @@ impl TestRun { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let run = TestRun::new("diagnostic_name", "1.0"); /// let dut = DutInfo::builder("my_dut").build(); /// run.scope(dut, |r| { @@ -217,7 +212,6 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// /// let run = TestRunBuilder::new("run_name", "1.0") /// .add_parameter("param1", "value1".into()) /// .build(); @@ -227,14 +221,13 @@ impl TestRunBuilder { self } - /// Adds the command line used to run the test session to the future + /// Adds the command line used to run the test session to the future /// [`TestRun`] object. /// /// # Examples /// /// ```rust /// # use ocptv::output::*; - /// /// let run = TestRunBuilder::new("run_name", "1.0") /// .command_line("my_diag --arg value") /// .build(); @@ -250,7 +243,6 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// /// let run = TestRunBuilder::new("run_name", "1.0") /// .config(Config::builder().build()) /// .build(); @@ -302,7 +294,7 @@ impl TestRunBuilder { /// A test run that was started. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart +/// ref: pub struct StartedTestRun { run: TestRun, @@ -319,14 +311,13 @@ impl StartedTestRun { /// Ends the test run. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; @@ -348,17 +339,16 @@ impl StartedTestRun { } /// Emits a Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. + /// This method accepts a [`tv::LogSeverity`] to define the severity + /// and a [`String`] for the message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log( @@ -389,16 +379,15 @@ impl StartedTestRun { } /// Emits a Log message. - /// This method accepts a [`objects::Log`] object. + /// This method accepts a [`tv::Log`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log_with_details( @@ -425,16 +414,15 @@ impl StartedTestRun { } /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom. + /// This method accepts a [`String`] to define the symptom. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_error("symptom").await?; @@ -451,17 +439,16 @@ impl StartedTestRun { } /// Emits a Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. + /// This method accepts a [`String`] to define the symptom and + /// another [`String`] as error message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_error_with_msg("symptom", "error messasge").await?; @@ -478,16 +465,15 @@ impl StartedTestRun { } /// Emits a Error message. - /// This method accepts an [`error::Error`] object. + /// This method accepts an [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; diff --git a/src/output/step.rs b/src/output/step.rs index 9669125..301f4fc 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -16,9 +16,9 @@ use crate::spec::{self, TestStepArtifactImpl}; use tv::OcptvError; use tv::{config, diagnosis, emitter, error, log, measure, Ident}; -/// A single test step in the scope of a [`TestRun`]. +/// A single test step in the scope of a [`tv::TestRun`]. /// -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts +/// ref: pub struct TestStep { name: String, @@ -40,14 +40,13 @@ impl TestStep { /// Starts the test step. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -69,7 +68,7 @@ impl TestStep { } /// Builds a scope in the [`TestStep`] object, taking care of starting and - /// ending it. View [`TestStep::start`] and [`TestStep::end`] methods. + /// ending it. View [`TestStep::start`] and [`StartedTestStep::end`] methods. /// After the scope is constructed, additional objects may be added to it. /// This is the preferred usage for the [`TestStep`], since it guarantees /// all the messages are emitted between the start and end messages, the order @@ -81,7 +80,6 @@ impl TestStep { /// # tokio_test::block_on(async { /// # use futures::FutureExt; /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -112,6 +110,7 @@ impl TestStep { } } +/// TODO: docs pub struct StartedTestStep { step: TestStep, measurement_seqno: Arc, @@ -120,14 +119,13 @@ pub struct StartedTestStep { impl StartedTestStep { /// Ends the test step. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -145,17 +143,16 @@ impl StartedTestStep { } /// Emits Log message. - /// This method accepts a [`models::LogSeverity`] to define the severity - /// and a [`std::string::String`] for the message. + /// This method accepts a [`tv::LogSeverity`] to define the severity + /// and a [`String`] for the message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -174,7 +171,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_log_info; /// /// let dut = DutInfo::new("my_dut"); @@ -203,16 +199,15 @@ impl StartedTestStep { } /// Emits Log message. - /// This method accepts a [`objects::Log`] object. + /// This method accepts a [`tv::Log`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -238,16 +233,15 @@ impl StartedTestStep { } /// Emits an Error symptom. - /// This method accepts a [`std::string::String`] to define the symptom. + /// This method accepts a [`String`] to define the symptom. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -264,7 +258,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -289,17 +282,16 @@ impl StartedTestStep { } /// Emits an Error message. - /// This method accepts a [`std::string::String`] to define the symptom and - /// another [`std::string::String`] as error message. + /// This method accepts a [`String`] to define the symptom and + /// another [`String`] as error message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -316,7 +308,6 @@ impl StartedTestStep { /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// use ocptv::ocptv_error; /// /// let dut = DutInfo::new("my_dut"); @@ -341,16 +332,15 @@ impl StartedTestStep { } /// Emits a Error message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; @@ -379,14 +369,13 @@ impl StartedTestStep { /// Emits an extension message; /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -415,14 +404,13 @@ impl StartedTestStep { /// Emits a Measurement message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -451,16 +439,15 @@ impl StartedTestStep { } /// Emits a Measurement message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; @@ -494,17 +481,15 @@ impl StartedTestStep { } /// Create a Measurement Series (a time-series list of measurements). - /// This method accepts a [`std::string::String`] as series ID and - /// a [`std::string::String`] as series name. + /// This method accepts a [`String`] as series ID and a [`String`] as series name. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -518,16 +503,15 @@ impl StartedTestStep { } /// Create a Measurement Series (a time-series list of measurements). - /// This method accepts a [`objects::MeasurementSeriesStart`] object. + /// This method accepts a [`tv::MeasurementSeriesInfo`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my_dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; @@ -558,14 +542,13 @@ impl StartedTestStep { /// Emits a Diagnosis message. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let dut = DutInfo::new("my dut"); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// @@ -592,16 +575,15 @@ impl StartedTestStep { } /// Emits a Diagnosis message. - /// This method accepts a [`objects::Error`] object. + /// This method accepts a [`tv::Error`] object. /// - /// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis + /// ref: /// /// # Examples /// /// ```rust /// # tokio_test::block_on(async { /// # use ocptv::output::*; - /// /// let mut dut = DutInfo::new("my_dut"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("fan").build()); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; diff --git a/src/output/writer.rs b/src/output/writer.rs index 8c2cca1..0905075 100644 --- a/src/output/writer.rs +++ b/src/output/writer.rs @@ -14,6 +14,7 @@ use tokio::fs; use tokio::io::AsyncWriteExt; use tokio::sync::Mutex; +/// TODO: docs #[async_trait] pub trait Writer { async fn write(&self, s: &str) -> Result<(), io::Error>; @@ -28,6 +29,7 @@ pub enum WriterType { Custom(Box), } +/// TODO: docs pub struct FileWriter { file: Arc>, } @@ -53,6 +55,7 @@ impl FileWriter { } } +/// TODO: docs #[derive(Debug)] pub struct BufferWriter { buffer: Arc>>, @@ -69,6 +72,7 @@ impl BufferWriter { } } +/// TODO: docs #[derive(Debug, Clone)] pub struct StdoutWriter {} diff --git a/src/spec.rs b/src/spec.rs index a4bc678..8424fe9 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -2,7 +2,7 @@ // // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. +// use std::collections::BTreeMap; @@ -13,6 +13,7 @@ use serde_with::serde_as; use crate::output as tv; +/// TODO: docs pub const SPEC_VERSION: (i8, i8) = (2, 0); mod rfc3339_format { @@ -58,6 +59,7 @@ mod serialize_ids { } } +/// TODO: docs #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum ValidatorType { @@ -83,6 +85,7 @@ pub enum ValidatorType { NotInSet, } +/// TODO: docs #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum SubcomponentType { @@ -101,9 +104,12 @@ pub enum SubcomponentType { } /// Outcome of a diagnosis operation. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosistype -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis/$defs/type +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone, Default)] pub enum DiagnosisType { #[serde(rename = "PASS")] @@ -116,9 +122,12 @@ pub enum DiagnosisType { } /// Represents the final execution status of a test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststatus -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_status.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStatus +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testStatus")] #[non_exhaustive] @@ -132,9 +141,12 @@ pub enum TestStatus { } /// Represents the final outcome of a test execution. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testresult -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd/$defs/testResult +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testResult")] #[non_exhaustive] @@ -148,9 +160,12 @@ pub enum TestResult { } /// Known log severity variants. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#severity -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log/$defs/severity +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[non_exhaustive] pub enum LogSeverity { @@ -167,9 +182,12 @@ pub enum LogSeverity { } /// Type specification for a software component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwaretype -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo/properties/softwareType +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareType")] #[non_exhaustive] @@ -212,9 +230,12 @@ pub enum RootImpl { /// Low-level model for the `schemaVersion` spec object. /// Specifies the version that should be used to interpret following json outputs. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#schemaversion -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/root.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/output/$defs/schemaVersion +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "schemaVersion")] pub struct SchemaVersion { @@ -236,9 +257,12 @@ impl Default for SchemaVersion { /// Low-level model for the `testRunArtifact` spec object. /// Container for the run level artifacts. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-run-artifacts -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunArtifact +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestRunArtifact { #[serde(flatten)] @@ -262,9 +286,12 @@ pub enum TestRunArtifactImpl { /// Low-level model for the `testRunStart` spec object. /// Start marker for the beginning of a diagnostic test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunStart")] pub struct TestRunStart { @@ -290,9 +317,12 @@ pub struct TestRunStart { /// Low-level model for the `dutInfo` spec object. /// Contains all relevant information describing the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#dutinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "dutInfo")] pub struct DutInfo { @@ -322,9 +352,12 @@ pub struct DutInfo { /// Low-level model for the `platformInfo` spec object. /// Describe platform specific attributes of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#platforminfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/platformInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "platformInfo")] pub struct PlatformInfo { @@ -334,9 +367,12 @@ pub struct PlatformInfo { /// Low-level model for the `softwareInfo` spec object. /// Represents information of a discovered or exercised software component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#softwareinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/softwareInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "softwareInfo")] pub struct SoftwareInfo { @@ -371,9 +407,12 @@ impl serialize_ids::IdGetter for SoftwareInfo { /// Low-level model for the `hardwareInfo` spec object. /// Represents information of an enumerated or exercised hardware component of the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#hardwareinfo -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/dut_info.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/dutInfo/$defs/hardwareInfo +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "hardwareInfo")] pub struct HardwareInfo { @@ -432,9 +471,12 @@ impl serialize_ids::IdGetter for HardwareInfo { /// Low-level model for the `testRunEnd` spec object. /// End marker signaling the finality of a diagnostic test. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#testrunend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_run_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testRunEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "testRunEnd")] pub struct TestRunEnd { @@ -448,9 +490,12 @@ pub struct TestRunEnd { /// Low-level model for the `error` spec object. /// Represents an error encountered by the diagnostic software. It may refer to a DUT /// component or the diagnostic itself. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#error -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/error.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/error +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, Default, Clone, PartialEq)] #[serde(rename = "error")] @@ -474,9 +519,12 @@ pub struct Error { /// Low-level model for `log` spec object. /// Is currently relevant for test run and test step artifact types. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#log -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/log.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/log +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "log")] pub struct Log { @@ -493,9 +541,12 @@ pub struct Log { /// Provides information about which file/line of the source code in /// the diagnostic package generated the output. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#sourcelocation -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/source_location.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/sourceLocation +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, Default, PartialEq)] #[serde(rename = "sourceLocation")] pub struct SourceLocation { @@ -508,9 +559,12 @@ pub struct SourceLocation { /// Low-level model for the `testStepArtifact` spec object. /// Container for the step level artifacts. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#test-step-artifacts -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] pub struct TestStepArtifact { #[serde(rename = "testStepId")] @@ -559,9 +613,12 @@ pub enum TestStepArtifactImpl { /// Low-level model for the `testStepStart` spec object. /// Start marker for a test step inside a diagnosis run. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepStart")] pub struct TestStepStart { @@ -571,9 +628,12 @@ pub struct TestStepStart { /// Low-level model for the `testStepEnd` spec object. /// End marker for a test step inside a diagnosis run. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#teststepend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "testStepEnd")] pub struct TestStepEnd { @@ -583,9 +643,12 @@ pub struct TestStepEnd { /// Low-level model for the `measurement` spec object. /// Represents an individual measurement taken by the diagnostic regarding the DUT. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurement -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurement +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurement")] @@ -620,9 +683,12 @@ pub struct Measurement { /// Low-level model for the `validator` spec object. /// Contains the validation logic that the diagnostic applied for a specific measurement. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#validator -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/validator.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/validator +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "validator")] pub struct Validator { @@ -643,9 +709,12 @@ pub struct Validator { /// Low-level model for the `subcomponent` spec object. /// Represents a physical subcomponent of a DUT hardware element. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#subcomponent -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/subcomponent.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/subcomponent +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Clone, PartialEq)] #[serde(rename = "subcomponent")] pub struct Subcomponent { @@ -671,9 +740,12 @@ pub struct Subcomponent { /// Low-level model for the `measurementSeriesStart` spec object. /// Start marker for a time based series of measurements. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesstart -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_start.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesStart +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesStart")] @@ -708,9 +780,12 @@ pub struct MeasurementSeriesStart { /// Low-level model for the `measurementSeriesEnd` spec object. /// End marker for a time based series of measurements. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementseriesend -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_end.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesEnd +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesEnd")] pub struct MeasurementSeriesEnd { @@ -723,9 +798,12 @@ pub struct MeasurementSeriesEnd { /// Low-level model for the `measurementSeriesElement` spec object. /// Equivalent to the `Measurement` model but inside a time based series. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#measurementserieselement -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/measurement_series_element.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/measurementSeriesElement +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] #[serde(rename = "measurementSeriesElement")] pub struct MeasurementSeriesElement { @@ -748,9 +826,12 @@ pub struct MeasurementSeriesElement { /// Low-level model for the `diagnosis` spec object. /// Contains the verdict given by the diagnostic regarding the DUT that was inspected. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#diagnosis -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/diagnosis.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/diagnosis +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[serde_as] #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "diagnosis")] @@ -781,9 +862,12 @@ pub struct Diagnosis { /// Low-level model for the `file` spec object. /// Represents a file artifact that was generated by running the diagnostic. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#file -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/file.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/file +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] pub struct File { @@ -811,9 +895,12 @@ pub struct File { /// Low-level model for the `extension` spec object. /// Left as an implementation detail, the `Extension` just has a name and arbitrary data. -/// ref: https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec#extension -/// schema url: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/output/test_step_artifact.json -/// schema ref: https://github.com/opencomputeproject/ocp-diag-core/testStepArtifact/$defs/extension +/// +/// ref: +/// +/// schema url: +/// +/// schema ref: #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "extension")] pub struct Extension { From 5db1db4603b61a56c2b44e12977bebc9293fea5d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 12:24:49 +0100 Subject: [PATCH 67/96] use full path in all macros - for consistency with diagnosis macros Signed-off-by: mimir-d --- src/output/macros.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/output/macros.rs b/src/output/macros.rs index d56acc0..d3d6dce 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -73,7 +73,7 @@ macro_rules! ocptv_error { } macro_rules! ocptv_log { - ($name:ident, $severity:ident) => { + ($name:ident, $severity:path) => { /// Emit an artifact of type Log. /// /// ref: @@ -107,7 +107,7 @@ macro_rules! ocptv_log { ($artifact:expr, $msg:expr) => { $artifact.add_log_with_details( &$crate::output::Log::builder($msg) - .severity($crate::output::LogSeverity::$severity) + .severity($severity) .source(file!(), line!() as i32) .build(), ) @@ -116,11 +116,11 @@ macro_rules! ocptv_log { }; } -ocptv_log!(ocptv_log_debug, Debug); -ocptv_log!(ocptv_log_info, Info); -ocptv_log!(ocptv_log_warning, Warning); -ocptv_log!(ocptv_log_error, Error); -ocptv_log!(ocptv_log_fatal, Fatal); +ocptv_log!(ocptv_log_debug, ocptv::output::LogSeverity::Debug); +ocptv_log!(ocptv_log_info, ocptv::output::LogSeverity::Info); +ocptv_log!(ocptv_log_warning, ocptv::output::LogSeverity::Warning); +ocptv_log!(ocptv_log_error, ocptv::output::LogSeverity::Error); +ocptv_log!(ocptv_log_fatal, ocptv::output::LogSeverity::Fatal); macro_rules! ocptv_diagnosis { ($name:ident, $diagnosis_type:path) => { From 08516842f2af34251f32e91a3d68873c55a23176 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 11:29:56 +0000 Subject: [PATCH 68/96] Fix comments in PR Signed-off-by: Giovanni Colapinto --- src/output/file.rs | 19 +++++++++---------- src/output/mod.rs | 1 - src/output/step.rs | 2 +- tests/output/runner.rs | 4 +--- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/src/output/file.rs b/src/output/file.rs index bfee8de..c95ce4b 100644 --- a/src/output/file.rs +++ b/src/output/file.rs @@ -4,6 +4,7 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use mime; use std::collections::BTreeMap; use crate::output as tv; @@ -34,7 +35,7 @@ use maplit::{btreemap, convert_args}; /// let file = File::builder("name", uri) /// .is_snapshot(true) /// .description("description") -/// .content_type(Mime::from_str("text/plain").unwrap()) +/// .content_type(mime::TEXT_PLAIN) /// .add_metadata("key", "value".into()) /// .build(); /// ``` @@ -43,7 +44,7 @@ pub struct File { uri: tv::Uri, is_snapshot: bool, description: Option, - content_type: Option, + content_type: Option, metadata: Option>, } @@ -80,7 +81,7 @@ impl File { /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .description("description") - /// .content_type(Mime::from_str("text/plain").unwrap()) + /// .content_type(mime::TEXT_PLAIN) /// .add_metadata("key", "value".into()) /// .build(); /// ``` @@ -122,7 +123,7 @@ impl File { /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = File::builder("name", uri) /// .description("description") -/// .content_type(Mime::from_str("text/plain").unwrap()) +/// .content_type(mime::TEXT_PLAIN) /// .add_metadata("key", "value".into()); /// let file = builder.build(); /// ``` @@ -131,7 +132,7 @@ pub struct FileBuilder { uri: tv::Uri, is_snapshot: bool, description: Option, - content_type: Option, + content_type: Option, metadata: Option>, } @@ -199,9 +200,9 @@ impl FileBuilder { /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = FileBuilder::new("name", uri) - /// .content_type(Mime::from_str("text/plain").unwrap()); + /// .content_type(mime::TEXT_PLAIN); /// ``` - pub fn content_type(mut self, content_type: tv::Mime) -> FileBuilder { + pub fn content_type(mut self, content_type: mime::Mime) -> FileBuilder { self.content_type = Some(content_type); self } @@ -256,8 +257,6 @@ impl FileBuilder { #[cfg(test)] mod tests { - use std::str::FromStr; - use super::*; use crate::output as tv; use crate::spec; @@ -293,7 +292,7 @@ mod tests { let uri = tv::Uri::parse("file:///tmp/foo")?; let is_snapshot = false; let description = "description".to_owned(); - let content_type = tv::Mime::from_str("text/plain")?; + let content_type = mime::TEXT_PLAIN; let meta_key = "key"; let meta_value = tv::Value::from("value"); let metadata = convert_args!(btreemap!( diff --git a/src/output/mod.rs b/src/output/mod.rs index 9bdb455..7687b2b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -43,7 +43,6 @@ pub use step::{StartedTestStep, TestStep}; pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export these as a public types we present -pub use mime::Mime; pub use serde_json::Value; pub use url::Url as Uri; diff --git a/src/output/step.rs b/src/output/step.rs index cd8100a..813a89c 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -687,7 +687,7 @@ impl StartedTestStep { /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let file = File::builder("name", uri) /// .description("description") - /// .content_type(Mime::from_str("text/plain").unwrap()) + /// .content_type(mime::TEXT_PLAIN) /// .add_metadata("key", "value".into()) /// .build(); /// step.file_with_details(&file).await?; diff --git a/tests/output/runner.rs b/tests/output/runner.rs index 465c08a..4264b4b 100644 --- a/tests/output/runner.rs +++ b/tests/output/runner.rs @@ -26,8 +26,6 @@ use tv::{ TimestampProvider, Validator, ValidatorType, }; -use std::str::FromStr; - const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; struct FixedTsProvider {} @@ -1471,7 +1469,7 @@ async fn test_step_with_file_builder() -> Result<()> { check_output_step(&expected, |s, _| { async { let file = File::builder("name", uri) - .content_type(tv::Mime::from_str("text/plain").unwrap()) + .content_type(mime::TEXT_PLAIN) .description("description") .add_metadata("key", "value".into()) .build(); From e14bedfb211bd9fa94e23c767b83662ea9e195a1 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 08:41:30 +0000 Subject: [PATCH 69/96] Add Diagnosis example Signed-off-by: Giovanni Colapinto --- Cargo.lock | 69 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 ++++ examples/diagnosis.rs | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 examples/diagnosis.rs diff --git a/Cargo.lock b/Cargo.lock index f4b5837..0050f53 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.2" @@ -431,6 +437,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.0" @@ -643,6 +660,7 @@ dependencies = [ "futures", "maplit", "predicates", + "rand", "serde", "serde_json", "serde_with", @@ -732,6 +750,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "predicates" version = "3.1.2" @@ -786,6 +813,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -794,6 +833,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "regex" @@ -1097,6 +1139,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.93" @@ -1251,3 +1299,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 5326df2..90070e7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ assert_fs = "1.1.2" futures = "0.3.30" predicates = "3.1.2" tokio-test = "0.4.4" +rand = "0.8.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ @@ -79,3 +80,7 @@ required-features = ["boxed-scopes"] [[example]] name = "simple_step_fail" required-features = ["boxed-scopes"] + +[[example]] +name = "diagnosis" +required-features = ["boxed-scopes"] \ No newline at end of file diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs new file mode 100644 index 0000000..77647a6 --- /dev/null +++ b/examples/diagnosis.rs @@ -0,0 +1,54 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use rand::Rng; +use tv::{TestResult, TestStatus}; + +fn get_fan_speed() -> i32 { + let mut rng = rand::thread_rng(); + rng.gen_range(1500..1700) +} + +async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + } else { + step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + } + + Ok(TestStatus::Complete) +} + +/// Simple demo with diagnosis. +#[tokio::main] +async fn main() -> Result<()> { + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| run_diagnosis_step(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From 88ada0609c58182963a5ceb7c0a7fd6f717938c4 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 11:44:27 +0000 Subject: [PATCH 70/96] Add example with macros Signed-off-by: Giovanni Colapinto --- examples/diagnosis.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs index 77647a6..dbb72e1 100644 --- a/examples/diagnosis.rs +++ b/examples/diagnosis.rs @@ -8,6 +8,7 @@ use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; +use ocptv::{ocptv_diagnosis_fail, ocptv_diagnosis_pass}; use rand::Rng; use tv::{TestResult, TestStatus}; @@ -28,6 +29,20 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + ocptv_diagnosis_pass!(step, "fan_ok").await?; + } else { + ocptv_diagnosis_fail!(step, "fan_low").await?; + } + + Ok(TestStatus::Complete) +} + /// Simple demo with diagnosis. #[tokio::main] async fn main() -> Result<()> { @@ -40,6 +55,9 @@ async fn main() -> Result<()> { r.add_step("step0") .scope(|s| run_diagnosis_step(s).boxed()) .await?; + r.add_step("step1") + .scope(|s| run_diagnosis_macros_step(s).boxed()) + .await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, From bcf73ca2010a2cbe30beee12a37fc8c92e73d5c3 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 12:48:20 +0100 Subject: [PATCH 71/96] reduce the code size for metadata inputs - at some cost for semantics, overload the meaning of an empty map to be Option::None. This however removes a bunch of boilerplate code all over the place - arguably, this can be fully avoided by not using Option in spec.rs, but in that case, it's clearer to match the Option with the non-mandatory in the spec in an 1:1 fashion Signed-off-by: mimir-d --- src/output/dut.rs | 19 +++------ src/output/measure.rs | 87 ++++++++++++----------------------------- src/output/run.rs | 25 +++++------- src/output/trait_ext.rs | 12 ++++++ 4 files changed, 50 insertions(+), 93 deletions(-) diff --git a/src/output/dut.rs b/src/output/dut.rs index cf5607d..a5a6c3e 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -4,12 +4,11 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -use maplit::{btreemap, convert_args}; use std::collections::BTreeMap; use crate::output as tv; +use crate::output::trait_ext::{MapExt, VecExt}; use crate::spec; -use tv::trait_ext::VecExt; /// TODO: docs #[derive(Clone, Debug, PartialEq, Default)] @@ -29,7 +28,7 @@ pub struct DutInfo { software_infos: Vec, hardware_infos: Vec, - metadata: Option>, + metadata: BTreeMap, } impl DutInfo { @@ -78,7 +77,7 @@ impl DutInfo { platform_infos: self.platform_infos.map_option(PlatformInfo::to_spec), software_infos: self.software_infos.map_option(DutSoftwareInfo::to_spec), hardware_infos: self.hardware_infos.map_option(DutHardwareInfo::to_spec), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } @@ -89,7 +88,7 @@ pub struct DutInfoBuilder { id: String, name: Option, platform_infos: Vec, - metadata: Option>, + metadata: BTreeMap, } impl DutInfoBuilder { @@ -111,15 +110,7 @@ impl DutInfoBuilder { } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value - ))), - }; + self.metadata.insert(key.to_string(), value.clone()); self } diff --git a/src/output/measure.rs b/src/output/measure.rs index 6394ef6..06581e3 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -10,14 +10,12 @@ use std::sync::Arc; #[cfg(feature = "boxed-scopes")] use futures::future::BoxFuture; -use maplit::{btreemap, convert_args}; use crate::output as tv; +use crate::output::trait_ext::{MapExt, VecExt}; use crate::spec; use tv::{dut, step, Ident}; -use super::trait_ext::VecExt; - /// The measurement series. /// A Measurement Series is a time-series list of measurements. /// @@ -76,7 +74,7 @@ impl MeasurementSeries { .as_ref() .map(dut::DutHardwareInfo::to_spec), subcomponent: info.subcomponent.as_ref().map(dut::Subcomponent::to_spec), - metadata: info.metadata.clone(), + metadata: info.metadata.option(), }; self.emitter @@ -236,7 +234,7 @@ impl StartedMeasurementSeries { .timestamp .unwrap_or(self.parent.emitter.timestamp_provider().now()), series_id: self.parent.id.clone(), - metadata: details.metadata, + metadata: details.metadata.option(), }; self.parent @@ -256,7 +254,7 @@ pub struct MeasurementSeriesElemDetails { value: tv::Value, timestamp: Option>, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementSeriesElemDetails { @@ -271,7 +269,7 @@ pub struct MeasurementSeriesElemDetailsBuilder { value: tv::Value, timestamp: Option>, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementSeriesElemDetailsBuilder { @@ -288,15 +286,7 @@ impl MeasurementSeriesElemDetailsBuilder { } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + self.metadata.insert(key.to_string(), value); self } @@ -315,7 +305,7 @@ pub struct Validator { name: Option, validator_type: spec::ValidatorType, value: tv::Value, - metadata: Option>, + metadata: BTreeMap, } impl Validator { @@ -327,7 +317,7 @@ impl Validator { name: self.name.clone(), validator_type: self.validator_type.clone(), value: self.value.clone(), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } @@ -338,7 +328,8 @@ pub struct ValidatorBuilder { name: Option, validator_type: spec::ValidatorType, value: tv::Value, - metadata: Option>, + + metadata: BTreeMap, } impl ValidatorBuilder { @@ -347,7 +338,7 @@ impl ValidatorBuilder { validator_type, value: value.clone(), name: None, - metadata: None, + metadata: BTreeMap::new(), } } pub fn name(mut self, value: &str) -> ValidatorBuilder { @@ -355,15 +346,7 @@ impl ValidatorBuilder { self } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> ValidatorBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + self.metadata.insert(key.to_string(), value.clone()); self } @@ -403,6 +386,7 @@ impl ValidatorBuilder { /// .subcomponent(&Subcomponent::builder("name").build()) /// .build(); /// ``` +#[derive(Default)] pub struct Measurement { name: String, @@ -413,7 +397,7 @@ pub struct Measurement { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl Measurement { @@ -429,11 +413,7 @@ impl Measurement { Measurement { name: name.to_string(), value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, + ..Default::default() } } @@ -484,7 +464,7 @@ impl Measurement { .subcomponent .as_ref() .map(|subcomponent| subcomponent.to_spec()), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), } } } @@ -505,6 +485,7 @@ impl Measurement { /// .subcomponent(&Subcomponent::builder("name").build()); /// let measurement = builder.build(); /// ``` +#[derive(Default)] pub struct MeasurementBuilder { name: String, @@ -515,7 +496,7 @@ pub struct MeasurementBuilder { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementBuilder { @@ -531,11 +512,7 @@ impl MeasurementBuilder { MeasurementBuilder { name: name.to_string(), value: value.clone(), - unit: None, - validators: None, - hardware_info: None, - subcomponent: None, - metadata: None, + ..Default::default() } } @@ -600,16 +577,7 @@ impl MeasurementBuilder { /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementBuilder { - match self.metadata { - Some(ref mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - } - None => { - self.metadata = Some(convert_args!(btreemap!( - key => value, - ))); - } - }; + self.metadata.insert(key.to_string(), value.clone()); self } @@ -663,7 +631,7 @@ pub struct MeasurementSeriesInfo { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementSeriesInfo { @@ -688,7 +656,7 @@ pub struct MeasurementSeriesInfoBuilder { hardware_info: Option, subcomponent: Option, - metadata: Option>, + metadata: BTreeMap, } impl MeasurementSeriesInfoBuilder { @@ -732,15 +700,7 @@ impl MeasurementSeriesInfoBuilder { } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesInfoBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value - ))), - }; + self.metadata.insert(key.to_string(), value.clone()); self } @@ -762,6 +722,7 @@ mod tests { use super::*; use crate::output as tv; use crate::spec; + use maplit::{btreemap, convert_args}; use tv::dut::*; use tv::ValidatorType; diff --git a/src/output/run.rs b/src/output/run.rs index 2cc4b49..6d184b1 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -13,13 +13,13 @@ use std::sync::{ Arc, }; -use maplit::{btreemap, convert_args}; - use crate::output as tv; use crate::spec; use tv::step::TestStep; use tv::{config, dut, emitter, error, log}; +use super::trait_ext::MapExt; + /// The outcome of a TestRun. /// It's returned when the scope method of the [`TestRun`] object is used. pub struct TestRunOutcome { @@ -37,7 +37,7 @@ pub struct TestRun { version: String, parameters: BTreeMap, command_line: String, - metadata: Option>, + metadata: BTreeMap, emitter: Arc, } @@ -90,7 +90,7 @@ impl TestRun { version: self.version.clone(), command_line: self.command_line.clone(), parameters: self.parameters.clone(), - metadata: self.metadata.clone(), + metadata: self.metadata.option(), dut_info: dut.to_spec(), }), }); @@ -185,13 +185,15 @@ impl TestRun { } /// Builder for the [`TestRun`] object. +#[derive(Default)] pub struct TestRunBuilder { name: String, version: String, parameters: BTreeMap, command_line: String, - metadata: Option>, + config: Option, + metadata: BTreeMap, } impl TestRunBuilder { @@ -201,8 +203,7 @@ impl TestRunBuilder { version: version.to_string(), parameters: BTreeMap::new(), command_line: env::args().collect::>()[1..].join(" "), - metadata: None, - config: None, + ..Default::default() } } @@ -264,15 +265,7 @@ impl TestRunBuilder { /// .build(); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> TestRunBuilder { - self.metadata = match self.metadata { - Some(mut metadata) => { - metadata.insert(key.to_string(), value.clone()); - Some(metadata) - } - None => Some(convert_args!(btreemap!( - key => value, - ))), - }; + self.metadata.insert(key.to_string(), value.clone()); self } diff --git a/src/output/trait_ext.rs b/src/output/trait_ext.rs index b3aae93..ce4e69e 100644 --- a/src/output/trait_ext.rs +++ b/src/output/trait_ext.rs @@ -4,6 +4,8 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::collections::BTreeMap; + pub trait VecExt { fn map_option(&self, func: F) -> Option> where @@ -18,3 +20,13 @@ impl VecExt for Vec { (!self.is_empty()).then_some(self.iter().map(func).collect()) } } + +pub trait MapExt { + fn option(&self) -> Option>; +} + +impl MapExt for BTreeMap { + fn option(&self) -> Option> { + (!self.is_empty()).then_some(self.clone()) + } +} From 28099de54c78644e45431b39c595f451907d3d98 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 12:51:35 +0100 Subject: [PATCH 72/96] add a script to run the CI checks - this should make it easier to discover breakages before github gets to run the same commands; this script is relatively stable, but should be updated if the gh actions are changed Signed-off-by: mimir-d --- scripts/check.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100755 scripts/check.sh diff --git a/scripts/check.sh b/scripts/check.sh new file mode 100755 index 0000000..c598031 --- /dev/null +++ b/scripts/check.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -eo pipefail + +# (c) Meta Platforms, Inc. and affiliates. +# +# Use of this source code is governed by an MIT-style +# license that can be found in the LICENSE file or at +# https://opensource.org/licenses/MIT. + +echo "Running CI checks..." + +cargo fmt --check + +# ensure the tests run ok with all features disabled +cargo test + +cargo test --locked --all-features + +# docs-rs supersedes cargo doc +cargo +nightly docs-rs + +# finish with coverage, so we get an output to check +cargo llvm-cov --locked --all-features From 92c2788dba90adc95b8eadf039c747157eb82961 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 13:18:40 +0100 Subject: [PATCH 73/96] split runner.rs into multiple smaller topics - no functional changes Signed-off-by: mimir-d --- tests/output/config.rs | 88 ++ tests/output/diagnosis.rs | 87 ++ tests/output/error.rs | 304 +++++++ tests/output/fixture.rs | 187 ++++ tests/output/log.rs | 159 ++++ tests/output/main.rs | 9 +- tests/output/measure.rs | 704 +++++++++++++++ tests/output/run.rs | 194 ++++ tests/output/runner.rs | 1800 ------------------------------------- tests/output/step.rs | 178 ++++ 10 files changed, 1909 insertions(+), 1801 deletions(-) create mode 100644 tests/output/config.rs create mode 100644 tests/output/diagnosis.rs create mode 100644 tests/output/error.rs create mode 100644 tests/output/fixture.rs create mode 100644 tests/output/log.rs create mode 100644 tests/output/measure.rs create mode 100644 tests/output/run.rs delete mode 100644 tests/output/runner.rs create mode 100644 tests/output/step.rs diff --git a/tests/output/config.rs b/tests/output/config.rs new file mode 100644 index 0000000..75f0531 --- /dev/null +++ b/tests/output/config.rs @@ -0,0 +1,88 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +#[cfg(coverage)] +use anyhow::Result; + +// reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot +// disable the coverage itself, only run this test when in coverage mode because assert_fs +// does ultimately assume there's a real filesystem somewhere +#[cfg(coverage)] +#[tokio::test] +async fn test_config_builder_with_file() -> Result<()> { + use std::fs; + + use assert_fs::prelude::*; + use assert_json_diff::assert_json_include; + use predicates::prelude::*; + use serde_json::json; + + use ocptv::output::{Config, DutInfo, TestResult, TestRun, TestStatus}; + + use super::fixture::*; + + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id" + }, + "name": "run_name", + "parameters": {}, + "version": "1.0", + "commandLine": "" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + let fs = assert_fs::TempDir::new()?; + let output_file = fs.child("output.jsonl"); + + let dut = DutInfo::builder("dut_id").build(); + + let run = TestRun::builder("run_name", "1.0") + .config( + Config::builder() + .timezone(chrono_tz::Europe::Rome) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .with_file_output(output_file.path()) + .await? + .build(), + ) + .build() + .start(dut) + .await?; + + run.add_error_with_msg("symptom", "Error message").await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + + output_file.assert(predicate::path::exists()); + let content = fs::read_to_string(output_file.path())?; + + for (idx, entry) in content.lines().enumerate() { + let value = serde_json::from_str::(entry).unwrap(); + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} diff --git a/tests/output/diagnosis.rs b/tests/output/diagnosis.rs new file mode 100644 index 0000000..22bef87 --- /dev/null +++ b/tests/output/diagnosis.rs @@ -0,0 +1,87 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{Diagnosis, DiagnosisType, Subcomponent}; + +use super::fixture::*; + +#[tokio::test] +async fn test_step_with_diagnosis() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "diagnosis": { + "verdict": "verdict", + "type": "PASS" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.diagnosis("verdict", DiagnosisType::Pass).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_diagnosis_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "diagnosis": { + "verdict": "verdict", + "type": "PASS", + "message": "message", + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let diagnosis = Diagnosis::builder("verdict", DiagnosisType::Pass) + .hardware_info(dut.hardware_info("hw0").unwrap()) // must exist + .subcomponent(&Subcomponent::builder("name").build()) + .message("message") + .build(); + s.diagnosis_with_details(&diagnosis).await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/error.rs b/tests/output/error.rs new file mode 100644 index 0000000..3f5370b --- /dev/null +++ b/tests/output/error.rs @@ -0,0 +1,304 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::Error; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { r.add_error("symptom").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { r.add_error_with_msg("symptom", "Error message").await }.boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "error": { + "message": "Error message", + "softwareInfoIds": [ + "sw0" + ], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, dut| { + async move { + r.add_error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(dut.software_info("sw0").unwrap()) // must exist + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error("no-dut").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_message_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "symptom": "no-dut", + "message": "failed to find dut", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_with_msg("no-dut", "failed to find dut") + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_error_with_details_before_start() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "error": { + "message": "failed to find dut", + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "no-dut" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + ]; + + check_output(&expected, |run_builder, _| { + async move { + let run = run_builder.build(); + run.add_error_with_details( + &Error::builder("no-dut") + .message("failed to find dut") + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_error("symptom").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_message() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "message": "Error message", + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_error_with_msg("symptom", "Error message").await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_error_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "error": { + "message": "Error message", + "softwareInfoIds": [ + "sw0" + ], + "sourceLocation": { + "file": "file", + "line": 1 + }, + "symptom": "symptom" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + s.add_error_with_details( + &Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(dut.software_info("sw0").unwrap()) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/fixture.rs b/tests/output/fixture.rs new file mode 100644 index 0000000..ff21d32 --- /dev/null +++ b/tests/output/fixture.rs @@ -0,0 +1,187 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use assert_json_diff::assert_json_eq; +use futures::future::BoxFuture; +use futures::future::Future; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{ + Config, DutInfo, HardwareInfo, Ident, OcptvError, SoftwareInfo, SoftwareType, StartedTestRun, + StartedTestStep, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, + SPEC_VERSION, +}; + +pub const DATETIME: chrono::DateTime = + chrono::DateTime::from_timestamp_nanos(0); +pub const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; +pub struct FixedTsProvider {} + +impl TimestampProvider for FixedTsProvider { + fn now(&self) -> chrono::DateTime { + // all cases will use time 0 but this is configurable + DATETIME.with_timezone(&chrono_tz::UTC) + } +} + +pub fn json_schema_version() -> serde_json::Value { + // seqno for schemaVersion is always 0 + json!({ + "schemaVersion": { + "major": SPEC_VERSION.0, + "minor": SPEC_VERSION.1 + }, + "sequenceNumber": 0, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_run_default_start() -> serde_json::Value { + // seqno for the default test run start is always 1 + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "name": "run_name", + "parameters": {}, + "version": "1.0", + "commandLine": "" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_run_pass(seqno: i32) -> serde_json::Value { + json!({ + "testRunArtifact": { + "testRunEnd": { + "result": "PASS", + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_step_default_start() -> serde_json::Value { + // seqno for the default test run start is always 2 + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepStart": { + "name": "first step" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }) +} + +pub fn json_step_complete(seqno: i32) -> serde_json::Value { + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepEnd": { + "status": "COMPLETE" + } + }, + "sequenceNumber": seqno, + "timestamp": DATETIME_FORMATTED + }) +} + +pub async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + R: Future>, + F: FnOnce(TestRunBuilder, DutInfo) -> R, +{ + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let mut dut = DutInfo::builder("dut_id").build(); + dut.add_software_info( + SoftwareInfo::builder("ubuntu") + .id(Ident::Exact("sw0".to_owned())) // name is important as fixture + .version("22") + .software_type(SoftwareType::System) + .build(), + ); + dut.add_hardware_info( + HardwareInfo::builder("fan") + .id(Ident::Exact("hw0".to_owned())) + .location("board0/fan") + .build(), + ); + + let run_builder = TestRun::builder("run_name", "1.0").config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .build(), + ); + + // run the main test closure + test_fn(run_builder, dut).await?; + + for (i, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_eq!(value, expected[i]); + } + + Ok(()) +} + +pub async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, +{ + check_output(expected, |run_builder, dut| async move { + let run = run_builder.build(); + + let run = run.start(dut.clone()).await?; + test_fn(&run, dut).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} + +pub async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> +where + F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, +{ + check_output(expected, |run_builder, dut| async move { + let run = run_builder.build().start(dut.clone()).await?; + + let step = run.add_step("first step").start().await?; + test_fn(&step, dut).await?; + step.end(TestStatus::Complete).await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + + Ok(()) + }) + .await +} diff --git a/tests/output/log.rs b/tests/output/log.rs new file mode 100644 index 0000000..9f54392 --- /dev/null +++ b/tests/output/log.rs @@ -0,0 +1,159 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{Log, LogSeverity}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_with_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_log() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_testrun_step_log_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO", + "sourceLocation": { + "file": "file", + "line": 1 + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_log_with_details( + &Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/main.rs b/tests/output/main.rs index c07310d..f5d7506 100644 --- a/tests/output/main.rs +++ b/tests/output/main.rs @@ -4,5 +4,12 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +mod config; +mod diagnosis; +mod error; +mod fixture; +mod log; mod macros; -mod runner; +mod measure; +mod run; +mod step; diff --git a/tests/output/measure.rs b/tests/output/measure.rs new file mode 100644 index 0000000..d0fa0a0 --- /dev/null +++ b/tests/output/measure.rs @@ -0,0 +1,704 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; + +use ocptv::output::{ + Ident, Measurement, MeasurementSeriesElemDetails, MeasurementSeriesInfo, Subcomponent, + Validator, ValidatorType, +}; + +use super::fixture::*; + +#[tokio::test] +async fn test_step_with_measurement() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurement": { + "name": "name", + "value": 50 + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, _| { + async { + s.add_measurement("name", 50.into()).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurement": { + "name": "name", + "value": 50, + "validators": [{ + "type": "EQUAL", + "value": 30 + }], + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value", + "key2": "value2" + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + + let measurement = Measurement::builder("name", 50.into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .hardware_info(hw_info) + .subcomponent(&Subcomponent::builder("name").build()) + .build(); + s.add_measurement_with_details(&measurement).await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_multiple_measurement_series() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series1", + "name": "name" + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series1", + "totalCount": 0 + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(7), + json_run_pass(8), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; + + let series_2 = s.add_measurement_series("name").start().await?; + series_2.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "series_id", + "name": "name", + "unit": "unit", + "validators": [{ + "type": "EQUAL", + "value": 30 + }], + "hardwareInfoId": "hw0", + "subcomponent": { + "name": "name" + }, + "metadata": { + "key": "value", + "key2": "value2" + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "series_id", + "totalCount": 0 + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(5), + json_run_pass(6), + ]; + + check_output_step(&expected, |s, dut| { + async move { + let hw_info = dut.hardware_info("hw0").unwrap(); // must exist + + let series = s + .add_measurement_series_with_details( + MeasurementSeriesInfo::builder("name") + .id(Ident::Exact("series_id".to_owned())) + .unit("unit") + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .hardware_info(hw_info) + .subcomponent(&Subcomponent::builder("name").build()) + .build(), + ) + .start() + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 1 + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series.add_measurement(60.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "value": 70, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "value": 80, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + // add more than one element to check the index increments correctly + series.add_measurement(60.into()).await?; + series.add_measurement(70.into()).await?; + series.add_measurement(80.into()).await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_details() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "metadata": { + "key": "value", + "key2": "value2" + }, + "value": 60, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 1 + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(6), + json_run_pass(7), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + series + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(60.into()) + .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .build(), + ) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "metadata": {"key": "value"}, + "value": 60, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "metadata": {"key2": "value2"}, + "value": 70, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "metadata": {"key3": "value3"}, + "value": 80, + "timestamp": DATETIME_FORMATTED, + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name").start().await?; + // add more than one element to check the index increments correctly + series + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(60.into()) + .add_metadata("key", "value".into()) + .build(), + ) + .await?; + series + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(70.into()) + .add_metadata("key2", "value2".into()) + .build(), + ) + .await?; + series + .add_measurement_with_details( + MeasurementSeriesElemDetails::builder(80.into()) + .add_metadata("key3", "value3".into()) + .build(), + ) + .await?; + series.end().await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_step_with_measurement_series_scope() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesStart": { + "measurementSeriesId": "step0_series0", + "name": "name" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 0, + "measurementSeriesId": "step0_series0", + "value": 60, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 4, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 1, + "measurementSeriesId": "step0_series0", + "value": 70, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 5, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesElement": { + "index": 2, + "measurementSeriesId": "step0_series0", + "value": 80, + "timestamp": DATETIME_FORMATTED + } + }, + "sequenceNumber": 6, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "measurementSeriesEnd": { + "measurementSeriesId": "step0_series0", + "totalCount": 3 + } + }, + "sequenceNumber": 7, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(8), + json_run_pass(9), + ]; + + check_output_step(&expected, |s, _| { + async { + let series = s.add_measurement_series("name"); + series + .scope(|s| { + async move { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; + + Ok(()) + } + .boxed() + }) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} diff --git a/tests/output/run.rs b/tests/output/run.rs new file mode 100644 index 0000000..1da27f3 --- /dev/null +++ b/tests/output/run.rs @@ -0,0 +1,194 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use assert_json_diff::assert_json_include; +use futures::FutureExt; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{DutInfo, TestResult, TestRun, TestStatus}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_start_and_end() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(2), + ]; + + check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_testrun_with_scope() -> Result<()> { + use ocptv::output::{LogSeverity, TestResult, TestRunOutcome, TestStatus}; + + let expected = [ + json_schema_version(), + json_run_default_start(), + json!({ + "testRunArtifact": { + "log": { + "message": "First message", + "severity": "INFO" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(3), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder.build(); + + run.scope(dut, |r| { + async move { + r.add_log(LogSeverity::Info, "First message").await?; + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_instantiation_with_new() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_run_pass(2), + ]; + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::new("run_name", "1.0").start(dut).await?; + run.end(TestStatus::Complete, TestResult::Pass).await?; + + for (idx, entry) in buffer.lock().await.iter().enumerate() { + let value = serde_json::from_str::(entry)?; + assert_json_include!(actual: value, expected: &expected[idx]); + } + + Ok(()) +} + +#[tokio::test] +async fn test_testrun_metadata() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "metadata": {"key": "value"}, + "name": "run_name", + "parameters": {}, + "version": "1.0", + + "commandLine": "", + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(2), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder + .add_metadata("key", "value".into()) + .build() + .start(dut) + .await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} + +#[tokio::test] +async fn test_testrun_builder() -> Result<()> { + let expected = [ + json_schema_version(), + json!({ + "testRunArtifact": { + "testRunStart": { + "commandLine": "cmd_line", + "dutInfo": { + "dutInfoId": "dut_id", + "softwareInfos": [{ + "softwareInfoId": "sw0", + "name": "ubuntu", + "version": "22", + "softwareType": "SYSTEM", + }], + "hardwareInfos": [{ + "hardwareInfoId": "hw0", + "name": "fan", + "location": "board0/fan" + }] + }, + "metadata": { + "key": "value", + "key2": "value2" + }, + "name": "run_name", + "parameters": { + "key": "value" + }, + "version": "1.0" + } + }, + "sequenceNumber": 1, + "timestamp": DATETIME_FORMATTED + }), + json_run_pass(2), + ]; + + check_output(&expected, |run_builder, dut| async { + let run = run_builder + .add_metadata("key", "value".into()) + .add_metadata("key2", "value2".into()) + .add_parameter("key", "value".into()) + .command_line("cmd_line") + .build() + .start(dut) + .await?; + + run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(()) + }) + .await +} diff --git a/tests/output/runner.rs b/tests/output/runner.rs deleted file mode 100644 index cd8df6c..0000000 --- a/tests/output/runner.rs +++ /dev/null @@ -1,1800 +0,0 @@ -// (c) Meta Platforms, Inc. and affiliates. -// -// Use of this source code is governed by an MIT-style -// license that can be found in the LICENSE file or at -// https://opensource.org/licenses/MIT. - -use std::sync::Arc; - -use anyhow::Result; - -use assert_json_diff::{assert_json_eq, assert_json_include}; -use futures::future::BoxFuture; -use futures::future::Future; -use futures::FutureExt; -use serde_json::json; -use tokio::sync::Mutex; - -use ocptv::output as tv; -use ocptv::output::OcptvError; -#[cfg(feature = "boxed-scopes")] -use tv::TestRunOutcome; -use tv::{ - Config, Diagnosis, DutInfo, Error, HardwareInfo, Ident, Log, LogSeverity, Measurement, - MeasurementSeriesElemDetails, MeasurementSeriesInfo, SoftwareInfo, SoftwareType, - StartedTestRun, StartedTestStep, Subcomponent, TestResult, TestRun, TestRunBuilder, TestStatus, - TimestampProvider, Validator, ValidatorType, -}; - -const DATETIME: chrono::DateTime = chrono::DateTime::from_timestamp_nanos(0); -const DATETIME_FORMATTED: &str = "1970-01-01T00:00:00.000Z"; -struct FixedTsProvider {} - -impl TimestampProvider for FixedTsProvider { - fn now(&self) -> chrono::DateTime { - // all cases will use time 0 but this is configurable - DATETIME.with_timezone(&chrono_tz::UTC) - } -} - -fn json_schema_version() -> serde_json::Value { - // seqno for schemaVersion is always 0 - json!({ - "schemaVersion": { - "major": tv::SPEC_VERSION.0, - "minor": tv::SPEC_VERSION.1 - }, - "sequenceNumber": 0, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_run_default_start() -> serde_json::Value { - // seqno for the default test run start is always 1 - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "name": "run_name", - "parameters": {}, - "version": "1.0", - "commandLine": "" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_run_pass(seqno: i32) -> serde_json::Value { - json!({ - "testRunArtifact": { - "testRunEnd": { - "result": "PASS", - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_step_default_start() -> serde_json::Value { - // seqno for the default test run start is always 2 - json!({ - "testStepArtifact": { - "testStepId": "step0", - "testStepStart": { - "name": "first step" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }) -} - -fn json_step_complete(seqno: i32) -> serde_json::Value { - json!({ - "testStepArtifact": { - "testStepId": "step0", - "testStepEnd": { - "status": "COMPLETE" - } - }, - "sequenceNumber": seqno, - "timestamp": DATETIME_FORMATTED - }) -} - -async fn check_output(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - R: Future>, - F: FnOnce(TestRunBuilder, DutInfo) -> R, -{ - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let mut dut = DutInfo::builder("dut_id").build(); - dut.add_software_info( - SoftwareInfo::builder("ubuntu") - .id(Ident::Exact("sw0".to_owned())) // name is important as fixture - .version("22") - .software_type(SoftwareType::System) - .build(), - ); - dut.add_hardware_info( - HardwareInfo::builder("fan") - .id(Ident::Exact("hw0".to_owned())) - .location("board0/fan") - .build(), - ); - - let run_builder = TestRun::builder("run_name", "1.0").config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .build(), - ); - - // run the main test closure - test_fn(run_builder, dut).await?; - - for (i, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_eq!(value, expected[i]); - } - - Ok(()) -} - -async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, -{ - check_output(expected, |run_builder, dut| async move { - let run = run_builder.build(); - - let run = run.start(dut.clone()).await?; - test_fn(&run, dut).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await -} - -async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> -where - F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), tv::OcptvError>>, -{ - check_output(expected, |run_builder, dut| async move { - let run = run_builder.build().start(dut.clone()).await?; - - let step = run.add_step("first step").start().await?; - test_fn(&step, dut).await?; - step.end(TestStatus::Complete).await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_start_and_end() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_run_pass(2), - ]; - - check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await -} - -#[tokio::test] -async fn test_testrun_with_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { r.add_error("symptom").await }.boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, _| { - async { r.add_error_with_msg("symptom", "Error message").await }.boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "softwareInfoIds": [ - "sw0" - ], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output_run(&expected, |r, dut| { - async move { - r.add_error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(dut.software_info("sw0").unwrap()) // must exist - .build(), - ) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "no-dut", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error("no-dut").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_message_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "symptom": "no-dut", - "message": "failed to find dut", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_with_msg("no-dut", "failed to find dut") - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_error_with_details_before_start() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "error": { - "message": "failed to find dut", - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "no-dut" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - ]; - - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_with_details( - &Error::builder("no-dut") - .message("failed to find dut") - .source("file", 1) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_testrun_with_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json!({ - "testRunArtifact": { - "log": { - "message": "First message", - "severity": "INFO" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder.build(); - - run.scope(dut, |r| { - async move { - r.add_log(LogSeverity::Info, "First message").await?; - - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() - }) - .await?; - - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_with_step() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json_step_complete(3), - json_run_pass(4), - ]; - - check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await -} - -#[tokio::test] -async fn test_testrun_step_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_log_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO", - "sourceLocation": { - "file": "file", - "line": 1 - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_log_with_details( - &Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_error("symptom").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error_with_message() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_error_with_msg("symptom", "Error message").await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_testrun_step_error_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "error": { - "message": "Error message", - "softwareInfoIds": [ - "sw0" - ], - "sourceLocation": { - "file": "file", - "line": 1 - }, - "symptom": "symptom" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - s.add_error_with_details( - &Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(dut.software_info("sw0").unwrap()) - .build(), - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_testrun_step_scope_log() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "log": { - "message": "This is a log message with INFO severity", - "severity": "INFO" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_run(&expected, |r, _| { - async { - r.add_step("first step") - .scope(|s| { - async move { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(TestStatus::Complete) - } - .boxed() - }) - .await - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurement": { - "name": "name", - "value": 50 - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.add_measurement("name", 50.into()).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurement": { - "name": "name", - "value": 50, - "validators": [{ - "type": "EQUAL", - "value": 30 - }], - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - "metadata": { - "key": "value", - "key2": "value2" - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let hw_info = dut.hardware_info("hw0").unwrap(); // must exist - - let measurement = Measurement::builder("name", 50.into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) - .build(); - s.add_measurement_with_details(&measurement).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_multiple_measurement_series() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series1", - "name": "name" - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series1", - "totalCount": 0 - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(7), - json_run_pass(8), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; - - let series_2 = s.add_measurement_series("name").start().await?; - series_2.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "series_id", - "name": "name", - "unit": "unit", - "validators": [{ - "type": "EQUAL", - "value": 30 - }], - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - "metadata": { - "key": "value", - "key2": "value2" - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "series_id", - "totalCount": 0 - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(5), - json_run_pass(6), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let hw_info = dut.hardware_info("hw0").unwrap(); // must exist - - let series = s - .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("name") - .id(Ident::Exact("series_id".to_owned())) - .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) - .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) - .build(), - ) - .start() - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 1 - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.add_measurement(60.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "value": 70, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "value": 80, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - // add more than one element to check the index increments correctly - series.add_measurement(60.into()).await?; - series.add_measurement(70.into()).await?; - series.add_measurement(80.into()).await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_with_details() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "metadata": { - "key": "value", - "key2": "value2" - }, - "value": 60, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 1 - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(6), - json_run_pass(7), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) - .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .build(), - ) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_measurement_series_element_with_metadata_index_no() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "metadata": {"key": "value"}, - "value": 60, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "metadata": {"key2": "value2"}, - "value": 70, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "metadata": {"key3": "value3"}, - "value": 80, - "timestamp": DATETIME_FORMATTED, - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - // add more than one element to check the index increments correctly - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) - .add_metadata("key", "value".into()) - .build(), - ) - .await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(70.into()) - .add_metadata("key2", "value2".into()) - .build(), - ) - .await?; - series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(80.into()) - .add_metadata("key3", "value3".into()) - .build(), - ) - .await?; - series.end().await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_diagnosis() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "diagnosis": { - "verdict": "verdict", - "type": "PASS" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, _| { - async { - s.diagnosis("verdict", tv::DiagnosisType::Pass).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_diagnosis_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "diagnosis": { - "verdict": "verdict", - "type": "PASS", - "message": "message", - "hardwareInfoId": "hw0", - "subcomponent": { - "name": "name" - }, - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - check_output_step(&expected, |s, dut| { - async move { - let diagnosis = Diagnosis::builder("verdict", tv::DiagnosisType::Pass) - .hardware_info(dut.hardware_info("hw0").unwrap()) // must exist - .subcomponent(&Subcomponent::builder("name").build()) - .message("message") - .build(); - s.diagnosis_with_details(&diagnosis).await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[cfg(feature = "boxed-scopes")] -#[tokio::test] -async fn test_step_with_measurement_series_scope() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesStart": { - "measurementSeriesId": "step0_series0", - "name": "name" - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 0, - "measurementSeriesId": "step0_series0", - "value": 60, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 4, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 1, - "measurementSeriesId": "step0_series0", - "value": 70, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 5, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesElement": { - "index": 2, - "measurementSeriesId": "step0_series0", - "value": 80, - "timestamp": DATETIME_FORMATTED - } - }, - "sequenceNumber": 6, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "measurementSeriesEnd": { - "measurementSeriesId": "step0_series0", - "totalCount": 3 - } - }, - "sequenceNumber": 7, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(8), - json_run_pass(9), - ]; - - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name"); - series - .scope(|s| { - async move { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; - - Ok(()) - } - .boxed() - }) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -// reasoning: the coverage(off) attribute is experimental in llvm-cov, so because we cannot -// disable the coverage itself, only run this test when in coverage mode because assert_fs -// does ultimately assume there's a real filesystem somewhere -#[cfg(coverage)] -#[tokio::test] -async fn test_config_builder_with_file() -> Result<()> { - use assert_fs::prelude::*; - use predicates::prelude::*; - use std::fs; - - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id" - }, - "name": "run_name", - "parameters": {}, - "version": "1.0", - "commandLine": "" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json!({ - "testRunArtifact": { - "error": { - "message": "Error message", - "symptom": "symptom" - } - }, - "sequenceNumber": 2, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(3), - ]; - - let fs = assert_fs::TempDir::new()?; - let output_file = fs.child("output.jsonl"); - - let dut = DutInfo::builder("dut_id").build(); - - let run = TestRun::builder("run_name", "1.0") - .config( - Config::builder() - .timezone(chrono_tz::Europe::Rome) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .with_file_output(output_file.path()) - .await? - .build(), - ) - .build() - .start(dut) - .await?; - - run.add_error_with_msg("symptom", "Error message").await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - - output_file.assert(predicate::path::exists()); - let content = fs::read_to_string(output_file.path())?; - - for (idx, entry) in content.lines().enumerate() { - let value = serde_json::from_str::(entry).unwrap(); - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) -} - -#[tokio::test] -async fn test_step_with_extension() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_step_default_start(), - json!({ - "testStepArtifact": { - "testStepId": "step0", - "extension": { - "name": "extension", - "content": { - "@type": "TestExtension", - "stringField": "string", - "numberField": 42 - } - } - }, - "sequenceNumber": 3, - "timestamp": DATETIME_FORMATTED - }), - json_step_complete(4), - json_run_pass(5), - ]; - - #[derive(serde::Serialize)] - struct Ext { - #[serde(rename = "@type")] - r#type: String, - #[serde(rename = "stringField")] - string_field: String, - #[serde(rename = "numberField")] - number_field: u32, - } - - check_output_step(&expected, |s, _| { - async { - s.add_extension( - "extension", - Ext { - r#type: "TestExtension".to_owned(), - string_field: "string".to_owned(), - number_field: 42, - }, - ) - .await?; - - Ok(()) - } - .boxed() - }) - .await -} - -#[tokio::test] -async fn test_step_with_extension_which_fails() -> Result<()> { - #[derive(thiserror::Error, Debug, PartialEq)] - enum TestError { - #[error("test_error_fail")] - Fail, - } - - fn fail_serialize(_: &u32, _serializer: S) -> Result - where - S: serde::Serializer, - { - Err(serde::ser::Error::custom(TestError::Fail)) - } - - #[derive(serde::Serialize)] - struct Ext { - #[serde(serialize_with = "fail_serialize")] - i: u32, - } - - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::builder("run_name", "1.0") - .config( - Config::builder() - .with_buffer_output(Arc::clone(&buffer)) - .with_timestamp_provider(Box::new(FixedTsProvider {})) - .build(), - ) - .build() - .start(dut) - .await?; - let step = run.add_step("first step").start().await?; - - let result = step.add_extension("extension", Ext { i: 0 }).await; - - match result { - Err(OcptvError::Format(e)) => { - // `to_string` is the only way to check this error. `serde_json::Error` only - // implements source/cause for io errors, and this is a string - assert_eq!(e.to_string(), "test_error_fail"); - } - _ => panic!("unexpected ocptv error type"), - } - - Ok(()) -} - -#[tokio::test] -async fn test_testrun_instantiation_with_new() -> Result<()> { - let expected = [ - json_schema_version(), - json_run_default_start(), - json_run_pass(2), - ]; - let buffer: Arc>> = Arc::new(Mutex::new(vec![])); - - let dut = DutInfo::builder("dut_id").build(); - let run = TestRun::new("run_name", "1.0").start(dut).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; - - for (idx, entry) in buffer.lock().await.iter().enumerate() { - let value = serde_json::from_str::(entry)?; - assert_json_include!(actual: value, expected: &expected[idx]); - } - - Ok(()) -} - -#[tokio::test] -async fn test_testrun_metadata() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "metadata": {"key": "value"}, - "name": "run_name", - "parameters": {}, - "version": "1.0", - - "commandLine": "", - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(2), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder - .add_metadata("key", "value".into()) - .build() - .start(dut) - .await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await -} - -#[tokio::test] -async fn test_testrun_builder() -> Result<()> { - let expected = [ - json_schema_version(), - json!({ - "testRunArtifact": { - "testRunStart": { - "commandLine": "cmd_line", - "dutInfo": { - "dutInfoId": "dut_id", - "softwareInfos": [{ - "softwareInfoId": "sw0", - "name": "ubuntu", - "version": "22", - "softwareType": "SYSTEM", - }], - "hardwareInfos": [{ - "hardwareInfoId": "hw0", - "name": "fan", - "location": "board0/fan" - }] - }, - "metadata": { - "key": "value", - "key2": "value2" - }, - "name": "run_name", - "parameters": { - "key": "value" - }, - "version": "1.0" - } - }, - "sequenceNumber": 1, - "timestamp": DATETIME_FORMATTED - }), - json_run_pass(2), - ]; - - check_output(&expected, |run_builder, dut| async { - let run = run_builder - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_parameter("key", "value".into()) - .command_line("cmd_line") - .build() - .start(dut) - .await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; - Ok(()) - }) - .await -} diff --git a/tests/output/step.rs b/tests/output/step.rs new file mode 100644 index 0000000..b74026d --- /dev/null +++ b/tests/output/step.rs @@ -0,0 +1,178 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::sync::Arc; + +use anyhow::Result; +use futures::FutureExt; +use serde_json::json; +use tokio::sync::Mutex; + +use ocptv::output::{Config, DutInfo, OcptvError, TestRun}; + +use super::fixture::*; + +#[tokio::test] +async fn test_testrun_with_step() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json_step_complete(3), + json_run_pass(4), + ]; + + check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await +} + +#[cfg(feature = "boxed-scopes")] +#[tokio::test] +async fn test_testrun_step_scope_log() -> Result<()> { + use ocptv::output::{LogSeverity, TestStatus}; + + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "log": { + "message": "This is a log message with INFO severity", + "severity": "INFO" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + check_output_run(&expected, |r, _| { + async { + r.add_step("first step") + .scope(|s| { + async move { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(TestStatus::Complete) + } + .boxed() + }) + .await + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_extension() -> Result<()> { + let expected = [ + json_schema_version(), + json_run_default_start(), + json_step_default_start(), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "extension": { + "name": "extension", + "content": { + "@type": "TestExtension", + "stringField": "string", + "numberField": 42 + } + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), + json_step_complete(4), + json_run_pass(5), + ]; + + #[derive(serde::Serialize)] + struct Ext { + #[serde(rename = "@type")] + r#type: String, + #[serde(rename = "stringField")] + string_field: String, + #[serde(rename = "numberField")] + number_field: u32, + } + + check_output_step(&expected, |s, _| { + async { + s.add_extension( + "extension", + Ext { + r#type: "TestExtension".to_owned(), + string_field: "string".to_owned(), + number_field: 42, + }, + ) + .await?; + + Ok(()) + } + .boxed() + }) + .await +} + +#[tokio::test] +async fn test_step_with_extension_which_fails() -> Result<()> { + #[derive(thiserror::Error, Debug, PartialEq)] + enum TestError { + #[error("test_error_fail")] + Fail, + } + + fn fail_serialize(_: &u32, _serializer: S) -> Result + where + S: serde::Serializer, + { + Err(serde::ser::Error::custom(TestError::Fail)) + } + + #[derive(serde::Serialize)] + struct Ext { + #[serde(serialize_with = "fail_serialize")] + i: u32, + } + + let buffer: Arc>> = Arc::new(Mutex::new(vec![])); + let dut = DutInfo::builder("dut_id").build(); + let run = TestRun::builder("run_name", "1.0") + .config( + Config::builder() + .with_buffer_output(Arc::clone(&buffer)) + .with_timestamp_provider(Box::new(FixedTsProvider {})) + .build(), + ) + .build() + .start(dut) + .await?; + let step = run.add_step("first step").start().await?; + + let result = step.add_extension("extension", Ext { i: 0 }).await; + + match result { + Err(OcptvError::Format(e)) => { + // `to_string` is the only way to check this error. `serde_json::Error` only + // implements source/cause for io errors, and this is a string + assert_eq!(e.to_string(), "test_error_fail"); + } + _ => panic!("unexpected ocptv error type"), + } + + Ok(()) +} From 933aee0fdf8edd732e9843aae3976e4207b64b24 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 08:41:30 +0000 Subject: [PATCH 74/96] Add Diagnosis example Signed-off-by: Giovanni Colapinto --- Cargo.lock | 69 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 5 ++++ examples/diagnosis.rs | 54 +++++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+) create mode 100644 examples/diagnosis.rs diff --git a/Cargo.lock b/Cargo.lock index dbe433f..1b62085 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -160,6 +160,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.2" @@ -440,6 +446,17 @@ dependencies = [ "slab", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.31.1" @@ -669,6 +686,7 @@ dependencies = [ "maplit", "mime", "predicates", + "rand", "serde", "serde_json", "serde_with", @@ -756,6 +774,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + [[package]] name = "predicates" version = "3.1.2" @@ -810,6 +837,18 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", "rand_core", ] @@ -818,6 +857,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "regex" @@ -1162,6 +1204,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.94" @@ -1316,3 +1364,24 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 57bf0ef..b49bee2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,7 @@ assert_fs = "1.1.2" futures = "0.3.30" predicates = "3.1.2" tokio-test = "0.4.4" +rand = "0.8.5" [lints.rust] unexpected_cfgs = { level = "warn", check-cfg = [ @@ -81,3 +82,7 @@ required-features = ["boxed-scopes"] [[example]] name = "simple_step_fail" required-features = ["boxed-scopes"] + +[[example]] +name = "diagnosis" +required-features = ["boxed-scopes"] \ No newline at end of file diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs new file mode 100644 index 0000000..77647a6 --- /dev/null +++ b/examples/diagnosis.rs @@ -0,0 +1,54 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use rand::Rng; +use tv::{TestResult, TestStatus}; + +fn get_fan_speed() -> i32 { + let mut rng = rand::thread_rng(); + rng.gen_range(1500..1700) +} + +async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + } else { + step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + } + + Ok(TestStatus::Complete) +} + +/// Simple demo with diagnosis. +#[tokio::main] +async fn main() -> Result<()> { + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| run_diagnosis_step(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From 12931fec52be3f9844507d5e84be1f8b558f2198 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 11:44:27 +0000 Subject: [PATCH 75/96] Add example with macros Signed-off-by: Giovanni Colapinto --- examples/diagnosis.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs index 77647a6..dbb72e1 100644 --- a/examples/diagnosis.rs +++ b/examples/diagnosis.rs @@ -8,6 +8,7 @@ use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; +use ocptv::{ocptv_diagnosis_fail, ocptv_diagnosis_pass}; use rand::Rng; use tv::{TestResult, TestStatus}; @@ -28,6 +29,20 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + ocptv_diagnosis_pass!(step, "fan_ok").await?; + } else { + ocptv_diagnosis_fail!(step, "fan_low").await?; + } + + Ok(TestStatus::Complete) +} + /// Simple demo with diagnosis. #[tokio::main] async fn main() -> Result<()> { @@ -40,6 +55,9 @@ async fn main() -> Result<()> { r.add_step("step0") .scope(|s| run_diagnosis_step(s).boxed()) .await?; + r.add_step("step1") + .scope(|s| run_diagnosis_macros_step(s).boxed()) + .await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, From f9753db5b429d88e109d28a6b97fdb2def9cfeba Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 13:32:34 +0100 Subject: [PATCH 76/96] improve api ergonomics; remove refs - remove ref requirement for some objects that are frequently consumed, without any need to keep additional objects around (or if so, they're Clone) - remove a bunch of unnecessary `.clone()` Signed-off-by: mimir-d --- examples/error_with_dut.rs | 2 +- examples/measurement_single.rs | 2 +- examples/measurement_subcomponent.rs | 8 ++-- examples/measurement_validators.rs | 8 ++-- src/output/dut.rs | 8 ++-- src/output/error.rs | 1 + src/output/macros.rs | 8 ++-- src/output/measure.rs | 68 ++++++++++++---------------- src/output/run.rs | 22 ++++----- src/output/step.rs | 20 ++++---- tests/output/diagnosis.rs | 2 +- tests/output/error.rs | 6 +-- tests/output/log.rs | 4 +- tests/output/measure.rs | 10 ++-- 14 files changed, 79 insertions(+), 90 deletions(-) diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index 652e0cf..6983090 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -27,7 +27,7 @@ async fn main() -> Result<()> { .scope(dut, |r| { async move { r.add_error_with_details( - &tv::Error::builder("power-fail") + tv::Error::builder("power-fail") .add_software_info(&sw_info) .build(), ) diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index 962bbd4..364e1fa 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -13,7 +13,7 @@ use tv::{TestResult, TestStatus}; async fn run_measure_step(step: &tv::StartedTestStep) -> Result { step.add_measurement("temperature", 42.5.into()).await?; step.add_measurement_with_details( - &tv::Measurement::builder("fan_speed", 1200.into()) + tv::Measurement::builder("fan_speed", 1200.into()) .unit("rpm") .build(), ) diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 515f5c4..cceafef 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -16,10 +16,10 @@ async fn run_measure_step( ram0: tv::DutHardwareInfo, ) -> Result { step.add_measurement_with_details( - &tv::Measurement::builder("temp0", 100.5.into()) + tv::Measurement::builder("temp0", 100.5.into()) .unit("F") .hardware_info(&ram0) - .subcomponent(&tv::Subcomponent::builder("chip0").build()) + .subcomponent(tv::Subcomponent::builder("chip0").build()) .build(), ) .await?; @@ -29,7 +29,7 @@ async fn run_measure_step( .unit("C") .hardware_info(&ram0) .subcomponent( - &tv::Subcomponent::builder("chip1") + tv::Subcomponent::builder("chip1") .location("U11") .version("1") .revision("1") @@ -59,7 +59,7 @@ async fn run_measure_step( async fn main() -> Result<()> { let mut dut = tv::DutInfo::builder("dut0") .name("host0.example.com") - .add_platform_info(&tv::PlatformInfo::new("memory-optimized")) + .add_platform_info(tv::PlatformInfo::new("memory-optimized")) .build(); dut.add_software_info( diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index 54202c6..a1e0299 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -13,9 +13,9 @@ use tv::{TestResult, TestStatus, ValidatorType}; async fn run_measure_step(step: &tv::StartedTestStep) -> Result { step.add_measurement_with_details( - &tv::Measurement::builder("temp", 40.into()) + tv::Measurement::builder("temp", 40.into()) .add_validator( - &tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) + tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) .name("gt_30") .build(), ) @@ -27,7 +27,7 @@ async fn run_measure_step(step: &tv::StartedTestStep) -> Result Result DutInfoBuilder { - self.platform_infos.push(platform_info.clone()); + pub fn add_platform_info(mut self, platform_info: PlatformInfo) -> DutInfoBuilder { + self.platform_infos.push(platform_info); self } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { - self.metadata.insert(key.to_string(), value.clone()); + self.metadata.insert(key.to_string(), value); self } @@ -520,7 +520,7 @@ mod tests { .name("dut") .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) - .add_platform_info(&PlatformInfo::builder("platform_info").build()) + .add_platform_info(PlatformInfo::builder("platform_info").build()) .build(); dut.add_software_info( diff --git a/src/output/error.rs b/src/output/error.rs index e7a3e72..3589cf4 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -9,6 +9,7 @@ use crate::spec; use tv::{dut, trait_ext::VecExt, DutSoftwareInfo}; /// TODO: docs +#[derive(Clone)] pub struct Error { symptom: String, message: Option, diff --git a/src/output/macros.rs b/src/output/macros.rs index d3d6dce..6993197 100644 --- a/src/output/macros.rs +++ b/src/output/macros.rs @@ -56,7 +56,7 @@ macro_rules! ocptv_error { ($runner:expr, $symptom:expr, $msg:expr) => { $runner.add_error_with_details( - &$crate::output::Error::builder($symptom) + $crate::output::Error::builder($symptom) .message($msg) .source(file!(), line!() as i32) .build(), @@ -65,7 +65,7 @@ macro_rules! ocptv_error { ($runner:expr, $symptom:expr) => { $runner.add_error_with_details( - &$crate::output::Error::builder($symptom) + $crate::output::Error::builder($symptom) .source(file!(), line!() as i32) .build(), ) @@ -106,7 +106,7 @@ macro_rules! ocptv_log { macro_rules! $name { ($artifact:expr, $msg:expr) => { $artifact.add_log_with_details( - &$crate::output::Log::builder($msg) + $crate::output::Log::builder($msg) .severity($severity) .source(file!(), line!() as i32) .build(), @@ -160,7 +160,7 @@ macro_rules! ocptv_diagnosis { macro_rules! $name { ($artifact:expr, $verdict:expr) => { $artifact.diagnosis_with_details( - &$crate::output::Diagnosis::builder($verdict, $diagnosis_type) + $crate::output::Diagnosis::builder($verdict, $diagnosis_type) .source(file!(), line!() as i32) .build(), ) diff --git a/src/output/measure.rs b/src/output/measure.rs index 06581e3..a697925 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -336,7 +336,7 @@ impl ValidatorBuilder { fn new(validator_type: spec::ValidatorType, value: tv::Value) -> Self { ValidatorBuilder { validator_type, - value: value.clone(), + value, name: None, metadata: BTreeMap::new(), } @@ -346,7 +346,7 @@ impl ValidatorBuilder { self } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> ValidatorBuilder { - self.metadata.insert(key.to_string(), value.clone()); + self.metadata.insert(key.to_string(), value); self } @@ -380,10 +380,10 @@ impl ValidatorBuilder { /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) -/// .subcomponent(&Subcomponent::builder("name").build()) +/// .subcomponent(Subcomponent::builder("name").build()) /// .build(); /// ``` #[derive(Default)] @@ -392,7 +392,7 @@ pub struct Measurement { value: tv::Value, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, @@ -412,7 +412,7 @@ impl Measurement { pub fn new(name: &str, value: tv::Value) -> Self { Measurement { name: name.to_string(), - value: value.clone(), + value, ..Default::default() } } @@ -428,10 +428,10 @@ impl Measurement { /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let measurement = Measurement::builder("name", 50.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) - /// .subcomponent(&Subcomponent::builder("name").build()) + /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); /// ``` pub fn builder(name: &str, value: tv::Value) -> MeasurementBuilder { @@ -452,10 +452,7 @@ impl Measurement { name: self.name.clone(), unit: self.unit.clone(), value: self.value.clone(), - validators: self - .validators - .clone() - .map(|vals| vals.iter().map(|val| val.to_spec()).collect()), + validators: self.validators.map_option(Validator::to_spec), hardware_info: self .hardware_info .as_ref() @@ -479,10 +476,10 @@ impl Measurement { /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// /// let builder = MeasurementBuilder::new("name", 50.into()) -/// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) -/// .subcomponent(&Subcomponent::builder("name").build()); +/// .subcomponent(Subcomponent::builder("name").build()); /// let measurement = builder.build(); /// ``` #[derive(Default)] @@ -491,7 +488,7 @@ pub struct MeasurementBuilder { value: tv::Value, unit: Option, - validators: Option>, + validators: Vec, hardware_info: Option, subcomponent: Option, @@ -511,7 +508,7 @@ impl MeasurementBuilder { pub fn new(name: &str, value: tv::Value) -> Self { MeasurementBuilder { name: name.to_string(), - value: value.clone(), + value, ..Default::default() } } @@ -523,16 +520,10 @@ impl MeasurementBuilder { /// ``` /// # use ocptv::output::*; /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()); + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()); /// ``` - pub fn add_validator(mut self, validator: &Validator) -> MeasurementBuilder { - self.validators = match self.validators { - Some(mut validators) => { - validators.push(validator.clone()); - Some(validators) - } - None => Some(vec![validator.clone()]), - }; + pub fn add_validator(mut self, validator: Validator) -> MeasurementBuilder { + self.validators.push(validator.clone()); self } @@ -560,10 +551,10 @@ impl MeasurementBuilder { /// ``` /// # use ocptv::output::*; /// let builder = MeasurementBuilder::new("name", 50.into()) - /// .subcomponent(&Subcomponent::builder("name").build()); + /// .subcomponent(Subcomponent::builder("name").build()); /// ``` - pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> MeasurementBuilder { - self.subcomponent = Some(subcomponent.clone()); + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> MeasurementBuilder { + self.subcomponent = Some(subcomponent); self } @@ -577,7 +568,7 @@ impl MeasurementBuilder { /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementBuilder { - self.metadata.insert(key.to_string(), value.clone()); + self.metadata.insert(key.to_string(), value); self } @@ -678,8 +669,8 @@ impl MeasurementSeriesInfoBuilder { self } - pub fn add_validator(mut self, validator: &Validator) -> MeasurementSeriesInfoBuilder { - self.validators.push(validator.clone()); + pub fn add_validator(mut self, validator: Validator) -> MeasurementSeriesInfoBuilder { + self.validators.push(validator); self } @@ -691,16 +682,13 @@ impl MeasurementSeriesInfoBuilder { self } - pub fn subcomponent( - mut self, - subcomponent: &dut::Subcomponent, - ) -> MeasurementSeriesInfoBuilder { - self.subcomponent = Some(subcomponent.clone()); + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> MeasurementSeriesInfoBuilder { + self.subcomponent = Some(subcomponent); self } pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesInfoBuilder { - self.metadata.insert(key.to_string(), value.clone()); + self.metadata.insert(key.to_string(), value); self } @@ -771,10 +759,10 @@ mod tests { let unit = "RPM"; let measurement = Measurement::builder(&name, value.clone()) .unit(unit) - .add_validator(&validator) - .add_validator(&validator) + .add_validator(validator.clone()) + .add_validator(validator.clone()) .hardware_info(&hw_info) - .subcomponent(&subcomponent) + .subcomponent(subcomponent.clone()) .add_metadata(meta_key, meta_value.clone()) .build(); diff --git a/src/output/run.rs b/src/output/run.rs index 6d184b1..b938304 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -149,7 +149,7 @@ impl TestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(&error).await?; + self.add_error_with_details(error).await?; Ok(()) } @@ -162,7 +162,7 @@ impl TestRun { pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(&error).await?; + self.add_error_with_details(error).await?; Ok(()) } @@ -172,7 +172,7 @@ impl TestRun { /// (eg. failing to discover a DUT). /// /// See: [`StartedTestRun::add_error_with_details`] for details and examples. - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; @@ -218,7 +218,7 @@ impl TestRunBuilder { /// .build(); /// ``` pub fn add_parameter(mut self, key: &str, value: tv::Value) -> TestRunBuilder { - self.parameters.insert(key.to_string(), value.clone()); + self.parameters.insert(key.to_string(), value); self } @@ -265,7 +265,7 @@ impl TestRunBuilder { /// .build(); /// ``` pub fn add_metadata(mut self, key: &str, value: tv::Value) -> TestRunBuilder { - self.metadata.insert(key.to_string(), value.clone()); + self.metadata.insert(key.to_string(), value); self } @@ -384,7 +384,7 @@ impl StartedTestRun { /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// run.add_log_with_details( - /// &Log::builder("This is a log message with INFO severity") + /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), @@ -394,7 +394,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_with_details(&self, log: log::Log) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -427,7 +427,7 @@ impl StartedTestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(&error).await?; + self.add_error_with_details(error).await?; Ok(()) } @@ -453,7 +453,7 @@ impl StartedTestRun { pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(&error).await?; + self.add_error_with_details(error).await?; Ok(()) } @@ -472,7 +472,7 @@ impl StartedTestRun { /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// /// run.add_error_with_details( - /// &Error::builder("symptom") + /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&sw_info) @@ -484,7 +484,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; diff --git a/src/output/step.rs b/src/output/step.rs index 301f4fc..54c75d7 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -213,7 +213,7 @@ impl StartedTestStep { /// /// let step = run.add_step("step_name").start().await?; /// step.add_log_with_details( - /// &Log::builder("This is a log message with INFO severity") + /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) /// .build(), @@ -223,7 +223,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: &log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_with_details(&self, log: log::Log) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -347,7 +347,7 @@ impl StartedTestStep { /// /// let step = run.add_step("step_name").start().await?; /// step.add_error_with_details( - /// &Error::builder("symptom") + /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) /// .add_software_info(&sw_info) @@ -358,7 +358,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: &error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -454,12 +454,12 @@ impl StartedTestStep { /// let step = run.add_step("step_name").start().await?; /// /// let measurement = Measurement::builder("name", 5000.into()) - /// .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) - /// .subcomponent(&Subcomponent::builder("name").build()) + /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); - /// step.add_measurement_with_details(&measurement).await?; + /// step.add_measurement_with_details(measurement).await?; /// /// step.end(TestStatus::Complete).await?; /// @@ -468,7 +468,7 @@ impl StartedTestStep { /// ``` pub async fn add_measurement_with_details( &self, - measurement: &measure::Measurement, + measurement: measure::Measurement, ) -> Result<(), tv::OcptvError> { self.step .emitter @@ -595,7 +595,7 @@ impl StartedTestStep { /// .subcomponent(&Subcomponent::builder("name").build()) /// .source("file.rs", 1) /// .build(); - /// step.diagnosis_with_details(&diagnosis).await?; + /// step.diagnosis_with_details(diagnosis).await?; /// /// step.end(TestStatus::Complete).await?; /// @@ -604,7 +604,7 @@ impl StartedTestStep { /// ``` pub async fn diagnosis_with_details( &self, - diagnosis: &diagnosis::Diagnosis, + diagnosis: diagnosis::Diagnosis, ) -> Result<(), tv::OcptvError> { self.step .emitter diff --git a/tests/output/diagnosis.rs b/tests/output/diagnosis.rs index 22bef87..ba43c10 100644 --- a/tests/output/diagnosis.rs +++ b/tests/output/diagnosis.rs @@ -77,7 +77,7 @@ async fn test_step_with_diagnosis_builder() -> Result<()> { .subcomponent(&Subcomponent::builder("name").build()) .message("message") .build(); - s.diagnosis_with_details(&diagnosis).await?; + s.diagnosis_with_details(diagnosis).await?; Ok(()) } diff --git a/tests/output/error.rs b/tests/output/error.rs index 3f5370b..2ce389c 100644 --- a/tests/output/error.rs +++ b/tests/output/error.rs @@ -87,7 +87,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { check_output_run(&expected, |r, dut| { async move { r.add_error_with_details( - &Error::builder("symptom") + Error::builder("symptom") .message("Error message") .source("file", 1) .add_software_info(dut.software_info("sw0").unwrap()) // must exist @@ -180,7 +180,7 @@ async fn test_testrun_with_error_with_details_before_start() -> Result<()> { async move { let run = run_builder.build(); run.add_error_with_details( - &Error::builder("no-dut") + Error::builder("no-dut") .message("failed to find dut") .source("file", 1) .build(), @@ -288,7 +288,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { check_output_step(&expected, |s, dut| { async move { s.add_error_with_details( - &Error::builder("symptom") + Error::builder("symptom") .message("Error message") .source("file", 1) .add_software_info(dut.software_info("sw0").unwrap()) diff --git a/tests/output/log.rs b/tests/output/log.rs index 9f54392..97cc5ec 100644 --- a/tests/output/log.rs +++ b/tests/output/log.rs @@ -68,7 +68,7 @@ async fn test_testrun_with_log_with_details() -> Result<()> { check_output_run(&expected, |r, _| { async { r.add_log_with_details( - &Log::builder("This is a log message with INFO severity") + Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) .build(), @@ -144,7 +144,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { check_output_step(&expected, |s, _| { async { s.add_log_with_details( - &Log::builder("This is a log message with INFO severity") + Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) .build(), diff --git a/tests/output/measure.rs b/tests/output/measure.rs index d0fa0a0..fa08c6b 100644 --- a/tests/output/measure.rs +++ b/tests/output/measure.rs @@ -85,13 +85,13 @@ async fn test_step_with_measurement_builder() -> Result<()> { let hw_info = dut.hardware_info("hw0").unwrap(); // must exist let measurement = Measurement::builder("name", 50.into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) + .subcomponent(Subcomponent::builder("name").build()) .build(); - s.add_measurement_with_details(&measurement).await?; + s.add_measurement_with_details(measurement).await?; Ok(()) } @@ -269,9 +269,9 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { .unit("unit") .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) - .add_validator(&Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) .hardware_info(hw_info) - .subcomponent(&Subcomponent::builder("name").build()) + .subcomponent(Subcomponent::builder("name").build()) .build(), ) .start() From b4358a0ae52cc4683562f3597fb1de19d207e58d Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 13:54:47 +0100 Subject: [PATCH 77/96] disallow user to instantiate builders directly - this simplifies the api by providing a single way to make builders Signed-off-by: mimir-d --- src/output/diagnosis.rs | 28 +++++++------------ src/output/dut.rs | 48 +++++++++++++++---------------- src/output/error.rs | 6 ++-- src/output/log.rs | 4 +-- src/output/measure.rs | 62 +++++++++++++++++------------------------ src/output/mod.rs | 6 ++-- src/output/run.rs | 18 ++++++------ 7 files changed, 77 insertions(+), 95 deletions(-) diff --git a/src/output/diagnosis.rs b/src/output/diagnosis.rs index 89c0507..9333427 100644 --- a/src/output/diagnosis.rs +++ b/src/output/diagnosis.rs @@ -142,15 +142,7 @@ pub struct DiagnosisBuilder { } impl DiagnosisBuilder { - /// Creates a new DiagnosisBuilder. - /// - /// # Examples - /// - /// ``` - /// # use ocptv::output::*; - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); - /// ``` - pub fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { + fn new(verdict: &str, diagnosis_type: spec::DiagnosisType) -> Self { DiagnosisBuilder { verdict: verdict.to_owned(), diagnosis_type, @@ -164,10 +156,10 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .message("message"); /// ``` - pub fn message(mut self, message: &str) -> DiagnosisBuilder { + pub fn message(mut self, message: &str) -> Self { self.message = Some(message.to_owned()); self } @@ -181,10 +173,10 @@ impl DiagnosisBuilder { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .hardware_info(&hw_info); /// ``` - pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> DiagnosisBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } @@ -195,10 +187,10 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .subcomponent(&Subcomponent::builder("name").build()); /// ``` - pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> DiagnosisBuilder { + pub fn subcomponent(mut self, subcomponent: &dut::Subcomponent) -> Self { self.subcomponent = Some(subcomponent.clone()); self } @@ -209,10 +201,10 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass) + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass) /// .source("file.rs", 1); /// ``` - pub fn source(mut self, file: &str, line: i32) -> DiagnosisBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_owned(), line, @@ -226,7 +218,7 @@ impl DiagnosisBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = DiagnosisBuilder::new("verdict", DiagnosisType::Pass); + /// let builder = Diagnosis::builder("verdict", DiagnosisType::Pass); /// let diagnosis = builder.build(); /// ``` pub fn build(self) -> Diagnosis { diff --git a/src/output/dut.rs b/src/output/dut.rs index 4a82a5a..f49a99e 100644 --- a/src/output/dut.rs +++ b/src/output/dut.rs @@ -92,24 +92,24 @@ pub struct DutInfoBuilder { } impl DutInfoBuilder { - pub fn new(id: &str) -> DutInfoBuilder { + fn new(id: &str) -> Self { DutInfoBuilder { id: id.to_string(), ..Default::default() } } - pub fn name(mut self, value: &str) -> DutInfoBuilder { + pub fn name(mut self, value: &str) -> Self { self.name = Some(value.to_string()); self } - pub fn add_platform_info(mut self, platform_info: PlatformInfo) -> DutInfoBuilder { + pub fn add_platform_info(mut self, platform_info: PlatformInfo) -> Self { self.platform_infos.push(platform_info); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> DutInfoBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { self.metadata.insert(key.to_string(), value); self } @@ -170,19 +170,19 @@ impl SubcomponentBuilder { revision: None, } } - pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> SubcomponentBuilder { + pub fn subcomponent_type(mut self, value: spec::SubcomponentType) -> Self { self.subcomponent_type = Some(value); self } - pub fn version(mut self, value: &str) -> SubcomponentBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn location(mut self, value: &str) -> SubcomponentBuilder { + pub fn location(mut self, value: &str) -> Self { self.location = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> SubcomponentBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } @@ -305,27 +305,27 @@ impl SoftwareInfoBuilder { } } - pub fn id(mut self, value: tv::Ident) -> SoftwareInfoBuilder { + pub fn id(mut self, value: tv::Ident) -> Self { self.id = value; self } - pub fn version(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } - pub fn software_type(mut self, value: spec::SoftwareType) -> SoftwareInfoBuilder { + pub fn software_type(mut self, value: spec::SoftwareType) -> Self { self.software_type = Some(value); self } - pub fn computer_system(mut self, value: &str) -> SoftwareInfoBuilder { + pub fn computer_system(mut self, value: &str) -> Self { self.computer_system = Some(value.to_string()); self } @@ -428,57 +428,57 @@ impl HardwareInfoBuilder { } } - pub fn id(mut self, value: tv::Ident) -> HardwareInfoBuilder { + pub fn id(mut self, value: tv::Ident) -> Self { self.id = value; self } - pub fn version(mut self, value: &str) -> HardwareInfoBuilder { + pub fn version(mut self, value: &str) -> Self { self.version = Some(value.to_string()); self } - pub fn revision(mut self, value: &str) -> HardwareInfoBuilder { + pub fn revision(mut self, value: &str) -> Self { self.revision = Some(value.to_string()); self } - pub fn location(mut self, value: &str) -> HardwareInfoBuilder { + pub fn location(mut self, value: &str) -> Self { self.location = Some(value.to_string()); self } - pub fn serial_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn serial_no(mut self, value: &str) -> Self { self.serial_no = Some(value.to_string()); self } - pub fn part_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn part_no(mut self, value: &str) -> Self { self.part_no = Some(value.to_string()); self } - pub fn manufacturer(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manufacturer(mut self, value: &str) -> Self { self.manufacturer = Some(value.to_string()); self } - pub fn manufacturer_part_no(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manufacturer_part_no(mut self, value: &str) -> Self { self.manufacturer_part_no = Some(value.to_string()); self } - pub fn odata_id(mut self, value: &str) -> HardwareInfoBuilder { + pub fn odata_id(mut self, value: &str) -> Self { self.odata_id = Some(value.to_string()); self } - pub fn computer_system(mut self, value: &str) -> HardwareInfoBuilder { + pub fn computer_system(mut self, value: &str) -> Self { self.computer_system = Some(value.to_string()); self } - pub fn manager(mut self, value: &str) -> HardwareInfoBuilder { + pub fn manager(mut self, value: &str) -> Self { self.manager = Some(value.to_string()); self } diff --git a/src/output/error.rs b/src/output/error.rs index 3589cf4..764a96c 100644 --- a/src/output/error.rs +++ b/src/output/error.rs @@ -49,12 +49,12 @@ impl ErrorBuilder { } } - pub fn message(mut self, value: &str) -> ErrorBuilder { + pub fn message(mut self, value: &str) -> Self { self.message = Some(value.to_string()); self } - pub fn source(mut self, file: &str, line: i32) -> ErrorBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, @@ -62,7 +62,7 @@ impl ErrorBuilder { self } - pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> ErrorBuilder { + pub fn add_software_info(mut self, software_info: &dut::DutSoftwareInfo) -> Self { self.software_infos.push(software_info.clone()); self } diff --git a/src/output/log.rs b/src/output/log.rs index dec9d4a..f39ad90 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -43,11 +43,11 @@ impl LogBuilder { source_location: None, } } - pub fn severity(mut self, value: spec::LogSeverity) -> LogBuilder { + pub fn severity(mut self, value: spec::LogSeverity) -> Self { self.severity = value; self } - pub fn source(mut self, file: &str, line: i32) -> LogBuilder { + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_string(), line, diff --git a/src/output/measure.rs b/src/output/measure.rs index a697925..cc32d55 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -273,7 +273,7 @@ pub struct MeasurementSeriesElemDetailsBuilder { } impl MeasurementSeriesElemDetailsBuilder { - pub fn new(value: tv::Value) -> Self { + fn new(value: tv::Value) -> Self { Self { value, ..Default::default() @@ -312,6 +312,7 @@ impl Validator { pub fn builder(validator_type: spec::ValidatorType, value: tv::Value) -> ValidatorBuilder { ValidatorBuilder::new(validator_type, value) } + pub fn to_spec(&self) -> spec::Validator { spec::Validator { name: self.name.clone(), @@ -341,11 +342,13 @@ impl ValidatorBuilder { metadata: BTreeMap::new(), } } - pub fn name(mut self, value: &str) -> ValidatorBuilder { + + pub fn name(mut self, value: &str) -> Self { self.name = Some(value.to_string()); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> ValidatorBuilder { + + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { self.metadata.insert(key.to_string(), value); self } @@ -475,7 +478,7 @@ impl Measurement { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// -/// let builder = MeasurementBuilder::new("name", 50.into()) +/// let builder = Measurement::builder("name", 50.into()) /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) /// .add_metadata("key", "value".into()) /// .hardware_info(&hw_info) @@ -497,15 +500,7 @@ pub struct MeasurementBuilder { } impl MeasurementBuilder { - /// Creates a new MeasurementBuilder. - /// - /// # Examples - /// - /// ``` - /// # use ocptv::output::*; - /// let builder = MeasurementBuilder::new("name", 50.into()); - /// ``` - pub fn new(name: &str, value: tv::Value) -> Self { + fn new(name: &str, value: tv::Value) -> Self { MeasurementBuilder { name: name.to_string(), value, @@ -519,10 +514,10 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = MeasurementBuilder::new("name", 50.into()) + /// let builder = Measurement::builder("name", 50.into()) /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()); /// ``` - pub fn add_validator(mut self, validator: Validator) -> MeasurementBuilder { + pub fn add_validator(mut self, validator: Validator) -> Self { self.validators.push(validator.clone()); self } @@ -536,10 +531,10 @@ impl MeasurementBuilder { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let builder = MeasurementBuilder::new("name", 50.into()) + /// let builder = Measurement::builder("name", 50.into()) /// .hardware_info(&hw_info); /// ``` - pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> MeasurementBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } @@ -550,10 +545,10 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = MeasurementBuilder::new("name", 50.into()) + /// let builder = Measurement::builder("name", 50.into()) /// .subcomponent(Subcomponent::builder("name").build()); /// ``` - pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> MeasurementBuilder { + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self { self.subcomponent = Some(subcomponent); self } @@ -565,9 +560,9 @@ impl MeasurementBuilder { /// ``` /// # use ocptv::output::*; /// let builder = - /// MeasurementBuilder::new("name", 50.into()).add_metadata("key", "value".into()); + /// Measurement::builder("name", 50.into()).add_metadata("key", "value".into()); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { self.metadata.insert(key.to_string(), value); self } @@ -578,7 +573,7 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = MeasurementBuilder::new("name", 50000.into()).unit("RPM"); + /// let builder = Measurement::builder("name", 50000.into()).unit("RPM"); /// ``` pub fn unit(mut self, unit: &str) -> MeasurementBuilder { self.unit = Some(unit.to_string()); @@ -590,10 +585,8 @@ impl MeasurementBuilder { /// # Examples /// /// ``` - /// use ocptv::output::MeasurementBuilder; - /// use ocptv::output::Value; - /// - /// let builder = MeasurementBuilder::new("name", 50.into()); + /// # use ocptv::output::*; + /// let builder = Measurement::builder("name", 50.into()); /// let measurement = builder.build(); /// ``` pub fn build(self) -> Measurement { @@ -651,7 +644,7 @@ pub struct MeasurementSeriesInfoBuilder { } impl MeasurementSeriesInfoBuilder { - pub fn new(name: &str) -> Self { + fn new(name: &str) -> Self { MeasurementSeriesInfoBuilder { id: Ident::Auto, name: name.to_string(), @@ -659,35 +652,32 @@ impl MeasurementSeriesInfoBuilder { } } - pub fn id(mut self, id: tv::Ident) -> MeasurementSeriesInfoBuilder { + pub fn id(mut self, id: tv::Ident) -> Self { self.id = id; self } - pub fn unit(mut self, unit: &str) -> MeasurementSeriesInfoBuilder { + pub fn unit(mut self, unit: &str) -> Self { self.unit = Some(unit.to_string()); self } - pub fn add_validator(mut self, validator: Validator) -> MeasurementSeriesInfoBuilder { + pub fn add_validator(mut self, validator: Validator) -> Self { self.validators.push(validator); self } - pub fn hardware_info( - mut self, - hardware_info: &dut::DutHardwareInfo, - ) -> MeasurementSeriesInfoBuilder { + pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { self.hardware_info = Some(hardware_info.clone()); self } - pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> MeasurementSeriesInfoBuilder { + pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self { self.subcomponent = Some(subcomponent); self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> MeasurementSeriesInfoBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { self.metadata.insert(key.to_string(), value); self } diff --git a/src/output/mod.rs b/src/output/mod.rs index 25970f2..27c2d20 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -33,8 +33,8 @@ pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; pub use measure::{ Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesElemDetails, - MeasurementSeriesInfo, MeasurementSeriesInfoBuilder, StartedMeasurementSeries, Validator, - ValidatorBuilder, + MeasurementSeriesElemDetailsBuilder, MeasurementSeriesInfo, MeasurementSeriesInfoBuilder, + StartedMeasurementSeries, Validator, ValidatorBuilder, }; pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; pub use step::{StartedTestStep, TestStep}; @@ -43,7 +43,7 @@ pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export this as a public type we present pub use serde_json::Value; -/// TODO: docs +// TODO: docs #[derive(Debug, thiserror::Error)] #[non_exhaustive] pub enum OcptvError { diff --git a/src/output/run.rs b/src/output/run.rs index b938304..66f5d7c 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -197,7 +197,7 @@ pub struct TestRunBuilder { } impl TestRunBuilder { - pub fn new(name: &str, version: &str) -> Self { + fn new(name: &str, version: &str) -> Self { Self { name: name.to_string(), version: version.to_string(), @@ -213,11 +213,11 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .add_parameter("param1", "value1".into()) /// .build(); /// ``` - pub fn add_parameter(mut self, key: &str, value: tv::Value) -> TestRunBuilder { + pub fn add_parameter(mut self, key: &str, value: tv::Value) -> Self { self.parameters.insert(key.to_string(), value); self } @@ -229,11 +229,11 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .command_line("my_diag --arg value") /// .build(); /// ``` - pub fn command_line(mut self, cmd: &str) -> TestRunBuilder { + pub fn command_line(mut self, cmd: &str) -> Self { self.command_line = cmd.to_string(); self } @@ -244,11 +244,11 @@ impl TestRunBuilder { /// /// ```rust /// # use ocptv::output::*; - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .config(Config::builder().build()) /// .build(); /// ``` - pub fn config(mut self, value: config::Config) -> TestRunBuilder { + pub fn config(mut self, value: config::Config) -> Self { self.config = Some(value); self } @@ -260,11 +260,11 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// - /// let run = TestRunBuilder::new("run_name", "1.0") + /// let run = TestRun::builder("run_name", "1.0") /// .add_metadata("meta1", "value1".into()) /// .build(); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> TestRunBuilder { + pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { self.metadata.insert(key.to_string(), value); self } From d419646475c867e75624cf1164d890f7879fdbf9 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 13:56:26 +0100 Subject: [PATCH 78/96] make all the spec enums non_exhaustive - this was missing from a publicly exported DiagnosisType, which means that it risks breaking crate api if changed - for safety reasons, just make all the enums (private or public) non_exhaustive Signed-off-by: mimir-d --- src/spec.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/spec.rs b/src/spec.rs index 8424fe9..a0cb26a 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -111,6 +111,7 @@ pub enum SubcomponentType { /// /// schema ref: #[derive(Debug, Serialize, PartialEq, Clone, Default)] +#[non_exhaustive] pub enum DiagnosisType { #[serde(rename = "PASS")] #[default] @@ -217,6 +218,7 @@ pub struct Root { } #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum RootImpl { #[serde(rename = "schemaVersion")] SchemaVersion(SchemaVersion), @@ -270,6 +272,7 @@ pub struct TestRunArtifact { } #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum TestRunArtifactImpl { #[serde(rename = "testRunStart")] TestRunStart(TestRunStart), @@ -576,6 +579,7 @@ pub struct TestStepArtifact { #[allow(clippy::large_enum_variant)] #[derive(Debug, Serialize, PartialEq, Clone)] +#[non_exhaustive] pub enum TestStepArtifactImpl { #[serde(rename = "testStepStart")] TestStepStart(TestStepStart), From 359c60b9c807f5c1121f3cc30e7b3504ed668409 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 14:13:05 +0100 Subject: [PATCH 79/96] shorten some method names - a bit less verbose - diagnosis was missing a verb Signed-off-by: mimir-d --- examples/error_with_dut.rs | 2 +- examples/measurement_series.rs | 20 ++++----- examples/measurement_single.rs | 2 +- examples/measurement_subcomponent.rs | 6 +-- examples/measurement_validators.rs | 8 ++-- src/output/macros.rs | 8 ++-- src/output/measure.rs | 62 ++++++++++++++-------------- src/output/mod.rs | 4 +- src/output/run.rs | 28 ++++++------- src/output/step.rs | 42 +++++++++---------- tests/output/config.rs | 2 +- tests/output/diagnosis.rs | 4 +- tests/output/error.rs | 13 +++--- tests/output/log.rs | 4 +- tests/output/measure.rs | 26 ++++++------ 15 files changed, 115 insertions(+), 116 deletions(-) diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index 6983090..76284fe 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -26,7 +26,7 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| { async move { - r.add_error_with_details( + r.add_error_detail( tv::Error::builder("power-fail") .add_software_info(&sw_info) .build(), diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index cfb046a..06c00d5 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -14,8 +14,8 @@ use tv::{TestResult, TestStatus}; async fn step0_measurements(step: &tv::StartedTestStep) -> Result { let fan_speed = step - .add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("fan_speed") + .add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("fan_speed") .unit("rpm") .build(), ) @@ -32,8 +32,8 @@ async fn step0_measurements(step: &tv::StartedTestStep) -> Result Result { - step.add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("temp0") + step.add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("temp0") .unit("C") .build(), ) @@ -41,8 +41,8 @@ async fn step1_measurements(step: &tv::StartedTestStep) -> Result Result Result { let freq0 = step - .add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("freq0") + .add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("freq0") .unit("hz") .build(), ) @@ -69,8 +69,8 @@ async fn step2_measurements(step: &tv::StartedTestStep) -> Result Result { step.add_measurement("temperature", 42.5.into()).await?; - step.add_measurement_with_details( + step.add_measurement_detail( tv::Measurement::builder("fan_speed", 1200.into()) .unit("rpm") .build(), diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index cceafef..63b14e0 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -15,7 +15,7 @@ async fn run_measure_step( step: &tv::StartedTestStep, ram0: tv::DutHardwareInfo, ) -> Result { - step.add_measurement_with_details( + step.add_measurement_detail( tv::Measurement::builder("temp0", 100.5.into()) .unit("F") .hardware_info(&ram0) @@ -24,8 +24,8 @@ async fn run_measure_step( ) .await?; - let chip1_temp = step.add_measurement_series_with_details( - tv::MeasurementSeriesInfo::builder("temp1") + let chip1_temp = step.add_measurement_series_detail( + tv::MeasurementSeriesDetail::builder("temp1") .unit("C") .hardware_info(&ram0) .subcomponent( diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index a1e0299..3b71e94 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -12,7 +12,7 @@ use ocptv::output as tv; use tv::{TestResult, TestStatus, ValidatorType}; async fn run_measure_step(step: &tv::StartedTestStep) -> Result { - step.add_measurement_with_details( + step.add_measurement_detail( tv::Measurement::builder("temp", 40.into()) .add_validator( tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) @@ -23,8 +23,8 @@ async fn run_measure_step(step: &tv::StartedTestStep) -> Result Result { - $runner.add_error_with_details( + $runner.add_error_detail( $crate::output::Error::builder($symptom) .message($msg) .source(file!(), line!() as i32) @@ -64,7 +64,7 @@ macro_rules! ocptv_error { }; ($runner:expr, $symptom:expr) => { - $runner.add_error_with_details( + $runner.add_error_detail( $crate::output::Error::builder($symptom) .source(file!(), line!() as i32) .build(), @@ -105,7 +105,7 @@ macro_rules! ocptv_log { #[macro_export] macro_rules! $name { ($artifact:expr, $msg:expr) => { - $artifact.add_log_with_details( + $artifact.add_log_detail( $crate::output::Log::builder($msg) .severity($severity) .source(file!(), line!() as i32) @@ -159,7 +159,7 @@ macro_rules! ocptv_diagnosis { #[macro_export] macro_rules! $name { ($artifact:expr, $verdict:expr) => { - $artifact.diagnosis_with_details( + $artifact.add_diagnosis_detail( $crate::output::Diagnosis::builder($verdict, $diagnosis_type) .source(file!(), line!() as i32) .build(), diff --git a/src/output/measure.rs b/src/output/measure.rs index cc32d55..11620de 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -22,7 +22,7 @@ use tv::{dut, step, Ident}; /// ref: pub struct MeasurementSeries { id: String, - info: MeasurementSeriesInfo, + detail: MeasurementSeriesDetail, emitter: Arc, } @@ -32,12 +32,12 @@ impl MeasurementSeries { // instances through the `StartedTestStep.add_measurement_series_*` apis pub(crate) fn new( series_id: &str, - info: MeasurementSeriesInfo, + info: MeasurementSeriesDetail, emitter: Arc, ) -> Self { Self { id: series_id.to_owned(), - info, + detail: info, emitter, } } @@ -62,7 +62,7 @@ impl MeasurementSeries { /// # }); /// ``` pub async fn start(self) -> Result { - let info = &self.info; + let info = &self.detail; let start = spec::MeasurementSeriesStart { name: info.name.clone(), @@ -195,7 +195,7 @@ impl StartedMeasurementSeries { /// # }); /// ``` pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError> { - self.add_measurement_with_details(MeasurementSeriesElemDetails { + self.add_measurement_detail(MeasurementElementDetail { value, ..Default::default() }) @@ -217,24 +217,24 @@ impl StartedMeasurementSeries { /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; - /// let elem = MeasurementSeriesElemDetails::builder(60.into()).add_metadata("key", "value".into()).build(); - /// series.add_measurement_with_details(elem).await?; + /// let elem = MeasurementElementDetail::builder(60.into()).add_metadata("key", "value".into()).build(); + /// series.add_measurement_detail(elem).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement_with_details( + pub async fn add_measurement_detail( &self, - details: MeasurementSeriesElemDetails, + element: MeasurementElementDetail, ) -> Result<(), tv::OcptvError> { let element = spec::MeasurementSeriesElement { index: self.incr_seqno(), - value: details.value, - timestamp: details + value: element.value, + timestamp: element .timestamp .unwrap_or(self.parent.emitter.timestamp_provider().now()), series_id: self.parent.id.clone(), - metadata: details.metadata.option(), + metadata: element.metadata.option(), }; self.parent @@ -250,29 +250,29 @@ impl StartedMeasurementSeries { /// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesElemDetails { +pub struct MeasurementElementDetail { value: tv::Value, timestamp: Option>, metadata: BTreeMap, } -impl MeasurementSeriesElemDetails { - pub fn builder(value: tv::Value) -> MeasurementSeriesElemDetailsBuilder { - MeasurementSeriesElemDetailsBuilder::new(value) +impl MeasurementElementDetail { + pub fn builder(value: tv::Value) -> MeasurementElementDetailBuilder { + MeasurementElementDetailBuilder::new(value) } } /// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesElemDetailsBuilder { +pub struct MeasurementElementDetailBuilder { value: tv::Value, timestamp: Option>, metadata: BTreeMap, } -impl MeasurementSeriesElemDetailsBuilder { +impl MeasurementElementDetailBuilder { fn new(value: tv::Value) -> Self { Self { value, @@ -290,8 +290,8 @@ impl MeasurementSeriesElemDetailsBuilder { self } - pub fn build(self) -> MeasurementSeriesElemDetails { - MeasurementSeriesElemDetails { + pub fn build(self) -> MeasurementElementDetail { + MeasurementElementDetail { value: self.value, timestamp: self.timestamp, metadata: self.metadata, @@ -603,7 +603,7 @@ impl MeasurementBuilder { } /// TODO: docs -pub struct MeasurementSeriesInfo { +pub struct MeasurementSeriesDetail { // note: this object is crate public and we need access to this field // when making a new series in `StartedTestStep.add_measurement_series*` pub(crate) id: tv::Ident, @@ -618,19 +618,19 @@ pub struct MeasurementSeriesInfo { metadata: BTreeMap, } -impl MeasurementSeriesInfo { - pub fn new(name: &str) -> MeasurementSeriesInfo { - MeasurementSeriesInfoBuilder::new(name).build() +impl MeasurementSeriesDetail { + pub fn new(name: &str) -> MeasurementSeriesDetail { + MeasurementSeriesDetailBuilder::new(name).build() } - pub fn builder(name: &str) -> MeasurementSeriesInfoBuilder { - MeasurementSeriesInfoBuilder::new(name) + pub fn builder(name: &str) -> MeasurementSeriesDetailBuilder { + MeasurementSeriesDetailBuilder::new(name) } } /// TODO: docs #[derive(Default)] -pub struct MeasurementSeriesInfoBuilder { +pub struct MeasurementSeriesDetailBuilder { id: tv::Ident, name: String, @@ -643,9 +643,9 @@ pub struct MeasurementSeriesInfoBuilder { metadata: BTreeMap, } -impl MeasurementSeriesInfoBuilder { +impl MeasurementSeriesDetailBuilder { fn new(name: &str) -> Self { - MeasurementSeriesInfoBuilder { + MeasurementSeriesDetailBuilder { id: Ident::Auto, name: name.to_string(), ..Default::default() @@ -682,8 +682,8 @@ impl MeasurementSeriesInfoBuilder { self } - pub fn build(self) -> MeasurementSeriesInfo { - MeasurementSeriesInfo { + pub fn build(self) -> MeasurementSeriesDetail { + MeasurementSeriesDetail { id: self.id, name: self.name, unit: self.unit, diff --git a/src/output/mod.rs b/src/output/mod.rs index 27c2d20..362c9b2 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -32,8 +32,8 @@ pub use dut::{ pub use error::{Error, ErrorBuilder}; pub use log::{Log, LogBuilder}; pub use measure::{ - Measurement, MeasurementBuilder, MeasurementSeries, MeasurementSeriesElemDetails, - MeasurementSeriesElemDetailsBuilder, MeasurementSeriesInfo, MeasurementSeriesInfoBuilder, + Measurement, MeasurementBuilder, MeasurementElementDetail, MeasurementElementDetailBuilder, + MeasurementSeries, MeasurementSeriesDetail, MeasurementSeriesDetailBuilder, StartedMeasurementSeries, Validator, ValidatorBuilder, }; pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; diff --git a/src/output/run.rs b/src/output/run.rs index 66f5d7c..3b5856d 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -149,7 +149,7 @@ impl TestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -158,11 +158,11 @@ impl TestRun { /// This operation is useful in such cases when there is an error before starting the test. /// (eg. failing to discover a DUT). /// - /// See: [`StartedTestRun::add_error_with_msg`] for details and examples. - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + /// See: [`StartedTestRun::add_error_msg`] for details and examples. + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -171,8 +171,8 @@ impl TestRun { /// This operation is useful in such cases when there is an error before starting the test. /// (eg. failing to discover a DUT). /// - /// See: [`StartedTestRun::add_error_with_details`] for details and examples. - pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { + /// See: [`StartedTestRun::add_error_detail`] for details and examples. + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; @@ -383,7 +383,7 @@ impl StartedTestRun { /// # use ocptv::output::*; /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; - /// run.add_log_with_details( + /// run.add_log_detail( /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) @@ -394,7 +394,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Log(log.to_artifact()), }; @@ -427,7 +427,7 @@ impl StartedTestRun { pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).build(); - self.add_error_with_details(error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -444,16 +444,16 @@ impl StartedTestRun { /// # use ocptv::output::*; /// let dut = DutInfo::builder("my_dut").build(); /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; - /// run.add_error_with_msg("symptom", "error messasge").await?; + /// run.add_error_msg("symptom", "error messasge").await?; /// run.end(TestStatus::Complete, TestResult::Pass).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); - self.add_error_with_details(error).await?; + self.add_error_detail(error).await?; Ok(()) } @@ -471,7 +471,7 @@ impl StartedTestRun { /// let sw_info = dut.add_software_info(SoftwareInfo::builder("name").build()); /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// - /// run.add_error_with_details( + /// run.add_error_detail( /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) @@ -484,7 +484,7 @@ impl StartedTestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { let artifact = spec::TestRunArtifact { artifact: spec::TestRunArtifactImpl::Error(error.to_artifact()), }; diff --git a/src/output/step.rs b/src/output/step.rs index 54c75d7..5bb1488 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -212,7 +212,7 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_log_with_details( + /// step.add_log_detail( /// Log::builder("This is a log message with INFO severity") /// .severity(LogSeverity::Info) /// .source("file", 1) @@ -223,7 +223,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_log_with_details(&self, log: log::Log) -> Result<(), tv::OcptvError> { + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Log(log.to_artifact())) @@ -296,7 +296,7 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_error_with_msg("symptom", "error message").await?; + /// step.add_error_msg("symptom", "error message").await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) @@ -320,7 +320,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError> { let error = error::Error::builder(symptom).message(msg).build(); self.step @@ -346,7 +346,7 @@ impl StartedTestStep { /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_error_with_details( + /// step.add_error_detail( /// Error::builder("symptom") /// .message("Error message") /// .source("file", 1) @@ -358,7 +358,7 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_error_with_details(&self, error: error::Error) -> Result<(), tv::OcptvError> { + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&TestStepArtifactImpl::Error(error.to_artifact())) @@ -459,21 +459,21 @@ impl StartedTestStep { /// .hardware_info(&hw_info) /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); - /// step.add_measurement_with_details(measurement).await?; + /// step.add_measurement_detail(measurement).await?; /// /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement_with_details( + pub async fn add_measurement_detail( &self, - measurement: measure::Measurement, + detail: measure::Measurement, ) -> Result<(), tv::OcptvError> { self.step .emitter .emit(&spec::TestStepArtifactImpl::Measurement( - measurement.to_artifact(), + detail.to_artifact(), )) .await?; @@ -499,11 +499,11 @@ impl StartedTestStep { /// # }); /// ``` pub fn add_measurement_series(&self, name: &str) -> tv::MeasurementSeries { - self.add_measurement_series_with_details(tv::MeasurementSeriesInfo::new(name)) + self.add_measurement_series_detail(tv::MeasurementSeriesDetail::new(name)) } /// Create a Measurement Series (a time-series list of measurements). - /// This method accepts a [`tv::MeasurementSeriesInfo`] object. + /// This method accepts a [`tv::MeasurementSeriesDetail`] object. /// /// ref: /// @@ -516,19 +516,19 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// let series = - /// step.add_measurement_series_with_details(MeasurementSeriesInfo::new("name")); + /// step.add_measurement_series_detail(MeasurementSeriesDetail::new("name")); /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub fn add_measurement_series_with_details( + pub fn add_measurement_series_detail( &self, - info: measure::MeasurementSeriesInfo, + detail: measure::MeasurementSeriesDetail, ) -> tv::MeasurementSeries { // spec says this identifier is unique in the scope of the test run, so create it from // the step identifier and a counter // ref: https://github.com/opencomputeproject/ocp-diag-core/blob/main/json_spec/README.md#measurementseriesstart - let series_id = match &info.id { + let series_id = match &detail.id { Ident::Auto => format!( "{}_series{}", self.step.emitter.step_id, @@ -537,7 +537,7 @@ impl StartedTestStep { Ident::Exact(value) => value.to_owned(), }; - tv::MeasurementSeries::new(&series_id, info, Arc::clone(&self.step.emitter)) + tv::MeasurementSeries::new(&series_id, detail, Arc::clone(&self.step.emitter)) } /// Emits a Diagnosis message. @@ -553,13 +553,13 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.diagnosis("verdict", DiagnosisType::Pass).await?; + /// step.add_diagnosis("verdict", DiagnosisType::Pass).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn diagnosis( + pub async fn add_diagnosis( &self, verdict: &str, diagnosis_type: spec::DiagnosisType, @@ -595,14 +595,14 @@ impl StartedTestStep { /// .subcomponent(&Subcomponent::builder("name").build()) /// .source("file.rs", 1) /// .build(); - /// step.diagnosis_with_details(diagnosis).await?; + /// step.add_diagnosis_detail(diagnosis).await?; /// /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn diagnosis_with_details( + pub async fn add_diagnosis_detail( &self, diagnosis: diagnosis::Diagnosis, ) -> Result<(), tv::OcptvError> { diff --git a/tests/output/config.rs b/tests/output/config.rs index 75f0531..81f0c57 100644 --- a/tests/output/config.rs +++ b/tests/output/config.rs @@ -72,7 +72,7 @@ async fn test_config_builder_with_file() -> Result<()> { .start(dut) .await?; - run.add_error_with_msg("symptom", "Error message").await?; + run.add_error_msg("symptom", "Error message").await?; run.end(TestStatus::Complete, TestResult::Pass).await?; diff --git a/tests/output/diagnosis.rs b/tests/output/diagnosis.rs index ba43c10..b2e7786 100644 --- a/tests/output/diagnosis.rs +++ b/tests/output/diagnosis.rs @@ -35,7 +35,7 @@ async fn test_step_with_diagnosis() -> Result<()> { check_output_step(&expected, |s, _| { async { - s.diagnosis("verdict", DiagnosisType::Pass).await?; + s.add_diagnosis("verdict", DiagnosisType::Pass).await?; Ok(()) } @@ -77,7 +77,7 @@ async fn test_step_with_diagnosis_builder() -> Result<()> { .subcomponent(&Subcomponent::builder("name").build()) .message("message") .build(); - s.diagnosis_with_details(diagnosis).await?; + s.add_diagnosis_detail(diagnosis).await?; Ok(()) } diff --git a/tests/output/error.rs b/tests/output/error.rs index 2ce389c..ce647d4 100644 --- a/tests/output/error.rs +++ b/tests/output/error.rs @@ -54,7 +54,7 @@ async fn test_testrun_with_error_with_message() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { r.add_error_with_msg("symptom", "Error message").await }.boxed() + async { r.add_error_msg("symptom", "Error message").await }.boxed() }) .await } @@ -86,7 +86,7 @@ async fn test_testrun_with_error_with_details() -> Result<()> { check_output_run(&expected, |r, dut| { async move { - r.add_error_with_details( + r.add_error_detail( Error::builder("symptom") .message("Error message") .source("file", 1) @@ -146,8 +146,7 @@ async fn test_testrun_with_error_with_message_before_start() -> Result<()> { check_output(&expected, |run_builder, _| { async move { let run = run_builder.build(); - run.add_error_with_msg("no-dut", "failed to find dut") - .await?; + run.add_error_msg("no-dut", "failed to find dut").await?; Ok(()) } @@ -179,7 +178,7 @@ async fn test_testrun_with_error_with_details_before_start() -> Result<()> { check_output(&expected, |run_builder, _| { async move { let run = run_builder.build(); - run.add_error_with_details( + run.add_error_detail( Error::builder("no-dut") .message("failed to find dut") .source("file", 1) @@ -248,7 +247,7 @@ async fn test_testrun_step_error_with_message() -> Result<()> { check_output_step(&expected, |s, _| { async { - s.add_error_with_msg("symptom", "Error message").await?; + s.add_error_msg("symptom", "Error message").await?; Ok(()) } @@ -287,7 +286,7 @@ async fn test_testrun_step_error_with_details() -> Result<()> { check_output_step(&expected, |s, dut| { async move { - s.add_error_with_details( + s.add_error_detail( Error::builder("symptom") .message("Error message") .source("file", 1) diff --git a/tests/output/log.rs b/tests/output/log.rs index 97cc5ec..e3ae311 100644 --- a/tests/output/log.rs +++ b/tests/output/log.rs @@ -67,7 +67,7 @@ async fn test_testrun_with_log_with_details() -> Result<()> { check_output_run(&expected, |r, _| { async { - r.add_log_with_details( + r.add_log_detail( Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) @@ -143,7 +143,7 @@ async fn test_testrun_step_log_with_details() -> Result<()> { check_output_step(&expected, |s, _| { async { - s.add_log_with_details( + s.add_log_detail( Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) .source("file", 1) diff --git a/tests/output/measure.rs b/tests/output/measure.rs index fa08c6b..61b037d 100644 --- a/tests/output/measure.rs +++ b/tests/output/measure.rs @@ -9,8 +9,8 @@ use futures::FutureExt; use serde_json::json; use ocptv::output::{ - Ident, Measurement, MeasurementSeriesElemDetails, MeasurementSeriesInfo, Subcomponent, - Validator, ValidatorType, + Ident, Measurement, MeasurementElementDetail, MeasurementSeriesDetail, Subcomponent, Validator, + ValidatorType, }; use super::fixture::*; @@ -91,7 +91,7 @@ async fn test_step_with_measurement_builder() -> Result<()> { .hardware_info(hw_info) .subcomponent(Subcomponent::builder("name").build()) .build(); - s.add_measurement_with_details(measurement).await?; + s.add_measurement_detail(measurement).await?; Ok(()) } @@ -263,8 +263,8 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { let hw_info = dut.hardware_info("hw0").unwrap(); // must exist let series = s - .add_measurement_series_with_details( - MeasurementSeriesInfo::builder("name") + .add_measurement_series_detail( + MeasurementSeriesDetail::builder("name") .id(Ident::Exact("series_id".to_owned())) .unit("unit") .add_metadata("key", "value".into()) @@ -483,8 +483,8 @@ async fn test_step_with_measurement_series_element_with_details() -> Result<()> async { let series = s.add_measurement_series("name").start().await?; series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) + .add_measurement_detail( + MeasurementElementDetail::builder(60.into()) .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) .add_metadata("key", "value".into()) .add_metadata("key2", "value2".into()) @@ -579,22 +579,22 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(60.into()) + .add_measurement_detail( + MeasurementElementDetail::builder(60.into()) .add_metadata("key", "value".into()) .build(), ) .await?; series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(70.into()) + .add_measurement_detail( + MeasurementElementDetail::builder(70.into()) .add_metadata("key2", "value2".into()) .build(), ) .await?; series - .add_measurement_with_details( - MeasurementSeriesElemDetails::builder(80.into()) + .add_measurement_detail( + MeasurementElementDetail::builder(80.into()) .add_metadata("key3", "value3".into()) .build(), ) From 2b36b0d4c8c583d654b036ad2b01b0c02bd93681 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 11:13:23 +0000 Subject: [PATCH 80/96] Add example for File API Signed-off-by: Giovanni Colapinto --- Cargo.toml | 4 ++++ examples/file.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/file.rs diff --git a/Cargo.toml b/Cargo.toml index 57bf0ef..a83c167 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,3 +81,7 @@ required-features = ["boxed-scopes"] [[example]] name = "simple_step_fail" required-features = ["boxed-scopes"] + +[[example]] +name = "file" +required-features = ["boxed-scopes"] \ No newline at end of file diff --git a/examples/file.rs b/examples/file.rs new file mode 100644 index 0000000..345de23 --- /dev/null +++ b/examples/file.rs @@ -0,0 +1,54 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use rand::Rng; +use tv::{TestResult, TestStatus}; + +fn get_fan_speed() -> i32 { + let mut rng = rand::thread_rng(); + rng.gen_range(1500..1700) +} + +async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + } else { + step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + } + + Ok(TestStatus::Complete) +} + +/// Simple demo with diagnosis. +#[tokio::main] +async fn main() -> Result<()> { + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| { s.file("output") }.boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From 2757d0cd684df8b3dc84153eb2a6c0b801e1cce9 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 13:25:17 +0000 Subject: [PATCH 81/96] Rebase and fix file example Signed-off-by: Giovanni Colapinto --- examples/file.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/examples/file.rs b/examples/file.rs index 345de23..9fe5743 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -4,31 +4,22 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::str::FromStr; + use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; -use rand::Rng; use tv::{TestResult, TestStatus}; -fn get_fan_speed() -> i32 { - let mut rng = rand::thread_rng(); - rng.gen_range(1500..1700) -} - -async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { - let fan_speed = get_fan_speed(); - - if fan_speed >= 1600 { - step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; - } else { - step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; - } +async fn run_file_step(step: &tv::StartedTestStep) -> Result { + let uri = tv::Uri::from_str("file:///root/mem_cfg_log").unwrap(); + step.file("mem_cfg_log", uri).await?; Ok(TestStatus::Complete) } -/// Simple demo with diagnosis. +/// Simple demo with file. #[tokio::main] async fn main() -> Result<()> { let dut = tv::DutInfo::builder("dut0").build(); @@ -38,7 +29,7 @@ async fn main() -> Result<()> { .scope(dut, |r| { async move { r.add_step("step0") - .scope(|s| { s.file("output") }.boxed()) + .scope(|s| run_file_step(s).boxed()) .await?; Ok(tv::TestRunOutcome { From b25c7692f1a12de6a830413559773a7a52000ddf Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 11:13:23 +0000 Subject: [PATCH 82/96] Add example for File API Signed-off-by: Giovanni Colapinto --- Cargo.toml | 4 ++++ examples/file.rs | 54 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 examples/file.rs diff --git a/Cargo.toml b/Cargo.toml index b49bee2..dd23e91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,4 +85,8 @@ required-features = ["boxed-scopes"] [[example]] name = "diagnosis" +required-features = ["boxed-scopes"] + +[[example]] +name = "file" required-features = ["boxed-scopes"] \ No newline at end of file diff --git a/examples/file.rs b/examples/file.rs new file mode 100644 index 0000000..345de23 --- /dev/null +++ b/examples/file.rs @@ -0,0 +1,54 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use rand::Rng; +use tv::{TestResult, TestStatus}; + +fn get_fan_speed() -> i32 { + let mut rng = rand::thread_rng(); + rng.gen_range(1500..1700) +} + +async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + } else { + step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + } + + Ok(TestStatus::Complete) +} + +/// Simple demo with diagnosis. +#[tokio::main] +async fn main() -> Result<()> { + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + r.add_step("step0") + .scope(|s| { s.file("output") }.boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} From a17057ee14e58cc391c3b5f1bcf6917bf8cab693 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 13:25:17 +0000 Subject: [PATCH 83/96] Rebase and fix file example Signed-off-by: Giovanni Colapinto --- examples/file.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/examples/file.rs b/examples/file.rs index 345de23..9fe5743 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -4,31 +4,22 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::str::FromStr; + use anyhow::Result; use futures::FutureExt; use ocptv::output as tv; -use rand::Rng; use tv::{TestResult, TestStatus}; -fn get_fan_speed() -> i32 { - let mut rng = rand::thread_rng(); - rng.gen_range(1500..1700) -} - -async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { - let fan_speed = get_fan_speed(); - - if fan_speed >= 1600 { - step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; - } else { - step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; - } +async fn run_file_step(step: &tv::StartedTestStep) -> Result { + let uri = tv::Uri::from_str("file:///root/mem_cfg_log").unwrap(); + step.file("mem_cfg_log", uri).await?; Ok(TestStatus::Complete) } -/// Simple demo with diagnosis. +/// Simple demo with file. #[tokio::main] async fn main() -> Result<()> { let dut = tv::DutInfo::builder("dut0").build(); @@ -38,7 +29,7 @@ async fn main() -> Result<()> { .scope(dut, |r| { async move { r.add_step("step0") - .scope(|s| { s.file("output") }.boxed()) + .scope(|s| run_file_step(s).boxed()) .await?; Ok(tv::TestRunOutcome { From bbbab46bfea95cfbad5c35ddbb7a25f37bea6083 Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 08:02:31 +0000 Subject: [PATCH 84/96] Reviewing README.md Signed-off-by: Giovanni Colapinto --- README.md | 205 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 204 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index faffc57..34d83d9 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,207 @@ [![codecov](https://codecov.io/github/opencomputeproject/ocp-diag-core-rust/graph/badge.svg?token=IJOG0T8XZ3)](https://codecov.io/github/opencomputeproject/ocp-diag-core-rust) -WIP \ No newline at end of file +The **OCP Test & Validation Initiative** is a collaboration between datacenter hyperscalers having the goal of standardizing aspects of the hardware validation/diagnosis space, along with providing necessary tooling to enable both diagnostic developers and executors to leverage these interfaces. + +Specifically, the [ocp-diag-core-rust](https://github.com/opencomputeproject/ocp-diag-core-rust) project makes it easy for developers to use the **OCP Test & Validation specification** artifacts by presenting a pure-rust api that can be used to output spec compliant JSON messages. + +To start, please see below for [installation instructions](https://github.com/opencomputeproject/ocp-diag-core-rust#installation) and [usage](https://github.com/opencomputeproject/ocp-diag-core-rust#usage). + +This project is part of [ocp-diag-core](https://github.com/opencomputeproject/ocp-diag-core) and exists under the same [MIT License Agreement](https://github.com/opencomputeproject/ocp-diag-core-rust/LICENSE). + +### Installation + +Stable releases of the **ocp-diag-core-rust** codebase are published to **crates.io** under the package name [ocptv](https://crates.io/crates/ocptv), and can be easily installed with rust package manager. + +For general usage, the following steps should be sufficient to get the latest stable version using the [Package Installer for Rust](https://github.com/rust-lang/cargo): + +- **\[option 1]** using `cargo add` + + ```bash + $ cargo add ocptv + ``` + +- **\[option 2]** specifying it in Cargo.toml file + + + ```toml + [dependencies] + ocptv = "1.0.0" + ``` + and then run + ```bash + $ cargo update + ``` + +To use the bleeding edge instead of the stable version, the git repository should be cloned. +This assumes that the clone is manually kept up to date by git pulling whenever there are new commits upstream. All of the installation methods below will automatically use the latest library code. + +First clone the upstream latest code: +```bash +$ git clone https://github.com/opencomputeproject/ocp-diag-core-rust.git +$ cd ocp-diag-core-rust +$ git checkout dev # dev branch has the latest code +``` +Then edit Cargo.toml in your project and add + +``` +[dependencies] +ocptv = { version = "1.0.0", path = "/path/to/ocp-diag-core-rust" } +``` + +The instructions above assume a Linux-type system. However, the steps should be identical on Windows and MacOS platforms. + +See [The Cargo Book](https://doc.rust-lang.org/cargo/index.html) for more details on how to use cargo. + +### Usage + +The specification does not impose any particular level of usage. To be compliant, a diagnostic package just needs output the correct artifact messages in the correct format. However, any particular such diagnostic is free to choose what aspects it needs to use/output; eg. a simple validation test may not output any measurements, opting to just have a final Diagnosis outcome. + +**Full API reference is available [here](https://docs.rs/ocptv).** + +A very simple starter example, which just outputs a diagnosis: +```rust +use anyhow::Result; +use futures::FutureExt; + +use ocptv::output as tv; +use ocptv::{ocptv_diagnosis_fail, ocptv_diagnosis_pass}; +use rand::Rng; +use tv::{TestResult, TestStatus}; + +fn get_fan_speed() -> i32 { + let mut rng = rand::thread_rng(); + rng.gen_range(1500..1700) +} + +async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + } else { + step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + } + + Ok(TestStatus::Complete) +} + +async fn run_diagnosis_macros_step( + step: &tv::StartedTestStep, +) -> Result { + let fan_speed = get_fan_speed(); + + if fan_speed >= 1600 { + ocptv_diagnosis_pass!(step, "fan_ok").await?; + } else { + ocptv_diagnosis_fail!(step, "fan_low").await?; + } + + Ok(TestStatus::Complete) +} + +#[tokio::main] +async fn main() -> Result<()> { + let dut = tv::DutInfo::builder("dut0").build(); + + tv::TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| { + async move { + /// add diagnosis without source location + r.add_step("step0") + .scope(|s| run_diagnosis_step(s).boxed()) + .await?; + /// using the macro, the source location is filled automatically + r.add_step("step1") + .scope(|s| run_diagnosis_macros_step(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + } + .boxed() + }) + .await?; + + Ok(()) +} +``` + +Expected output (slightly reformatted for readability): + +```json +{"sequenceNumber":0, "schemaVersion":{"major":2,"minor":0},"timestamp":"2024-10-11T11:39:07.026Z"} + +{"sequenceNumber":1,"testRunArtifact":{ + "testRunStart":{ + "name":"simple diagnosis", + "commandLine":"","parameters":{},"version":"1.0", + "dutInfo":{"dutInfoId":"dut0"} + }},"timestamp":"2024-10-11T11:39:07.026Z"} + +{"sequenceNumber":2,"testStepArtifact":{ + "testStepId":"step0","testStepStart":{"name":"step0"} + },"timestamp":"2024-10-11T11:39:07.026Z"} + +{"sequenceNumber":3,"testStepArtifact":{ + "diagnosis":{"type":"PASS","verdict":"fan_ok"}, + "testStepId":"step0"},"timestamp":"2024-10-11T11:39:07.027Z"} + +{"sequenceNumber":4,"testStepArtifact":{ + "testStepEnd":{"status":"COMPLETE"},"testStepId":"step0" + },"timestamp":"2024-10-11T11:39:07.027Z"} + +{"sequenceNumber":5,"testStepArtifact":{ + "testStepId":"step1","testStepStart":{"name":"step1"} + },"timestamp":"2024-10-11T11:39:07.027Z"} + +{"sequenceNumber":6,"testStepArtifact":{ + "diagnosis":{ + "sourceLocation":{"file":"examples/diagnosis.rs","line":40}, + "type":"FAIL","verdict":"fan_low" + },"testStepId":"step1" + },"timestamp":"2024-10-11T11:39:07.027Z"} + +{"sequenceNumber":7,"testStepArtifact":{ + "testStepEnd":{"status":"COMPLETE"},"testStepId":"step1" + },"timestamp":"2024-10-11T11:39:07.027Z"} + +{"sequenceNumber":8,"testRunArtifact":{ + "testRunEnd":{"result":"PASS","status":"COMPLETE"} + },"timestamp":"2024-10-11T11:39:07.027Z"} + +``` + +### Examples + +The examples in [examples folder](https://github.com/opencomputeproject/ocp-diag-core-rust/tree/dev/examples) could be run using cargo. + +This is one of the example configured in Cargo.toml: + +```toml +[[example]] +name = "diagnosis" +required-features = ["boxed-scopes"] +``` + +```bash +# run diagnosis example +$ cargo run --example diagnosis --features="boxed-scopes" +``` + +### Developer notes + +If you would like to contribute, please head over to [developer notes](https://github.com/opencomputeproject/ocp-diag-core-rust/tree/dev/DEVELOPERS.md) for instructions. + +### Contact + +Feel free to start a new [discussion](https://github.com/opencomputeproject/ocp-diag-core-rust/discussions), or otherwise post an [issue/request](https://github.com/opencomputeproject/ocp-diag-core-rust/issues). + +An email contact is also available at: ocp-test-validation@OCP-All.groups.io + + \ No newline at end of file From 278990877f7f048023d51fdad3585adb1420558f Mon Sep 17 00:00:00 2001 From: mimir-d Date: Fri, 11 Oct 2024 16:45:57 +0100 Subject: [PATCH 85/96] remove workflow_dispatch trigger - add dev branch for actions instead of allowing manual trigger; this will cover the same pushes Signed-off-by: mimir-d --- .github/workflows/check.yaml | 3 +-- .github/workflows/test.yaml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check.yaml b/.github/workflows/check.yaml index cd816e3..4a7c0f3 100644 --- a/.github/workflows/check.yaml +++ b/.github/workflows/check.yaml @@ -2,9 +2,8 @@ name: check on: push: - branches: [main] + branches: [dev, main] pull_request: - workflow_dispatch: # only read-only for GITHUB_TOKEN permissions: diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 0776c8a..729a877 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -2,9 +2,8 @@ name: test on: push: - branches: [main] + branches: [dev, main] pull_request: - workflow_dispatch: # only read-only for GITHUB_TOKEN permissions: From 867af04cab8c39e3299139543824df7af8b71adc Mon Sep 17 00:00:00 2001 From: Giovanni Colapinto Date: Fri, 11 Oct 2024 16:55:02 +0000 Subject: [PATCH 86/96] Fix README Signed-off-by: Giovanni Colapinto --- README.md | 47 ++++++++++++++++++----------------------------- 1 file changed, 18 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 34d83d9..17d6328 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ This project is part of [ocp-diag-core](https://github.com/opencomputeproject/oc ### Installation -Stable releases of the **ocp-diag-core-rust** codebase are published to **crates.io** under the package name [ocptv](https://crates.io/crates/ocptv), and can be easily installed with rust package manager. +Stable releases of the **ocp-diag-core-rust** codebase are published to **crates.io** under the package name [ocptv](https://crates.io/crates/ocptv), and can be easily installed with cargo. For general usage, the following steps should be sufficient to get the latest stable version using the [Package Installer for Rust](https://github.com/rust-lang/cargo): @@ -27,27 +27,14 @@ For general usage, the following steps should be sufficient to get the latest st ```toml [dependencies] - ocptv = "1.0.0" + ocptv = "0.1.0" ``` - and then run - ```bash - $ cargo update - ``` - -To use the bleeding edge instead of the stable version, the git repository should be cloned. -This assumes that the clone is manually kept up to date by git pulling whenever there are new commits upstream. All of the installation methods below will automatically use the latest library code. - -First clone the upstream latest code: -```bash -$ git clone https://github.com/opencomputeproject/ocp-diag-core-rust.git -$ cd ocp-diag-core-rust -$ git checkout dev # dev branch has the latest code -``` -Then edit Cargo.toml in your project and add + +To use the bleeding edge instead of the stable version, the dependecies section should be modified like this: ``` [dependencies] -ocptv = { version = "1.0.0", path = "/path/to/ocp-diag-core-rust" } +ocptv = { git = "https://github.com/opencomputeproject/ocp-diag-core-rust.git", branch = "dev" } ``` The instructions above assume a Linux-type system. However, the steps should be identical on Windows and MacOS platforms. @@ -56,7 +43,17 @@ See [The Cargo Book](https://doc.rust-lang.org/cargo/index.html) for more detail ### Usage -The specification does not impose any particular level of usage. To be compliant, a diagnostic package just needs output the correct artifact messages in the correct format. However, any particular such diagnostic is free to choose what aspects it needs to use/output; eg. a simple validation test may not output any measurements, opting to just have a final Diagnosis outcome. +The [specification](https://github.com/opencomputeproject/ocp-diag-core/tree/main/json_spec) does not impose any particular level of usage. To be compliant, a diagnostic package just needs output the correct artifact messages in the correct format. However, any particular such diagnostic is free to choose what aspects it needs to use/output; eg. a simple validation test may not output any measurements, opting to just have a final Diagnosis outcome. + +This crate provides the "boxed-scopes" feature. It guarantees to emit the close message every time is needed (i.e. TestRun, TestStep, etc.). +To enable the feature in Cargo.toml: + +```toml + [dependencies] + ocptv = { version = "0.1.0", features = ["boxed-scopes"] } +``` + +If the feature is not used, is up to the user to close correctly all the API that need it. **Full API reference is available [here](https://docs.rs/ocptv).** @@ -79,9 +76,9 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result= 1600 { - step.diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; + step.add_diagnosis("fan_ok", tv::DiagnosisType::Pass).await?; } else { - step.diagnosis("fan_low", tv::DiagnosisType::Fail).await?; + step.add_diagnosis("fan_low", tv::DiagnosisType::Fail).await?; } Ok(TestStatus::Complete) @@ -180,14 +177,6 @@ Expected output (slightly reformatted for readability): The examples in [examples folder](https://github.com/opencomputeproject/ocp-diag-core-rust/tree/dev/examples) could be run using cargo. -This is one of the example configured in Cargo.toml: - -```toml -[[example]] -name = "diagnosis" -required-features = ["boxed-scopes"] -``` - ```bash # run diagnosis example $ cargo run --example diagnosis --features="boxed-scopes" From 41e074a7fa255863a7137629b848c28dee16b84f Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 14:49:16 +0100 Subject: [PATCH 87/96] make codecov/patch informational - the PR coverage frequently fails because it's going down due to `await` calls not being correctly recognized by llvm-cov - disable this check, and only keep the total project coverage Signed-off-by: mimir-d --- .github/codecov.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/codecov.yaml b/.github/codecov.yaml index cce915d..108479e 100644 --- a/.github/codecov.yaml +++ b/.github/codecov.yaml @@ -9,6 +9,9 @@ coverage: default: # Avoid false negatives threshold: 1% + patch: + default: + informational: true comment: layout: "condensed_header, condensed_files" From 2db3e47f0b0add35170cf24088c902ee269ebad7 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 13:39:42 +0100 Subject: [PATCH 88/96] resolve the typestate scope in a different way - context: `end()` methods need to consume self so that the user must not use the contextual object after having emitted the end artifact. However, this introduces an issue in `scope()` methods, in that we need to pass the run object both to the async lambda and to call end, which forces the lambda to capture by reference. We cannot move into the lambda because we need to consume self in `end()`, so the lambda must receive a reference. This leads to needing to use a boxed future, because rust syntax doesn't have a way to specify captured lifetimes in async anon. - since `end()` is the only contentious method, make a new type `ScopedTestRun` which has everything except `end` and pass that with lifetime 'static into the lambda. This removes the need for the boxed future since no shorter refs are captured. Signed-off-by: mimir-d --- Cargo.lock | 12 ++++++ Cargo.toml | 15 +------ examples/custom_writer.rs | 18 ++++----- examples/diagnosis.rs | 25 +++++------- examples/error_with_dut.rs | 26 +++++------- examples/extensions.rs | 15 +++---- examples/file.rs | 21 +++++----- examples/measurement_series.rs | 39 +++++++++--------- examples/measurement_single.rs | 21 +++++----- examples/measurement_subcomponent.rs | 19 ++++----- examples/measurement_validators.rs | 19 ++++----- examples/simple_run_skip.rs | 2 - examples/simple_step_fail.rs | 37 ++++++++--------- src/output/mod.rs | 2 +- src/output/run.rs | 60 +++++++++++++++++++++------- tests/output/error.rs | 4 +- tests/output/fixture.rs | 19 ++++++--- tests/output/log.rs | 4 +- tests/output/run.rs | 18 ++++----- tests/output/step.rs | 2 +- 20 files changed, 190 insertions(+), 188 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 934cbb4..a68e694 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,17 @@ dependencies = [ "syn", ] +[[package]] +name = "delegate" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc2323e10c92e1cf4d86e11538512e6dc03ceb586842970b6332af3d4046a046" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "deranged" version = "0.3.11" @@ -682,6 +693,7 @@ dependencies = [ "async-trait", "chrono", "chrono-tz", + "delegate", "futures", "maplit", "mime", diff --git a/Cargo.toml b/Cargo.toml index dd23e91..1635da7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ boxed-scopes = [] async-trait = "0.1.83" chrono = "0.4.38" chrono-tz = "0.10.0" +delegate = "0.13.1" futures = "0.3.30" maplit = "1.0.2" mime = "0.3.17" @@ -47,14 +48,6 @@ unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage,coverage_nightly)', ] } -[[example]] -name = "custom_writer" -required-features = ["boxed-scopes"] - -[[example]] -name = "error_with_dut" -required-features = ["boxed-scopes"] - [[example]] name = "extensions" required-features = ["boxed-scopes"] @@ -75,10 +68,6 @@ required-features = ["boxed-scopes"] name = "measurement_validators" required-features = ["boxed-scopes"] -[[example]] -name = "simple_run_skip" -required-features = ["boxed-scopes"] - [[example]] name = "simple_step_fail" required-features = ["boxed-scopes"] @@ -89,4 +78,4 @@ required-features = ["boxed-scopes"] [[example]] name = "file" -required-features = ["boxed-scopes"] \ No newline at end of file +required-features = ["boxed-scopes"] diff --git a/examples/custom_writer.rs b/examples/custom_writer.rs index bc2dee4..ad73ea3 100644 --- a/examples/custom_writer.rs +++ b/examples/custom_writer.rs @@ -8,7 +8,6 @@ use std::io; use anyhow::Result; use async_trait::async_trait; -use futures::FutureExt; use tokio::sync::mpsc; use ocptv::ocptv_log_debug; @@ -45,16 +44,13 @@ async fn main() -> Result<()> { tv::TestRun::builder("extensions", "1.0") .config(config) .build() - .scope(dut, |r| { - async move { - ocptv_log_debug!(r, "log debug").await?; - - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + .scope(dut, |r| async move { + ocptv_log_debug!(r, "log debug").await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs index dbdc5ec..f351df6 100644 --- a/examples/diagnosis.rs +++ b/examples/diagnosis.rs @@ -52,21 +52,18 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| run_diagnosis_step(s).boxed()) - .await?; - r.add_step("step1") - .scope(|s| run_diagnosis_macros_step(s).boxed()) - .await?; + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| run_diagnosis_step(s).boxed()) + .await?; + r.add_step("step1") + .scope(|s| run_diagnosis_macros_step(s).boxed()) + .await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index 76284fe..c855d7d 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{SoftwareType, TestResult, TestStatus}; @@ -24,21 +23,18 @@ async fn main() -> Result<()> { #[cfg(feature = "boxed-scopes")] tv::TestRun::builder("run error with dut", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_error_detail( - tv::Error::builder("power-fail") - .add_software_info(&sw_info) - .build(), - ) - .await?; + .scope(dut, |r| async move { + r.add_error_detail( + tv::Error::builder("power-fail") + .add_software_info(&sw_info) + .build(), + ) + .await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Fail, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) }) .await?; diff --git a/examples/extensions.rs b/examples/extensions.rs index 1b46a3f..d718069 100644 --- a/examples/extensions.rs +++ b/examples/extensions.rs @@ -48,16 +48,13 @@ async fn main() -> Result<()> { tv::TestRun::builder("extensions", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0").scope(|s| step0(s).boxed()).await?; + .scope(dut, |r| async move { + r.add_step("step0").scope(|s| step0(s).boxed()).await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/file.rs b/examples/file.rs index 0188b17..2bef304 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -26,18 +26,15 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| run_file_step(s).boxed()) - .await?; - - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| run_file_step(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index 06c00d5..70b5e33 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -97,27 +97,24 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| step0_measurements(s).boxed()) - .await?; - - #[cfg(feature = "boxed-scopes")] - r.add_step("step1") - .scope(|s| step1_measurements(s).boxed()) - .await?; - - r.add_step("step2") - .scope(|s| step2_measurements(s).boxed()) - .await?; - - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| step0_measurements(s).boxed()) + .await?; + + #[cfg(feature = "boxed-scopes")] + r.add_step("step1") + .scope(|s| step1_measurements(s).boxed()) + .await?; + + r.add_step("step2") + .scope(|s| step2_measurements(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index e4160f4..5a64527 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -29,18 +29,15 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| run_measure_step(s).boxed()) - .await?; - - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| run_measure_step(s).boxed()) + .await?; + + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 63b14e0..61aeb7a 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -88,18 +88,15 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| run_measure_step(s, ram0).boxed()) - .await?; + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| run_measure_step(s, ram0).boxed()) + .await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index 3b71e94..20bac17 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -59,18 +59,15 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| run_measure_step(s).boxed()) - .await?; + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| run_measure_step(s).boxed()) + .await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/examples/simple_run_skip.rs b/examples/simple_run_skip.rs index c8b5d6d..5a1d650 100644 --- a/examples/simple_run_skip.rs +++ b/examples/simple_run_skip.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{TestResult, TestStatus}; @@ -26,7 +25,6 @@ async fn main() -> Result<()> { result: TestResult::NotApplicable, }); } - .boxed() }) .await?; diff --git a/examples/simple_step_fail.rs b/examples/simple_step_fail.rs index a0e9c75..af6387e 100644 --- a/examples/simple_step_fail.rs +++ b/examples/simple_step_fail.rs @@ -19,28 +19,25 @@ async fn main() -> Result<()> { tv::TestRun::builder("step fail", "1.0") .build() - .scope(dut, |r| { - async move { - r.add_step("step0") - .scope(|s| { - async move { - ocptv_log_info!(s, "info log").await?; - Ok(TestStatus::Complete) - } - .boxed() - }) - .await?; + .scope(dut, |r| async move { + r.add_step("step0") + .scope(|s| { + async move { + ocptv_log_info!(s, "info log").await?; + Ok(TestStatus::Complete) + } + .boxed() + }) + .await?; - r.add_step("step1") - .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) - .await?; + r.add_step("step1") + .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) + .await?; - Ok(tv::TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Fail, - }) - } - .boxed() + Ok(tv::TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Fail, + }) }) .await?; diff --git a/src/output/mod.rs b/src/output/mod.rs index 83e865e..101ad0b 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -38,7 +38,7 @@ pub use measure::{ MeasurementSeries, MeasurementSeriesDetail, MeasurementSeriesDetailBuilder, StartedMeasurementSeries, Validator, ValidatorBuilder, }; -pub use run::{StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; +pub use run::{ScopedTestRun, StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; pub use step::{StartedTestStep, TestStep}; pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; diff --git a/src/output/run.rs b/src/output/run.rs index 3b5856d..0245a20 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -4,15 +4,16 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#[cfg(feature = "boxed-scopes")] -use futures::future::BoxFuture; use std::collections::BTreeMap; use std::env; +use std::future::Future; use std::sync::{ atomic::{self, Ordering}, Arc, }; +use delegate::delegate; + use crate::output as tv; use crate::spec; use tv::step::TestStep; @@ -128,14 +129,17 @@ impl TestRun { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - #[cfg(feature = "boxed-scopes")] - pub async fn scope(self, dut: dut::DutInfo, func: F) -> Result<(), tv::OcptvError> + pub async fn scope(self, dut: dut::DutInfo, func: F) -> Result<(), tv::OcptvError> where - F: FnOnce(&StartedTestRun) -> BoxFuture<'_, Result>, + R: Future> + Send + 'static, + F: FnOnce(ScopedTestRun) -> R, { - let run = self.start(dut).await?; - let outcome = func(&run).await?; - run.end(outcome.status, outcome.result).await?; + let run = Arc::new(self.start(dut).await?); + let outcome = func(ScopedTestRun { + run: Arc::clone(&run), + }) + .await?; + run.end_impl(outcome.status, outcome.result).await?; Ok(()) } @@ -302,6 +306,21 @@ impl StartedTestRun { } } + // note: keep the self-consuming method for crate api, but use this one internally, + // since `StartedTestRun::end` only needs to take ownership for syntactic reasons + async fn end_impl( + &self, + status: spec::TestStatus, + result: spec::TestResult, + ) -> Result<(), tv::OcptvError> { + let end = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { + artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), + }); + + self.run.emitter.emit(&end).await?; + Ok(()) + } + /// Ends the test run. /// /// ref: @@ -323,12 +342,7 @@ impl StartedTestRun { status: spec::TestStatus, result: spec::TestResult, ) -> Result<(), tv::OcptvError> { - let end = spec::RootImpl::TestRunArtifact(spec::TestRunArtifact { - artifact: spec::TestRunArtifactImpl::TestRunEnd(spec::TestRunEnd { status, result }), - }); - - self.run.emitter.emit(&end).await?; - Ok(()) + self.end_impl(status, result).await } /// Emits a Log message. @@ -503,3 +517,21 @@ impl StartedTestRun { TestStep::new(&step_id, name, Arc::clone(&self.run.emitter)) } } + +/// TODO: docs +pub struct ScopedTestRun { + run: Arc, +} + +impl ScopedTestRun { + delegate! { + to self.run { + pub async fn add_log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError>; + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError>; + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError>; + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError>; + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError>; + pub fn add_step(&self, name: &str) -> TestStep; + } + } +} diff --git a/tests/output/error.rs b/tests/output/error.rs index ce647d4..2134c26 100644 --- a/tests/output/error.rs +++ b/tests/output/error.rs @@ -30,7 +30,7 @@ async fn test_testrun_with_error() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { r.add_error("symptom").await }.boxed() + async move { r.add_error("symptom").await }.boxed() }) .await } @@ -54,7 +54,7 @@ async fn test_testrun_with_error_with_message() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { r.add_error_msg("symptom", "Error message").await }.boxed() + async move { r.add_error_msg("symptom", "Error message").await }.boxed() }) .await } diff --git a/tests/output/fixture.rs b/tests/output/fixture.rs index ff21d32..5c038b8 100644 --- a/tests/output/fixture.rs +++ b/tests/output/fixture.rs @@ -10,11 +10,12 @@ use anyhow::Result; use assert_json_diff::assert_json_eq; use futures::future::BoxFuture; use futures::future::Future; +use ocptv::output::TestRunOutcome; use serde_json::json; use tokio::sync::Mutex; use ocptv::output::{ - Config, DutInfo, HardwareInfo, Ident, OcptvError, SoftwareInfo, SoftwareType, StartedTestRun, + Config, DutInfo, HardwareInfo, Ident, OcptvError, ScopedTestRun, SoftwareInfo, SoftwareType, StartedTestStep, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, SPEC_VERSION, }; @@ -152,16 +153,22 @@ where Ok(()) } -pub async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> +pub async fn check_output_run(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestRun, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, + R: Future> + Send + 'static, + F: FnOnce(ScopedTestRun, DutInfo) -> R + Send + 'static, { check_output(expected, |run_builder, dut| async move { let run = run_builder.build(); - let run = run.start(dut.clone()).await?; - test_fn(&run, dut).await?; - run.end(TestStatus::Complete, TestResult::Pass).await?; + run.scope(dut.clone(), |run| async move { + test_fn(run, dut).await?; + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + }) + .await?; Ok(()) }) diff --git a/tests/output/log.rs b/tests/output/log.rs index e3ae311..0a1826b 100644 --- a/tests/output/log.rs +++ b/tests/output/log.rs @@ -31,7 +31,7 @@ async fn test_testrun_with_log() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { + async move { r.add_log( LogSeverity::Info, "This is a log message with INFO severity", @@ -66,7 +66,7 @@ async fn test_testrun_with_log_with_details() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { + async move { r.add_log_detail( Log::builder("This is a log message with INFO severity") .severity(LogSeverity::Info) diff --git a/tests/output/run.rs b/tests/output/run.rs index 1da27f3..bf0ac79 100644 --- a/tests/output/run.rs +++ b/tests/output/run.rs @@ -8,7 +8,6 @@ use std::sync::Arc; use anyhow::Result; use assert_json_diff::assert_json_include; -use futures::FutureExt; use serde_json::json; use tokio::sync::Mutex; @@ -24,7 +23,7 @@ async fn test_testrun_start_and_end() -> Result<()> { json_run_pass(2), ]; - check_output_run(&expected, |_, _| async { Ok(()) }.boxed()).await + check_output_run(&expected, |_, _| async { Ok(()) }).await } #[cfg(feature = "boxed-scopes")] @@ -51,16 +50,13 @@ async fn test_testrun_with_scope() -> Result<()> { check_output(&expected, |run_builder, dut| async { let run = run_builder.build(); - run.scope(dut, |r| { - async move { - r.add_log(LogSeverity::Info, "First message").await?; + run.scope(dut, |r| async move { + r.add_log(LogSeverity::Info, "First message").await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, - }) - } - .boxed() + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) .await?; diff --git a/tests/output/step.rs b/tests/output/step.rs index b74026d..bd9c3a3 100644 --- a/tests/output/step.rs +++ b/tests/output/step.rs @@ -53,7 +53,7 @@ async fn test_testrun_step_scope_log() -> Result<()> { ]; check_output_run(&expected, |r, _| { - async { + async move { r.add_step("first step") .scope(|s| { async move { From 53a6e23772c170e30f5c932321adcb7e789d6751 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 14:18:56 +0100 Subject: [PATCH 89/96] refactor scopes for step + measurement series - similar to previous commit, this removes the dependency on boxed futures; remove the cargo dependency as well, and the feature flag Signed-off-by: mimir-d --- Cargo.toml | 36 ------- examples/diagnosis.rs | 15 +-- examples/error_with_dut.rs | 1 - examples/extensions.rs | 5 +- examples/file.rs | 5 +- examples/measurement_series.rs | 16 ++- examples/measurement_single.rs | 5 +- examples/measurement_subcomponent.rs | 14 +-- examples/measurement_validators.rs | 16 +-- examples/simple_step_fail.rs | 12 +-- src/output/measure.rs | 64 ++++++++---- src/output/mod.rs | 2 +- src/output/run.rs | 2 + src/output/step.rs | 144 ++++++++++++++++++--------- tests/output/diagnosis.rs | 11 +- tests/output/error.rs | 99 ++++++++---------- tests/output/file.rs | 29 ++---- tests/output/fixture.rs | 45 ++++----- tests/output/log.rs | 73 ++++++-------- tests/output/measure.rs | 104 ++++++++----------- tests/output/run.rs | 1 - tests/output/step.rs | 91 +++++++++-------- 22 files changed, 372 insertions(+), 418 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1635da7..35f5686 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,15 +8,11 @@ repository = "https://github.com/opencomputeproject/ocp-diag-core-rust" license = "MIT" edition = "2021" -[features] -boxed-scopes = [] - [dependencies] async-trait = "0.1.83" chrono = "0.4.38" chrono-tz = "0.10.0" delegate = "0.13.1" -futures = "0.3.30" maplit = "1.0.2" mime = "0.3.17" serde = { version = "1.0.210", features = ["derive"] } @@ -47,35 +43,3 @@ rand = "0.8.5" unexpected_cfgs = { level = "warn", check-cfg = [ 'cfg(coverage,coverage_nightly)', ] } - -[[example]] -name = "extensions" -required-features = ["boxed-scopes"] - -[[example]] -name = "measurement_series" -required-features = ["boxed-scopes"] - -[[example]] -name = "measurement_single" -required-features = ["boxed-scopes"] - -[[example]] -name = "measurement_subcomponent" -required-features = ["boxed-scopes"] - -[[example]] -name = "measurement_validators" -required-features = ["boxed-scopes"] - -[[example]] -name = "simple_step_fail" -required-features = ["boxed-scopes"] - -[[example]] -name = "diagnosis" -required-features = ["boxed-scopes"] - -[[example]] -name = "file" -required-features = ["boxed-scopes"] diff --git a/examples/diagnosis.rs b/examples/diagnosis.rs index f351df6..59c23a6 100644 --- a/examples/diagnosis.rs +++ b/examples/diagnosis.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use ocptv::{ocptv_diagnosis_fail, ocptv_diagnosis_pass}; @@ -17,7 +16,7 @@ fn get_fan_speed() -> i32 { rng.gen_range(1500..1700) } -async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { +async fn run_diagnosis_step(step: tv::ScopedTestStep) -> Result { let fan_speed = get_fan_speed(); if fan_speed >= 1600 { @@ -31,9 +30,7 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result Result { +async fn run_diagnosis_macros_step(step: tv::ScopedTestStep) -> Result { let fan_speed = get_fan_speed(); if fan_speed >= 1600 { @@ -53,12 +50,8 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0") - .scope(|s| run_diagnosis_step(s).boxed()) - .await?; - r.add_step("step1") - .scope(|s| run_diagnosis_macros_step(s).boxed()) - .await?; + r.add_step("step0").scope(run_diagnosis_step).await?; + r.add_step("step1").scope(run_diagnosis_macros_step).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/error_with_dut.rs b/examples/error_with_dut.rs index c855d7d..9194802 100644 --- a/examples/error_with_dut.rs +++ b/examples/error_with_dut.rs @@ -20,7 +20,6 @@ async fn main() -> Result<()> { .build(), ); - #[cfg(feature = "boxed-scopes")] tv::TestRun::builder("run error with dut", "1.0") .build() .scope(dut, |r| async move { diff --git a/examples/extensions.rs b/examples/extensions.rs index d718069..f78886f 100644 --- a/examples/extensions.rs +++ b/examples/extensions.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde::Serialize; use ocptv::output as tv; @@ -25,7 +24,7 @@ struct ComplexExtension { subtypes: Vec, } -async fn step0(s: &tv::StartedTestStep) -> Result { +async fn step0(s: tv::ScopedTestStep) -> Result { s.add_extension("simple", "extension_identifier").await?; s.add_extension( @@ -49,7 +48,7 @@ async fn main() -> Result<()> { tv::TestRun::builder("extensions", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0").scope(|s| step0(s).boxed()).await?; + r.add_step("step0").scope(step0).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/file.rs b/examples/file.rs index 2bef304..4ee475e 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -7,12 +7,11 @@ use std::str::FromStr; use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{TestResult, TestStatus}; -async fn run_file_step(step: &tv::StartedTestStep) -> Result { +async fn run_file_step(step: tv::ScopedTestStep) -> Result { let uri = tv::Uri::from_str("file:///root/mem_cfg_log").unwrap(); step.add_file("mem_cfg_log", uri).await?; @@ -28,7 +27,7 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| async move { r.add_step("step0") - .scope(|s| run_file_step(s).boxed()) + .scope(run_file_step) .await?; Ok(tv::TestRunOutcome { diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index 70b5e33..7a325b9 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -7,12 +7,11 @@ use anyhow::Result; use chrono::Duration; -use futures::FutureExt; use ocptv::output::{self as tv}; use tv::{TestResult, TestStatus}; -async fn step0_measurements(step: &tv::StartedTestStep) -> Result { +async fn step0_measurements(step: tv::ScopedTestStep) -> Result { let fan_speed = step .add_measurement_series_detail( tv::MeasurementSeriesDetail::builder("fan_speed") @@ -30,8 +29,7 @@ async fn step0_measurements(step: &tv::StartedTestStep) -> Result Result { +async fn step1_measurements(step: tv::ScopedTestStep) -> Result { step.add_measurement_series_detail( tv::MeasurementSeriesDetail::builder("temp0") .unit("C") @@ -51,14 +49,13 @@ async fn step1_measurements(step: &tv::StartedTestStep) -> Result Result { +async fn step2_measurements(step: tv::ScopedTestStep) -> Result { let freq0 = step .add_measurement_series_detail( tv::MeasurementSeriesDetail::builder("freq0") @@ -99,16 +96,15 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| async move { r.add_step("step0") - .scope(|s| step0_measurements(s).boxed()) + .scope(step0_measurements) .await?; - #[cfg(feature = "boxed-scopes")] r.add_step("step1") - .scope(|s| step1_measurements(s).boxed()) + .scope(step1_measurements) .await?; r.add_step("step2") - .scope(|s| step2_measurements(s).boxed()) + .scope(step2_measurements) .await?; Ok(tv::TestRunOutcome { diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index 5a64527..c39b984 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -5,12 +5,11 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{TestResult, TestStatus}; -async fn run_measure_step(step: &tv::StartedTestStep) -> Result { +async fn run_measure_step(step: tv::ScopedTestStep) -> Result { step.add_measurement("temperature", 42.5.into()).await?; step.add_measurement_detail( tv::Measurement::builder("fan_speed", 1200.into()) @@ -31,7 +30,7 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| async move { r.add_step("step0") - .scope(|s| run_measure_step(s).boxed()) + .scope(run_measure_step) .await?; Ok(tv::TestRunOutcome { diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 61aeb7a..755547a 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -7,12 +7,11 @@ use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{SubcomponentType, TestResult, TestStatus}; async fn run_measure_step( - step: &tv::StartedTestStep, + step: tv::ScopedTestStep, ram0: tv::DutHardwareInfo, ) -> Result { step.add_measurement_detail( @@ -40,13 +39,10 @@ async fn run_measure_step( ); chip1_temp - .scope(|s| { - async move { - s.add_measurement(79.into()).await?; + .scope(|s| async move { + s.add_measurement(79.into()).await?; - Ok(()) - } - .boxed() + Ok(()) }) .await?; @@ -90,7 +86,7 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| async move { r.add_step("step0") - .scope(|s| run_measure_step(s, ram0).boxed()) + .scope(|s| run_measure_step(s, ram0)) .await?; Ok(tv::TestRunOutcome { diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index 20bac17..f8655cd 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -6,12 +6,11 @@ // #![allow(warnings)] use anyhow::Result; -use futures::FutureExt; use ocptv::output as tv; use tv::{TestResult, TestStatus, ValidatorType}; -async fn run_measure_step(step: &tv::StartedTestStep) -> Result { +async fn run_measure_step(step: tv::ScopedTestStep) -> Result { step.add_measurement_detail( tv::Measurement::builder("temp", 40.into()) .add_validator( @@ -31,13 +30,10 @@ async fn run_measure_step(step: &tv::StartedTestStep) -> Result Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0") - .scope(|s| run_measure_step(s).boxed()) - .await?; + r.add_step("step0").scope(run_measure_step).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/simple_step_fail.rs b/examples/simple_step_fail.rs index af6387e..eba2402 100644 --- a/examples/simple_step_fail.rs +++ b/examples/simple_step_fail.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use ocptv::ocptv_log_info; use ocptv::output as tv; @@ -21,17 +20,14 @@ async fn main() -> Result<()> { .build() .scope(dut, |r| async move { r.add_step("step0") - .scope(|s| { - async move { - ocptv_log_info!(s, "info log").await?; - Ok(TestStatus::Complete) - } - .boxed() + .scope(|s| async move { + ocptv_log_info!(s, "info log").await?; + Ok(TestStatus::Complete) }) .await?; r.add_step("step1") - .scope(|_s| async move { Ok(TestStatus::Error) }.boxed()) + .scope(|_s| async move { Ok(TestStatus::Error) }) .await?; Ok(tv::TestRunOutcome { diff --git a/src/output/measure.rs b/src/output/measure.rs index 11620de..f397a01 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -5,11 +5,11 @@ // https://opensource.org/licenses/MIT. use std::collections::BTreeMap; +use std::future::Future; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; -#[cfg(feature = "boxed-scopes")] -use futures::future::BoxFuture; +use delegate::delegate; use crate::output as tv; use crate::output::trait_ext::{MapExt, VecExt}; @@ -117,14 +117,17 @@ impl MeasurementSeries { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - #[cfg(feature = "boxed-scopes")] - pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> where - F: FnOnce(&StartedMeasurementSeries) -> BoxFuture<'_, Result<(), tv::OcptvError>>, + R: Future> + Send + 'static, + F: FnOnce(ScopedMeasurementSeries) -> R + Send + 'static, { - let series = self.start().await?; - func(&series).await?; - series.end().await?; + let series = Arc::new(self.start().await?); + func(ScopedMeasurementSeries { + series: Arc::clone(&series), + }) + .await?; + series.end_impl().await?; Ok(()) } @@ -142,6 +145,22 @@ impl StartedMeasurementSeries { self.seqno.fetch_add(1, Ordering::AcqRel) } + // note: keep the self-consuming method for crate api, but use this one internally, + // since `StartedMeasurementSeries::end` only needs to take ownership for syntactic reasons + async fn end_impl(&self) -> Result<(), tv::OcptvError> { + let end = spec::MeasurementSeriesEnd { + series_id: self.parent.id.clone(), + total_count: self.seqno.load(Ordering::Acquire), + }; + + self.parent + .emitter + .emit(&spec::TestStepArtifactImpl::MeasurementSeriesEnd(end)) + .await?; + + Ok(()) + } + /// Ends the measurement series. /// /// ref: @@ -162,17 +181,7 @@ impl StartedMeasurementSeries { /// # }); /// ``` pub async fn end(self) -> Result<(), tv::OcptvError> { - let end = spec::MeasurementSeriesEnd { - series_id: self.parent.id.clone(), - total_count: self.seqno.load(Ordering::Acquire), - }; - - self.parent - .emitter - .emit(&spec::TestStepArtifactImpl::MeasurementSeriesEnd(end)) - .await?; - - Ok(()) + self.end_impl().await } /// Adds a measurement element to the measurement series. @@ -248,6 +257,23 @@ impl StartedMeasurementSeries { } } +/// TODO: docs +pub struct ScopedMeasurementSeries { + series: Arc, +} + +impl ScopedMeasurementSeries { + delegate! { + to self.series { + pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError>; + pub async fn add_measurement_detail( + &self, + element: MeasurementElementDetail, + ) -> Result<(), tv::OcptvError>; + } + } +} + /// TODO: docs #[derive(Default)] pub struct MeasurementElementDetail { diff --git a/src/output/mod.rs b/src/output/mod.rs index 101ad0b..39668e1 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -39,7 +39,7 @@ pub use measure::{ StartedMeasurementSeries, Validator, ValidatorBuilder, }; pub use run::{ScopedTestRun, StartedTestRun, TestRun, TestRunBuilder, TestRunOutcome}; -pub use step::{StartedTestStep, TestStep}; +pub use step::{ScopedTestStep, StartedTestStep, TestStep}; pub use writer::{BufferWriter, FileWriter, StdoutWriter, Writer}; // re-export these as a public types we present diff --git a/src/output/run.rs b/src/output/run.rs index 0245a20..4bd0f41 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -528,9 +528,11 @@ impl ScopedTestRun { to self.run { pub async fn add_log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError>; pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError>; + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError>; pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError>; pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError>; + pub fn add_step(&self, name: &str) -> TestStep; } } diff --git a/src/output/step.rs b/src/output/step.rs index ec27871..67c322a 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -4,12 +4,12 @@ // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. +use std::future::Future; use std::io; use std::sync::atomic::{self, Ordering}; use std::sync::Arc; -#[cfg(feature = "boxed-scopes")] -use futures::future::BoxFuture; +use delegate::delegate; use crate::output as tv; use crate::spec::{self, TestStepArtifactImpl}; @@ -97,14 +97,17 @@ impl TestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - #[cfg(feature = "boxed-scopes")] - pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> + pub async fn scope(self, func: F) -> Result<(), tv::OcptvError> where - F: FnOnce(&StartedTestStep) -> BoxFuture<'_, Result>, + R: Future> + Send + 'static, + F: FnOnce(ScopedTestStep) -> R + Send + 'static, { - let step = self.start().await?; - let status = func(&step).await?; - step.end(status).await?; + let step = Arc::new(self.start().await?); + let status = func(ScopedTestStep { + step: Arc::clone(&step), + }) + .await?; + step.end_impl(status).await?; Ok(()) } @@ -117,6 +120,15 @@ pub struct StartedTestStep { } impl StartedTestStep { + // note: keep the self-consuming method for crate api, but use this one internally, + // since `StartedTestStep::end` only needs to take ownership for syntactic reasons + async fn end_impl(&self, status: tv::TestStatus) -> Result<(), tv::OcptvError> { + let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); + + self.step.emitter.emit(&end).await?; + Ok(()) + } + /// Ends the test step. /// /// ref: @@ -135,11 +147,8 @@ impl StartedTestStep { /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn end(self, status: spec::TestStatus) -> Result<(), tv::OcptvError> { - let end = TestStepArtifactImpl::TestStepEnd(spec::TestStepEnd { status }); - - self.step.emitter.emit(&end).await?; - Ok(()) + pub async fn end(self, status: tv::TestStatus) -> Result<(), tv::OcptvError> { + self.end_impl(status).await } /// Emits Log message. @@ -367,41 +376,6 @@ impl StartedTestStep { Ok(()) } - /// Emits an extension message; - /// - /// ref: - /// - /// # Examples - /// - /// ```rust - /// # tokio_test::block_on(async { - /// # use ocptv::output::*; - /// let dut = DutInfo::new("my_dut"); - /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; - /// let step = run.add_step("step_name").start().await?; - /// - /// #[derive(serde::Serialize)] - /// struct Ext { i: u32 } - /// - /// step.add_extension("ext_name", Ext { i: 42 }).await?; - /// - /// # Ok::<(), OcptvError>(()) - /// # }); - /// ``` - pub async fn add_extension( - &self, - name: &str, - any: S, - ) -> Result<(), tv::OcptvError> { - let ext = TestStepArtifactImpl::Extension(spec::Extension { - name: name.to_owned(), - content: serde_json::to_value(&any).map_err(|e| OcptvError::Format(Box::new(e)))?, - }); - - self.step.emitter.emit(&ext).await?; - Ok(()) - } - /// Emits a Measurement message. /// /// ref: @@ -684,6 +658,80 @@ impl StartedTestStep { Ok(()) } + + /// Emits an extension message; + /// + /// ref: + /// + /// # Examples + /// + /// ```rust + /// # tokio_test::block_on(async { + /// # use ocptv::output::*; + /// let dut = DutInfo::new("my_dut"); + /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; + /// let step = run.add_step("step_name").start().await?; + /// + /// #[derive(serde::Serialize)] + /// struct Ext { i: u32 } + /// + /// step.add_extension("ext_name", Ext { i: 42 }).await?; + /// + /// # Ok::<(), OcptvError>(()) + /// # }); + /// ``` + pub async fn add_extension( + &self, + name: &str, + any: S, + ) -> Result<(), tv::OcptvError> { + let ext = TestStepArtifactImpl::Extension(spec::Extension { + name: name.to_owned(), + content: serde_json::to_value(&any).map_err(|e| OcptvError::Format(Box::new(e)))?, + }); + + self.step.emitter.emit(&ext).await?; + Ok(()) + } +} + +/// TODO: docs +pub struct ScopedTestStep { + step: Arc, +} + +impl ScopedTestStep { + delegate! { + to self.step { + pub async fn add_log(&self, severity: spec::LogSeverity, msg: &str) -> Result<(), tv::OcptvError>; + pub async fn add_log_detail(&self, log: log::Log) -> Result<(), tv::OcptvError>; + + pub async fn add_error(&self, symptom: &str) -> Result<(), tv::OcptvError>; + pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError>; + pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError>; + + pub async fn add_measurement(&self, name: &str, value: tv::Value) -> Result<(), tv::OcptvError>; + pub async fn add_measurement_detail(&self, detail: measure::Measurement) -> Result<(), tv::OcptvError>; + + pub fn add_measurement_series(&self, name: &str) -> tv::MeasurementSeries; + pub fn add_measurement_series_detail( + &self, + detail: measure::MeasurementSeriesDetail, + ) -> tv::MeasurementSeries; + + pub async fn add_diagnosis( + &self, + verdict: &str, + diagnosis_type: spec::DiagnosisType, + ) -> Result<(), tv::OcptvError>; + pub async fn add_diagnosis_detail(&self, diagnosis: diagnosis::Diagnosis) -> Result<(), tv::OcptvError>; + + pub async fn add_file(&self, name: &str, uri: tv::Uri) -> Result<(), tv::OcptvError>; + pub async fn add_file_detail(&self, file: file::File) -> Result<(), tv::OcptvError>; + + pub async fn add_extension(&self, name: &str, any: S) -> Result<(), tv::OcptvError>; + } + } } pub struct StepEmitter { diff --git a/tests/output/diagnosis.rs b/tests/output/diagnosis.rs index b2e7786..ecd439a 100644 --- a/tests/output/diagnosis.rs +++ b/tests/output/diagnosis.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde_json::json; use ocptv::output::{Diagnosis, DiagnosisType, Subcomponent}; @@ -33,13 +32,10 @@ async fn test_step_with_diagnosis() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_diagnosis("verdict", DiagnosisType::Pass).await?; + check_output_step(&expected, |s, _| async move { + s.add_diagnosis("verdict", DiagnosisType::Pass).await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -81,7 +77,6 @@ async fn test_step_with_diagnosis_builder() -> Result<()> { Ok(()) } - .boxed() }) .await } diff --git a/tests/output/error.rs b/tests/output/error.rs index 2134c26..19e55b4 100644 --- a/tests/output/error.rs +++ b/tests/output/error.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde_json::json; use ocptv::output::Error; @@ -29,9 +28,10 @@ async fn test_testrun_with_error() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |r, _| { - async move { r.add_error("symptom").await }.boxed() - }) + check_output_run( + &expected, + |r, _| async move { r.add_error("symptom").await }, + ) .await } @@ -53,8 +53,8 @@ async fn test_testrun_with_error_with_message() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |r, _| { - async move { r.add_error_msg("symptom", "Error message").await }.boxed() + check_output_run(&expected, |r, _| async move { + r.add_error_msg("symptom", "Error message").await }) .await } @@ -95,7 +95,6 @@ async fn test_testrun_with_error_with_details() -> Result<()> { ) .await } - .boxed() }) .await } @@ -115,14 +114,11 @@ async fn test_testrun_with_error_before_start() -> Result<()> { }), ]; - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error("no-dut").await?; + check_output(&expected, |run_builder, _| async move { + let run = run_builder.build(); + run.add_error("no-dut").await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -143,14 +139,11 @@ async fn test_testrun_with_error_with_message_before_start() -> Result<()> { }), ]; - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_msg("no-dut", "failed to find dut").await?; + check_output(&expected, |run_builder, _| async move { + let run = run_builder.build(); + run.add_error_msg("no-dut", "failed to find dut").await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -175,20 +168,17 @@ async fn test_testrun_with_error_with_details_before_start() -> Result<()> { }), ]; - check_output(&expected, |run_builder, _| { - async move { - let run = run_builder.build(); - run.add_error_detail( - Error::builder("no-dut") - .message("failed to find dut") - .source("file", 1) - .build(), - ) - .await?; + check_output(&expected, |run_builder, _| async move { + let run = run_builder.build(); + run.add_error_detail( + Error::builder("no-dut") + .message("failed to find dut") + .source("file", 1) + .build(), + ) + .await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -213,13 +203,10 @@ async fn test_testrun_step_error() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_error("symptom").await?; + check_output_step(&expected, |s, _| async move { + s.add_error("symptom").await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -245,13 +232,10 @@ async fn test_testrun_step_error_with_message() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_error_msg("symptom", "Error message").await?; + check_output_step(&expected, |s, _| async move { + s.add_error_msg("symptom", "Error message").await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -284,20 +268,17 @@ async fn test_testrun_step_error_with_details() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, dut| { - async move { - s.add_error_detail( - Error::builder("symptom") - .message("Error message") - .source("file", 1) - .add_software_info(dut.software_info("sw0").unwrap()) - .build(), - ) - .await?; + check_output_step(&expected, |s, dut| async move { + s.add_error_detail( + Error::builder("symptom") + .message("Error message") + .source("file", 1) + .add_software_info(dut.software_info("sw0").unwrap()) + .build(), + ) + .await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } diff --git a/tests/output/file.rs b/tests/output/file.rs index d9d9065..bc0ce98 100644 --- a/tests/output/file.rs +++ b/tests/output/file.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde_json::json; use ocptv::output::{File, Uri}; @@ -35,13 +34,10 @@ async fn test_step_with_file() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_file("name", uri).await?; + check_output_step(&expected, |s, _| async move { + s.add_file("name", uri).await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -74,18 +70,15 @@ async fn test_step_with_file_builder() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - let file = File::builder("name", uri) - .content_type(mime::TEXT_PLAIN) - .description("description") - .add_metadata("key", "value".into()) - .build(); - s.add_file_detail(file).await?; + check_output_step(&expected, |s, _| async move { + let file = File::builder("name", uri) + .content_type(mime::TEXT_PLAIN) + .description("description") + .add_metadata("key", "value".into()) + .build(); + s.add_file_detail(file).await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } diff --git a/tests/output/fixture.rs b/tests/output/fixture.rs index 5c038b8..1c25a8c 100644 --- a/tests/output/fixture.rs +++ b/tests/output/fixture.rs @@ -8,16 +8,14 @@ use std::sync::Arc; use anyhow::Result; use assert_json_diff::assert_json_eq; -use futures::future::BoxFuture; use futures::future::Future; -use ocptv::output::TestRunOutcome; use serde_json::json; use tokio::sync::Mutex; use ocptv::output::{ - Config, DutInfo, HardwareInfo, Ident, OcptvError, ScopedTestRun, SoftwareInfo, SoftwareType, - StartedTestStep, TestResult, TestRun, TestRunBuilder, TestStatus, TimestampProvider, - SPEC_VERSION, + Config, DutInfo, HardwareInfo, Ident, OcptvError, ScopedTestRun, ScopedTestStep, SoftwareInfo, + SoftwareType, TestResult, TestRun, TestRunBuilder, TestRunOutcome, TestStatus, + TimestampProvider, SPEC_VERSION, }; pub const DATETIME: chrono::DateTime = @@ -159,34 +157,35 @@ where F: FnOnce(ScopedTestRun, DutInfo) -> R + Send + 'static, { check_output(expected, |run_builder, dut| async move { - let run = run_builder.build(); - - run.scope(dut.clone(), |run| async move { - test_fn(run, dut).await?; - Ok(TestRunOutcome { - status: TestStatus::Complete, - result: TestResult::Pass, + run_builder + .build() + .scope(dut.clone(), |run| async move { + test_fn(run, dut).await?; + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) }) - }) - .await?; + .await?; Ok(()) }) .await } -pub async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> +pub async fn check_output_step(expected: &[serde_json::Value], test_fn: F) -> Result<()> where - F: for<'a> FnOnce(&'a StartedTestStep, DutInfo) -> BoxFuture<'a, Result<(), OcptvError>>, + R: Future> + Send + 'static, + F: FnOnce(ScopedTestStep, DutInfo) -> R + Send + 'static, { - check_output(expected, |run_builder, dut| async move { - let run = run_builder.build().start(dut.clone()).await?; + check_output_run(expected, |run, dut| async move { + run.add_step("first step") + .scope(|step| async move { + test_fn(step, dut).await?; - let step = run.add_step("first step").start().await?; - test_fn(&step, dut).await?; - step.end(TestStatus::Complete).await?; - - run.end(TestStatus::Complete, TestResult::Pass).await?; + Ok(TestStatus::Complete) + }) + .await?; Ok(()) }) diff --git a/tests/output/log.rs b/tests/output/log.rs index 0a1826b..cc8fc2c 100644 --- a/tests/output/log.rs +++ b/tests/output/log.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde_json::json; use ocptv::output::{Log, LogSeverity}; @@ -30,15 +29,12 @@ async fn test_testrun_with_log() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |r, _| { - async move { - r.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await - } - .boxed() + check_output_run(&expected, |r, _| async move { + r.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await }) .await } @@ -65,17 +61,14 @@ async fn test_testrun_with_log_with_details() -> Result<()> { json_run_pass(3), ]; - check_output_run(&expected, |r, _| { - async move { - r.add_log_detail( - Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await - } - .boxed() + check_output_run(&expected, |r, _| async move { + r.add_log_detail( + Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await }) .await } @@ -101,17 +94,14 @@ async fn test_testrun_step_log() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; + check_output_step(&expected, |s, _| async move { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -141,19 +131,16 @@ async fn test_testrun_step_log_with_details() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_log_detail( - Log::builder("This is a log message with INFO severity") - .severity(LogSeverity::Info) - .source("file", 1) - .build(), - ) - .await?; + check_output_step(&expected, |s, _| async move { + s.add_log_detail( + Log::builder("This is a log message with INFO severity") + .severity(LogSeverity::Info) + .source("file", 1) + .build(), + ) + .await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } diff --git a/tests/output/measure.rs b/tests/output/measure.rs index 61b037d..e3da99b 100644 --- a/tests/output/measure.rs +++ b/tests/output/measure.rs @@ -5,7 +5,6 @@ // https://opensource.org/licenses/MIT. use anyhow::Result; -use futures::FutureExt; use serde_json::json; use ocptv::output::{ @@ -36,13 +35,10 @@ async fn test_step_with_measurement() -> Result<()> { json_run_pass(5), ]; - check_output_step(&expected, |s, _| { - async { - s.add_measurement("name", 50.into()).await?; + check_output_step(&expected, |s, _| async move { + s.add_measurement("name", 50.into()).await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -95,7 +91,6 @@ async fn test_step_with_measurement_builder() -> Result<()> { Ok(()) } - .boxed() }) .await } @@ -132,14 +127,11 @@ async fn test_step_with_measurement_series() -> Result<()> { json_run_pass(6), ]; - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; + check_output_step(&expected, |s, _| async move { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -198,17 +190,14 @@ async fn test_step_with_multiple_measurement_series() -> Result<()> { json_run_pass(8), ]; - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.end().await?; + check_output_step(&expected, |s, _| async move { + let series = s.add_measurement_series("name").start().await?; + series.end().await?; - let series_2 = s.add_measurement_series("name").start().await?; - series_2.end().await?; + let series_2 = s.add_measurement_series("name").start().await?; + series_2.end().await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -280,7 +269,6 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { Ok(()) } - .boxed() }) .await } @@ -330,15 +318,12 @@ async fn test_step_with_measurement_series_element() -> Result<()> { json_run_pass(7), ]; - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series.add_measurement(60.into()).await?; - series.end().await?; + check_output_step(&expected, |s, _| async move { + let series = s.add_measurement_series("name").start().await?; + series.add_measurement(60.into()).await?; + series.end().await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } @@ -415,7 +400,7 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { ]; check_output_step(&expected, |s, _| { - async { + async move { let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series.add_measurement(60.into()).await?; @@ -425,7 +410,6 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { Ok(()) } - .boxed() }) .await } @@ -479,11 +463,10 @@ async fn test_step_with_measurement_series_element_with_details() -> Result<()> json_run_pass(7), ]; - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name").start().await?; - series - .add_measurement_detail( + check_output_step(&expected, |s, _| async move { + s.add_measurement_series("name") + .scope(|s| async move { + s.add_measurement_detail( MeasurementElementDetail::builder(60.into()) .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) .add_metadata("key", "value".into()) @@ -491,11 +474,12 @@ async fn test_step_with_measurement_series_element_with_details() -> Result<()> .build(), ) .await?; - series.end().await?; - Ok(()) - } - .boxed() + Ok(()) + }) + .await?; + + Ok(()) }) .await } @@ -575,7 +559,7 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R ]; check_output_step(&expected, |s, _| { - async { + async move { let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly series @@ -603,12 +587,10 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R Ok(()) } - .boxed() }) .await } -#[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_step_with_measurement_series_scope() -> Result<()> { let expected = [ @@ -680,25 +662,19 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { json_run_pass(9), ]; - check_output_step(&expected, |s, _| { - async { - let series = s.add_measurement_series("name"); - series - .scope(|s| { - async move { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; + check_output_step(&expected, |s, _| async move { + let series = s.add_measurement_series("name"); + series + .scope(|s| async move { + s.add_measurement(60.into()).await?; + s.add_measurement(70.into()).await?; + s.add_measurement(80.into()).await?; - Ok(()) - } - .boxed() - }) - .await?; + Ok(()) + }) + .await?; - Ok(()) - } - .boxed() + Ok(()) }) .await } diff --git a/tests/output/run.rs b/tests/output/run.rs index bf0ac79..3ca1677 100644 --- a/tests/output/run.rs +++ b/tests/output/run.rs @@ -26,7 +26,6 @@ async fn test_testrun_start_and_end() -> Result<()> { check_output_run(&expected, |_, _| async { Ok(()) }).await } -#[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_testrun_with_scope() -> Result<()> { use ocptv::output::{LogSeverity, TestResult, TestRunOutcome, TestStatus}; diff --git a/tests/output/step.rs b/tests/output/step.rs index bd9c3a3..bf0b358 100644 --- a/tests/output/step.rs +++ b/tests/output/step.rs @@ -7,11 +7,10 @@ use std::sync::Arc; use anyhow::Result; -use futures::FutureExt; use serde_json::json; use tokio::sync::Mutex; -use ocptv::output::{Config, DutInfo, OcptvError, TestRun}; +use ocptv::output::{Config, DutInfo, OcptvError, TestRun, TestStatus}; use super::fixture::*; @@ -20,15 +19,38 @@ async fn test_testrun_with_step() -> Result<()> { let expected = [ json_schema_version(), json_run_default_start(), - json_step_default_start(), - json_step_complete(3), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepStart": { + "name": "first step" + } + }, + "sequenceNumber": 2, + "timestamp": DATETIME_FORMATTED + }), + json!({ + "testStepArtifact": { + "testStepId": "step0", + "testStepEnd": { + "status": "COMPLETE" + } + }, + "sequenceNumber": 3, + "timestamp": DATETIME_FORMATTED + }), json_run_pass(4), ]; - check_output_step(&expected, |_, _| async { Ok(()) }.boxed()).await + check_output_run(&expected, |r, _| async move { + let step = r.add_step("first step").start().await?; + step.end(TestStatus::Complete).await?; + + Ok(()) + }) + .await } -#[cfg(feature = "boxed-scopes")] #[tokio::test] async fn test_testrun_step_scope_log() -> Result<()> { use ocptv::output::{LogSeverity, TestStatus}; @@ -52,24 +74,18 @@ async fn test_testrun_step_scope_log() -> Result<()> { json_run_pass(5), ]; - check_output_run(&expected, |r, _| { - async move { - r.add_step("first step") - .scope(|s| { - async move { - s.add_log( - LogSeverity::Info, - "This is a log message with INFO severity", - ) - .await?; - - Ok(TestStatus::Complete) - } - .boxed() - }) - .await - } - .boxed() + check_output_run(&expected, |r, _| async move { + r.add_step("first step") + .scope(|s| async move { + s.add_log( + LogSeverity::Info, + "This is a log message with INFO severity", + ) + .await?; + + Ok(TestStatus::Complete) + }) + .await }) .await } @@ -109,21 +125,18 @@ async fn test_step_with_extension() -> Result<()> { number_field: u32, } - check_output_step(&expected, |s, _| { - async { - s.add_extension( - "extension", - Ext { - r#type: "TestExtension".to_owned(), - string_field: "string".to_owned(), - number_field: 42, - }, - ) - .await?; - - Ok(()) - } - .boxed() + check_output_step(&expected, |s, _| async move { + s.add_extension( + "extension", + Ext { + r#type: "TestExtension".to_owned(), + string_field: "string".to_owned(), + number_field: 42, + }, + ) + .await?; + + Ok(()) }) .await } From 60b96330d4fc2474ccf86fc59f5c7c97f64524d4 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 14:39:27 +0100 Subject: [PATCH 90/96] remove the boilerplate needed for tv::Value - this commit adds template args such that the users dont need to manually covert their values into `tv::Value` - applies for measurements, metadata and run start params Signed-off-by: mimir-d --- examples/file.rs | 4 +- examples/measurement_series.rs | 50 ++++++-------- examples/measurement_single.rs | 8 +-- examples/measurement_subcomponent.rs | 4 +- examples/measurement_validators.rs | 12 ++-- src/output/dut.rs | 8 +-- src/output/file.rs | 12 ++-- src/output/log.rs | 2 + src/output/measure.rs | 100 ++++++++++++++------------- src/output/run.rs | 14 ++-- src/output/step.rs | 16 ++--- tests/output/file.rs | 2 +- tests/output/measure.rs | 48 ++++++------- tests/output/run.rs | 8 +-- 14 files changed, 141 insertions(+), 147 deletions(-) diff --git a/examples/file.rs b/examples/file.rs index 4ee475e..9b29ca1 100644 --- a/examples/file.rs +++ b/examples/file.rs @@ -26,9 +26,7 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0") - .scope(run_file_step) - .await?; + r.add_step("step0").scope(run_file_step).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index 7a325b9..c707148 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -21,9 +21,9 @@ async fn step0_measurements(step: tv::ScopedTestStep) -> Result Result Result Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0") - .scope(step0_measurements) - .await?; + r.add_step("step0").scope(step0_measurements).await?; - r.add_step("step1") - .scope(step1_measurements) - .await?; + r.add_step("step1").scope(step1_measurements).await?; - r.add_step("step2") - .scope(step2_measurements) - .await?; + r.add_step("step2").scope(step2_measurements).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/measurement_single.rs b/examples/measurement_single.rs index c39b984..41c2f7e 100644 --- a/examples/measurement_single.rs +++ b/examples/measurement_single.rs @@ -10,9 +10,9 @@ use ocptv::output as tv; use tv::{TestResult, TestStatus}; async fn run_measure_step(step: tv::ScopedTestStep) -> Result { - step.add_measurement("temperature", 42.5.into()).await?; + step.add_measurement("temperature", 42.5).await?; step.add_measurement_detail( - tv::Measurement::builder("fan_speed", 1200.into()) + tv::Measurement::builder("fan_speed", 1200) .unit("rpm") .build(), ) @@ -29,9 +29,7 @@ async fn main() -> Result<()> { tv::TestRun::builder("simple measurement", "1.0") .build() .scope(dut, |r| async move { - r.add_step("step0") - .scope(run_measure_step) - .await?; + r.add_step("step0").scope(run_measure_step).await?; Ok(tv::TestRunOutcome { status: TestStatus::Complete, diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index 755547a..d9a3d87 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -15,7 +15,7 @@ async fn run_measure_step( ram0: tv::DutHardwareInfo, ) -> Result { step.add_measurement_detail( - tv::Measurement::builder("temp0", 100.5.into()) + tv::Measurement::builder("temp0", 100.5) .unit("F") .hardware_info(&ram0) .subcomponent(tv::Subcomponent::builder("chip0").build()) @@ -40,7 +40,7 @@ async fn run_measure_step( chip1_temp .scope(|s| async move { - s.add_measurement(79.into()).await?; + s.add_measurement(79).await?; Ok(()) }) diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index f8655cd..23650f4 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -12,9 +12,9 @@ use tv::{TestResult, TestStatus, ValidatorType}; async fn run_measure_step(step: tv::ScopedTestStep) -> Result { step.add_measurement_detail( - tv::Measurement::builder("temp", 40.into()) + tv::Measurement::builder("temp", 40) .add_validator( - tv::Validator::builder(ValidatorType::GreaterThan, 30.into()) + tv::Validator::builder(ValidatorType::GreaterThan, 30) .name("gt_30") .build(), ) @@ -25,20 +25,18 @@ async fn run_measure_step(step: tv::ScopedTestStep) -> Result Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } @@ -518,8 +518,8 @@ mod tests { fn test_dut_builder() -> Result<()> { let mut dut = DutInfo::builder("1234") .name("dut") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") .add_platform_info(PlatformInfo::builder("platform_info").build()) .build(); diff --git a/src/output/file.rs b/src/output/file.rs index dab09f7..8c72dea 100644 --- a/src/output/file.rs +++ b/src/output/file.rs @@ -35,7 +35,7 @@ use crate::spec; /// .is_snapshot(true) /// .description("description") /// .content_type(mime::TEXT_PLAIN) -/// .add_metadata("key", "value".into()) +/// .add_metadata("key", "value") /// .build(); /// ``` pub struct File { @@ -79,7 +79,7 @@ impl File { /// let file = File::builder("name", uri) /// .description("description") /// .content_type(mime::TEXT_PLAIN) - /// .add_metadata("key", "value".into()) + /// .add_metadata("key", "value") /// .build(); /// ``` pub fn builder(name: &str, uri: tv::Uri) -> FileBuilder { @@ -119,7 +119,7 @@ impl File { /// let builder = File::builder("name", uri) /// .description("description") /// .content_type(mime::TEXT_PLAIN) -/// .add_metadata("key", "value".into()); +/// .add_metadata("key", "value"); /// let file = builder.build(); /// ``` pub struct FileBuilder { @@ -199,10 +199,10 @@ impl FileBuilder { /// /// let uri = Uri::parse("file:///tmp/foo").unwrap(); /// let builder = File::builder("name", uri) - /// .add_metadata("key", "value".into()); + /// .add_metadata("key", "value"); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> FileBuilder { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> FileBuilder { + self.metadata.insert(key.to_string(), value.into()); self } diff --git a/src/output/log.rs b/src/output/log.rs index f39ad90..b5e933a 100644 --- a/src/output/log.rs +++ b/src/output/log.rs @@ -43,10 +43,12 @@ impl LogBuilder { source_location: None, } } + pub fn severity(mut self, value: spec::LogSeverity) -> Self { self.severity = value; self } + pub fn source(mut self, file: &str, line: i32) -> Self { self.source_location = Some(spec::SourceLocation { file: file.to_string(), diff --git a/src/output/measure.rs b/src/output/measure.rs index f397a01..030a323 100644 --- a/src/output/measure.rs +++ b/src/output/measure.rs @@ -107,9 +107,9 @@ impl MeasurementSeries { /// let series = step.add_measurement_series("name"); /// series.scope(|s| { /// async move { - /// s.add_measurement(60.into()).await?; - /// s.add_measurement(70.into()).await?; - /// s.add_measurement(80.into()).await?; + /// s.add_measurement(60).await?; + /// s.add_measurement(70).await?; + /// s.add_measurement(80).await?; /// Ok(()) /// }.boxed() /// }).await?; @@ -198,14 +198,17 @@ impl StartedMeasurementSeries { /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; - /// series.add_measurement(60.into()).await?; + /// series.add_measurement(60).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError> { + pub async fn add_measurement>( + &self, + value: V, + ) -> Result<(), tv::OcptvError> { self.add_measurement_detail(MeasurementElementDetail { - value, + value: value.into(), ..Default::default() }) .await @@ -226,7 +229,7 @@ impl StartedMeasurementSeries { /// let step = run.add_step("step_name").start().await?; /// /// let series = step.add_measurement_series("name").start().await?; - /// let elem = MeasurementElementDetail::builder(60.into()).add_metadata("key", "value".into()).build(); + /// let elem = MeasurementElementDetail::builder(60).add_metadata("key", "value").build(); /// series.add_measurement_detail(elem).await?; /// /// # Ok::<(), OcptvError>(()) @@ -265,7 +268,7 @@ pub struct ScopedMeasurementSeries { impl ScopedMeasurementSeries { delegate! { to self.series { - pub async fn add_measurement(&self, value: tv::Value) -> Result<(), tv::OcptvError>; + pub async fn add_measurement>(&self, value: V) -> Result<(), tv::OcptvError>; pub async fn add_measurement_detail( &self, element: MeasurementElementDetail, @@ -284,8 +287,8 @@ pub struct MeasurementElementDetail { } impl MeasurementElementDetail { - pub fn builder(value: tv::Value) -> MeasurementElementDetailBuilder { - MeasurementElementDetailBuilder::new(value) + pub fn builder>(value: V) -> MeasurementElementDetailBuilder { + MeasurementElementDetailBuilder::new(value.into()) } } @@ -311,8 +314,8 @@ impl MeasurementElementDetailBuilder { self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } @@ -335,8 +338,11 @@ pub struct Validator { } impl Validator { - pub fn builder(validator_type: spec::ValidatorType, value: tv::Value) -> ValidatorBuilder { - ValidatorBuilder::new(validator_type, value) + pub fn builder>( + validator_type: spec::ValidatorType, + value: V, + ) -> ValidatorBuilder { + ValidatorBuilder::new(validator_type, value.into()) } pub fn to_spec(&self) -> spec::Validator { @@ -374,8 +380,8 @@ impl ValidatorBuilder { self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } @@ -398,7 +404,7 @@ impl ValidatorBuilder { /// /// ``` /// # use ocptv::output::*; -/// let measurement = Measurement::new("name", 50.into()); +/// let measurement = Measurement::new("name", 50); /// ``` /// /// ## Create a Measurement object with the `builder` method @@ -408,9 +414,9 @@ impl ValidatorBuilder { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// -/// let measurement = Measurement::builder("name", 50.into()) -/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) +/// let measurement = Measurement::builder("name", 50) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) +/// .add_metadata("key", "value") /// .hardware_info(&hw_info) /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); @@ -436,12 +442,12 @@ impl Measurement { /// /// ``` /// # use ocptv::output::*; - /// let measurement = Measurement::new("name", 50.into()); + /// let measurement = Measurement::new("name", 50); /// ``` - pub fn new(name: &str, value: tv::Value) -> Self { + pub fn new>(name: &str, value: V) -> Self { Measurement { name: name.to_string(), - value, + value: value.into(), ..Default::default() } } @@ -456,15 +462,15 @@ impl Measurement { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let measurement = Measurement::builder("name", 50.into()) - /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) + /// let measurement = Measurement::builder("name", 50) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) + /// .add_metadata("key", "value") /// .hardware_info(&hw_info) /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); /// ``` - pub fn builder(name: &str, value: tv::Value) -> MeasurementBuilder { - MeasurementBuilder::new(name, value) + pub fn builder>(name: &str, value: V) -> MeasurementBuilder { + MeasurementBuilder::new(name, value.into()) } /// Creates an artifact from a Measurement object. @@ -473,7 +479,7 @@ impl Measurement { /// /// ``` /// # use ocptv::output::*; - /// let measurement = Measurement::new("name", 50.into()); + /// let measurement = Measurement::new("name", 50); /// let _ = measurement.to_artifact(); /// ``` pub fn to_artifact(&self) -> spec::Measurement { @@ -504,9 +510,9 @@ impl Measurement { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// -/// let builder = Measurement::builder("name", 50.into()) -/// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) -/// .add_metadata("key", "value".into()) +/// let builder = Measurement::builder("name", 50) +/// .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) +/// .add_metadata("key", "value") /// .hardware_info(&hw_info) /// .subcomponent(Subcomponent::builder("name").build()); /// let measurement = builder.build(); @@ -540,8 +546,8 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = Measurement::builder("name", 50.into()) - /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()); + /// let builder = Measurement::builder("name", 50) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30).build()); /// ``` pub fn add_validator(mut self, validator: Validator) -> Self { self.validators.push(validator.clone()); @@ -557,7 +563,7 @@ impl MeasurementBuilder { /// let mut dut = DutInfo::new("dut0"); /// let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); /// - /// let builder = Measurement::builder("name", 50.into()) + /// let builder = Measurement::builder("name", 50) /// .hardware_info(&hw_info); /// ``` pub fn hardware_info(mut self, hardware_info: &dut::DutHardwareInfo) -> Self { @@ -571,7 +577,7 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = Measurement::builder("name", 50.into()) + /// let builder = Measurement::builder("name", 50) /// .subcomponent(Subcomponent::builder("name").build()); /// ``` pub fn subcomponent(mut self, subcomponent: dut::Subcomponent) -> Self { @@ -586,10 +592,10 @@ impl MeasurementBuilder { /// ``` /// # use ocptv::output::*; /// let builder = - /// Measurement::builder("name", 50.into()).add_metadata("key", "value".into()); + /// Measurement::builder("name", 50).add_metadata("key", "value"); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } @@ -599,7 +605,7 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = Measurement::builder("name", 50000.into()).unit("RPM"); + /// let builder = Measurement::builder("name", 50000).unit("RPM"); /// ``` pub fn unit(mut self, unit: &str) -> MeasurementBuilder { self.unit = Some(unit.to_string()); @@ -612,7 +618,7 @@ impl MeasurementBuilder { /// /// ``` /// # use ocptv::output::*; - /// let builder = Measurement::builder("name", 50.into()); + /// let builder = Measurement::builder("name", 50); /// let measurement = builder.build(); /// ``` pub fn build(self) -> Measurement { @@ -703,8 +709,8 @@ impl MeasurementSeriesDetailBuilder { self } - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } @@ -762,7 +768,7 @@ mod tests { let name = "name".to_owned(); let value = tv::Value::from(50000); let hw_info = dut.add_hardware_info(HardwareInfo::builder("name").build()); - let validator = Validator::builder(spec::ValidatorType::Equal, 30.into()).build(); + let validator = Validator::builder(spec::ValidatorType::Equal, 30).build(); let meta_key = "key"; let meta_value = tv::Value::from("value"); @@ -801,10 +807,10 @@ mod tests { #[test] fn test_validator() -> Result<()> { - let validator = Validator::builder(ValidatorType::Equal, 30.into()) + let validator = Validator::builder(ValidatorType::Equal, 30) .name("validator") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") .build(); let spec_validator = validator.to_spec(); diff --git a/src/output/run.rs b/src/output/run.rs index 4bd0f41..596be44 100644 --- a/src/output/run.rs +++ b/src/output/run.rs @@ -102,7 +102,7 @@ impl TestRun { } /// Builds a scope in the [`TestRun`] object, taking care of starting and - /// ending it. View [`TestRun::start`] and [`TestRun::end`] methods. + /// ending it. View [`TestRun::start`] and [`StartedTestRun::end`] methods. /// After the scope is constructed, additional objects may be added to it. /// This is the preferred usage for the [`TestRun`], since it guarantees /// all the messages are emitted between the start and end messages, the order @@ -218,11 +218,11 @@ impl TestRunBuilder { /// ```rust /// # use ocptv::output::*; /// let run = TestRun::builder("run_name", "1.0") - /// .add_parameter("param1", "value1".into()) + /// .add_parameter("param1", "value1") /// .build(); /// ``` - pub fn add_parameter(mut self, key: &str, value: tv::Value) -> Self { - self.parameters.insert(key.to_string(), value); + pub fn add_parameter>(mut self, key: &str, value: V) -> Self { + self.parameters.insert(key.to_string(), value.into()); self } @@ -265,11 +265,11 @@ impl TestRunBuilder { /// # use ocptv::output::*; /// /// let run = TestRun::builder("run_name", "1.0") - /// .add_metadata("meta1", "value1".into()) + /// .add_metadata("meta1", "value1") /// .build(); /// ``` - pub fn add_metadata(mut self, key: &str, value: tv::Value) -> Self { - self.metadata.insert(key.to_string(), value); + pub fn add_metadata>(mut self, key: &str, value: V) -> Self { + self.metadata.insert(key.to_string(), value.into()); self } diff --git a/src/output/step.rs b/src/output/step.rs index 67c322a..b9d4cba 100644 --- a/src/output/step.rs +++ b/src/output/step.rs @@ -389,16 +389,16 @@ impl StartedTestStep { /// let run = TestRun::new("diagnostic_name", "1.0").start(dut).await?; /// /// let step = run.add_step("step_name").start().await?; - /// step.add_measurement("name", 50.into()).await?; + /// step.add_measurement("name", 50).await?; /// step.end(TestStatus::Complete).await?; /// /// # Ok::<(), OcptvError>(()) /// # }); /// ``` - pub async fn add_measurement( + pub async fn add_measurement>( &self, name: &str, - value: tv::Value, + value: V, ) -> Result<(), tv::OcptvError> { let measurement = measure::Measurement::new(name, value); @@ -427,9 +427,9 @@ impl StartedTestStep { /// let run = TestRun::builder("diagnostic_name", "1.0").build().start(dut).await?; /// let step = run.add_step("step_name").start().await?; /// - /// let measurement = Measurement::builder("name", 5000.into()) - /// .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) - /// .add_metadata("key", "value".into()) + /// let measurement = Measurement::builder("name", 5000) + /// .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) + /// .add_metadata("key", "value") /// .hardware_info(&hw_info) /// .subcomponent(Subcomponent::builder("name").build()) /// .build(); @@ -642,7 +642,7 @@ impl StartedTestStep { /// let file = File::builder("name", uri) /// .description("description") /// .content_type(mime::TEXT_PLAIN) - /// .add_metadata("key", "value".into()) + /// .add_metadata("key", "value") /// .build(); /// step.add_file_detail(file).await?; /// step.end(TestStatus::Complete).await?; @@ -710,7 +710,7 @@ impl ScopedTestStep { pub async fn add_error_msg(&self, symptom: &str, msg: &str) -> Result<(), tv::OcptvError>; pub async fn add_error_detail(&self, error: error::Error) -> Result<(), tv::OcptvError>; - pub async fn add_measurement(&self, name: &str, value: tv::Value) -> Result<(), tv::OcptvError>; + pub async fn add_measurement>(&self, name: &str, value: V) -> Result<(), tv::OcptvError>; pub async fn add_measurement_detail(&self, detail: measure::Measurement) -> Result<(), tv::OcptvError>; pub fn add_measurement_series(&self, name: &str) -> tv::MeasurementSeries; diff --git a/tests/output/file.rs b/tests/output/file.rs index bc0ce98..abab281 100644 --- a/tests/output/file.rs +++ b/tests/output/file.rs @@ -74,7 +74,7 @@ async fn test_step_with_file_builder() -> Result<()> { let file = File::builder("name", uri) .content_type(mime::TEXT_PLAIN) .description("description") - .add_metadata("key", "value".into()) + .add_metadata("key", "value") .build(); s.add_file_detail(file).await?; diff --git a/tests/output/measure.rs b/tests/output/measure.rs index e3da99b..371c16c 100644 --- a/tests/output/measure.rs +++ b/tests/output/measure.rs @@ -36,7 +36,7 @@ async fn test_step_with_measurement() -> Result<()> { ]; check_output_step(&expected, |s, _| async move { - s.add_measurement("name", 50.into()).await?; + s.add_measurement("name", 50).await?; Ok(()) }) @@ -80,10 +80,10 @@ async fn test_step_with_measurement_builder() -> Result<()> { async move { let hw_info = dut.hardware_info("hw0").unwrap(); // must exist - let measurement = Measurement::builder("name", 50.into()) - .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) + let measurement = Measurement::builder("name", 50) + .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") .hardware_info(hw_info) .subcomponent(Subcomponent::builder("name").build()) .build(); @@ -256,9 +256,9 @@ async fn test_step_with_measurement_series_with_details() -> Result<()> { MeasurementSeriesDetail::builder("name") .id(Ident::Exact("series_id".to_owned())) .unit("unit") - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_validator(Validator::builder(ValidatorType::Equal, 30.into()).build()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") + .add_validator(Validator::builder(ValidatorType::Equal, 30).build()) .hardware_info(hw_info) .subcomponent(Subcomponent::builder("name").build()) .build(), @@ -320,7 +320,7 @@ async fn test_step_with_measurement_series_element() -> Result<()> { check_output_step(&expected, |s, _| async move { let series = s.add_measurement_series("name").start().await?; - series.add_measurement(60.into()).await?; + series.add_measurement(60).await?; series.end().await?; Ok(()) @@ -403,9 +403,9 @@ async fn test_step_with_measurement_series_element_index_no() -> Result<()> { async move { let series = s.add_measurement_series("name").start().await?; // add more than one element to check the index increments correctly - series.add_measurement(60.into()).await?; - series.add_measurement(70.into()).await?; - series.add_measurement(80.into()).await?; + series.add_measurement(60).await?; + series.add_measurement(70).await?; + series.add_measurement(80).await?; series.end().await?; Ok(()) @@ -467,10 +467,10 @@ async fn test_step_with_measurement_series_element_with_details() -> Result<()> s.add_measurement_series("name") .scope(|s| async move { s.add_measurement_detail( - MeasurementElementDetail::builder(60.into()) + MeasurementElementDetail::builder(60) .timestamp(DATETIME.with_timezone(&chrono_tz::UTC)) - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") .build(), ) .await?; @@ -564,22 +564,22 @@ async fn test_step_with_measurement_series_element_with_metadata_index_no() -> R // add more than one element to check the index increments correctly series .add_measurement_detail( - MeasurementElementDetail::builder(60.into()) - .add_metadata("key", "value".into()) + MeasurementElementDetail::builder(60) + .add_metadata("key", "value") .build(), ) .await?; series .add_measurement_detail( - MeasurementElementDetail::builder(70.into()) - .add_metadata("key2", "value2".into()) + MeasurementElementDetail::builder(70) + .add_metadata("key2", "value2") .build(), ) .await?; series .add_measurement_detail( - MeasurementElementDetail::builder(80.into()) - .add_metadata("key3", "value3".into()) + MeasurementElementDetail::builder(80) + .add_metadata("key3", "value3") .build(), ) .await?; @@ -666,9 +666,9 @@ async fn test_step_with_measurement_series_scope() -> Result<()> { let series = s.add_measurement_series("name"); series .scope(|s| async move { - s.add_measurement(60.into()).await?; - s.add_measurement(70.into()).await?; - s.add_measurement(80.into()).await?; + s.add_measurement(60).await?; + s.add_measurement(70).await?; + s.add_measurement(80).await?; Ok(()) }) diff --git a/tests/output/run.rs b/tests/output/run.rs index 3ca1677..c7e8af3 100644 --- a/tests/output/run.rs +++ b/tests/output/run.rs @@ -122,7 +122,7 @@ async fn test_testrun_metadata() -> Result<()> { check_output(&expected, |run_builder, dut| async { let run = run_builder - .add_metadata("key", "value".into()) + .add_metadata("key", "value") .build() .start(dut) .await?; @@ -174,9 +174,9 @@ async fn test_testrun_builder() -> Result<()> { check_output(&expected, |run_builder, dut| async { let run = run_builder - .add_metadata("key", "value".into()) - .add_metadata("key2", "value2".into()) - .add_parameter("key", "value".into()) + .add_metadata("key", "value") + .add_metadata("key2", "value2") + .add_parameter("key", "value") .command_line("cmd_line") .build() .start(dut) From 12406cd9be73b6d7074503f83ac7f8f22cd132e9 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 15:09:41 +0100 Subject: [PATCH 91/96] add measurement_concurrency example - this was the last example for now, adding before the spec validation check; required adding a new error variant Signed-off-by: mimir-d --- examples/measurement_concurrency.rs | 62 ++++++++++++++++++++++++++++ examples/measurement_series.rs | 1 - examples/measurement_subcomponent.rs | 1 - examples/measurement_validators.rs | 1 - src/output/mod.rs | 3 ++ 5 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 examples/measurement_concurrency.rs diff --git a/examples/measurement_concurrency.rs b/examples/measurement_concurrency.rs new file mode 100644 index 0000000..d9bdcaa --- /dev/null +++ b/examples/measurement_concurrency.rs @@ -0,0 +1,62 @@ +// (c) Meta Platforms, Inc. and affiliates. +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +use std::{sync::Arc, time::Duration}; + +use anyhow::Result; +use rand; + +use ocptv::output as tv; +use tv::{DutInfo, TestResult, TestRun, TestRunOutcome, TestStatus}; + +/// While the general recommendation is to run test steps sequentially, the specification does not +/// mandate for this to happen. This example shows multiple steps running in parallel, each +/// emitting their own measurements. +#[tokio::main(flavor = "multi_thread", worker_threads = 10)] +async fn main() -> Result<()> { + let dut = DutInfo::builder("dut0").build(); + + TestRun::builder("simple measurement", "1.0") + .build() + .scope(dut, |r| async move { + let run = Arc::new(r); + + let tasks = (0..5) + .map(|i| { + tokio::spawn({ + let r = Arc::clone(&run); + async move { + r.add_step(&format!("step{}", i)) + .scope(move |s| async move { + let offset = rand::random::() % 10000; + tokio::time::sleep(Duration::from_micros(offset)).await; + + let fan_speed = 1000 + 100 * i; + s.add_measurement(&format!("fan{}", i), fan_speed) + .await + .unwrap(); + + Ok(TestStatus::Complete) + }) + .await + } + }) + }) + .collect::>(); + + for t in tasks { + t.await.map_err(|e| tv::OcptvError::Other(Box::new(e)))??; + } + + Ok(TestRunOutcome { + status: TestStatus::Complete, + result: TestResult::Pass, + }) + }) + .await?; + + Ok(()) +} diff --git a/examples/measurement_series.rs b/examples/measurement_series.rs index c707148..cea0e74 100644 --- a/examples/measurement_series.rs +++ b/examples/measurement_series.rs @@ -3,7 +3,6 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -#![allow(warnings)] use anyhow::Result; use chrono::Duration; diff --git a/examples/measurement_subcomponent.rs b/examples/measurement_subcomponent.rs index d9a3d87..0eb01f2 100644 --- a/examples/measurement_subcomponent.rs +++ b/examples/measurement_subcomponent.rs @@ -3,7 +3,6 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -// #![allow(warnings)] use anyhow::Result; diff --git a/examples/measurement_validators.rs b/examples/measurement_validators.rs index 23650f4..09b2c3f 100644 --- a/examples/measurement_validators.rs +++ b/examples/measurement_validators.rs @@ -3,7 +3,6 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file or at // https://opensource.org/licenses/MIT. -// #![allow(warnings)] use anyhow::Result; diff --git a/src/output/mod.rs b/src/output/mod.rs index 39668e1..1a76e1d 100644 --- a/src/output/mod.rs +++ b/src/output/mod.rs @@ -55,4 +55,7 @@ pub enum OcptvError { #[error("failed to format input object")] Format(Box), // opaque type so we don't leak impl + + #[error("other error")] + Other(Box), } From 52e3314538dfb1f1f41c7e106772774bae746953 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 15:41:42 +0100 Subject: [PATCH 92/96] add action to validate examples' outputs Signed-off-by: mimir-d --- .github/workflows/test.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 729a877..f938a08 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -68,3 +68,29 @@ jobs: fail_ci_if_error: true token: ${{ secrets.CODECOV_TOKEN }} env_vars: OS,RUSTVER + + spec: + runs-on: ubuntu-latest + name: examples / spec validation + steps: + - uses: actions/checkout@v4 + - name: Install rust stable + uses: dtolnay/rust-toolchain@stable + - name: pull validator + run: git clone https://github.com/opencomputeproject/ocp-diag-core.git --depth=1 + - name: Install go + uses: actions/setup-go@v2 + with: + go-version: "1.17.6" + - name: run validator against examples + run: | + ROOT="$(pwd)" + cd ocp-diag-core/validators/spec_validator + cargo metadata --manifest-path $ROOT/Cargo.toml --format-version 1 | + jq -r '.["packages"][] | select(.name == "ocptv") | .targets[] | select(.kind[0] == "example") | .name' | + xargs -I{} bash -c " + echo validating output of example {}... && + cargo run --manifest-path $ROOT/Cargo.toml --example {} | + tee /dev/stderr | + go run . -schema ../../json_spec/output/root.json - + " \ No newline at end of file From 639cce790aa509cec5cb29cbc475f7747df907d0 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Sat, 12 Oct 2024 16:21:44 +0100 Subject: [PATCH 93/96] fix spec violations found by the validator - File object had a badly named field - remove an extension example entry; see #22 for details Signed-off-by: mimir-d --- .github/workflows/test.yaml | 2 +- examples/extensions.rs | 10 ++++------ src/spec.rs | 2 +- tests/output/file.rs | 4 ++-- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f938a08..9cf0f88 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -93,4 +93,4 @@ jobs: cargo run --manifest-path $ROOT/Cargo.toml --example {} | tee /dev/stderr | go run . -schema ../../json_spec/output/root.json - - " \ No newline at end of file + " diff --git a/examples/extensions.rs b/examples/extensions.rs index f78886f..4a377fe 100644 --- a/examples/extensions.rs +++ b/examples/extensions.rs @@ -16,7 +16,7 @@ enum ExtensionType { } #[derive(Serialize)] -struct ComplexExtension { +struct Extension { #[serde(rename = "@type")] ext_type: ExtensionType, @@ -25,13 +25,11 @@ struct ComplexExtension { } async fn step0(s: tv::ScopedTestStep) -> Result { - s.add_extension("simple", "extension_identifier").await?; - s.add_extension( - "complex", - ComplexExtension { + "ext0", + Extension { ext_type: ExtensionType::Example, - field: "demo".to_owned(), + field: "example".to_owned(), subtypes: vec![1, 42], }, ) diff --git a/src/spec.rs b/src/spec.rs index a0cb26a..f61e9dc 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -875,7 +875,7 @@ pub struct Diagnosis { #[derive(Debug, Serialize, PartialEq, Clone)] #[serde(rename = "file")] pub struct File { - #[serde(rename = "name")] + #[serde(rename = "displayName")] pub name: String, #[serde(rename = "uri")] diff --git a/tests/output/file.rs b/tests/output/file.rs index abab281..7648374 100644 --- a/tests/output/file.rs +++ b/tests/output/file.rs @@ -22,7 +22,7 @@ async fn test_step_with_file() -> Result<()> { "testStepArtifact": { "testStepId": "step0", "file": { - "name": "name", + "displayName": "name", "uri": uri.clone().as_str().to_owned(), "isSnapshot": false } @@ -53,7 +53,7 @@ async fn test_step_with_file_builder() -> Result<()> { "testStepArtifact": { "testStepId": "step0", "file": { - "name": "name", + "displayName": "name", "uri": uri.clone().as_str().to_owned(), "isSnapshot": false, "contentType": "text/plain", From bd24a98edfae763c87a346d343f6685b9e5f3dbb Mon Sep 17 00:00:00 2001 From: mimir-d Date: Mon, 14 Oct 2024 11:41:36 +0100 Subject: [PATCH 94/96] some minor fixes in the readme Signed-off-by: mimir-d --- README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 077c1ac..ba29a3c 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ fn get_fan_speed() -> i32 { rng.gen_range(1500..1700) } -async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result { +async fn run_diagnosis_step(step: tv::ScopedTestStep) -> Result { let fan_speed = get_fan_speed(); if fan_speed >= 1600 { @@ -73,9 +73,7 @@ async fn run_diagnosis_step(step: &tv::StartedTestStep) -> Result Result { +async fn run_diagnosis_macros_step(step: tv::ScopedTestStep) -> Result { let fan_speed = get_fan_speed(); /// using the macro, the source location is filled automatically @@ -161,7 +159,7 @@ Expected output (slightly reformatted for readability): ### Examples -The examples in [examples folder](https://github.com/opencomputeproject/ocp-diag-core-rust/tree/dev/examples) could be run using cargo. +The examples in [examples folder](https://github.com/opencomputeproject/ocp-diag-core-rust/tree/dev/examples) can be run using cargo. ```bash # run diagnosis example @@ -177,7 +175,3 @@ If you would like to contribute, please head over to [developer notes](https://g Feel free to start a new [discussion](https://github.com/opencomputeproject/ocp-diag-core-rust/discussions), or otherwise post an [issue/request](https://github.com/opencomputeproject/ocp-diag-core-rust/issues). An email contact is also available at: ocp-test-validation@OCP-All.groups.io - - \ No newline at end of file From 34273937021f3a0c201c04671abaef384c3a6353 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Mon, 14 Oct 2024 15:34:56 +0100 Subject: [PATCH 95/96] add publish to crates.io workflow - any commit on main will trigger a publish - add developer release instructions Signed-off-by: mimir-d --- .github/workflows/publish.yaml | 54 ++++++++++++++++++++++++++++++++++ DEVELOPERS.md | 33 ++++++++++++++++++++- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/publish.yaml diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..ee08dd8 --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,54 @@ +name: publish + +on: + push: + branches: [main] + +# only read-only for GITHUB_TOKEN +permissions: + contents: read + +jobs: + publish_audit: + name: audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: rustsec/audit-check@v1.4.1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + publish_test: + name: test on ${{ matrix.os }} / stable + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + - name: Install stable + uses: dtolnay/rust-toolchain@stable + - name: cargo test --locked + run: cargo test --locked --all-features + + publish: + name: publish to crates.io + needs: + - publish_audit + - publish_test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@stable + with: + components: release + - name: cargo login + run: cargo login ${{ secrets.CRATES_IO_TOKEN }} + - name: Publish + run: |- + cargo release \ + publish \ + --all-features \ + --allow-branch HEAD \ + --no-confirm \ + --execute diff --git a/DEVELOPERS.md b/DEVELOPERS.md index 8751335..68a829b 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -8,4 +8,35 @@ assert_eq!(x, 42); Ok(()) } - ``` \ No newline at end of file + ``` + +### Release process + +To make a new release, and publish to crates.io, a new tagged commit needs to exist on the `main` branch. This is done with a simple merge from the `dev` branch. **Do not** push any other kinds of commits to the `main` branch. + +Steps: +1. bump the version. Will need [`cargo-release`](https://crates.io/crates/cargo-release) crate. Example here bumps the *patch* version. +```bash +$ git checkout dev +$ cargo release version patch --execute +$ cargo release changes # note any changelog to add to the commit, or manually craft it +$ git add . +$ git commit +$ git push origin dev +``` +2. merge `dev` into `main` +```bash +$ git checkout main +$ git merge --no-ff dev +``` +3. tag the merge commit +```bash +$ git checkout main +$ cargo release tag --sign-tag --execute +``` +4. push with tags +```bash +$ git checkout main +$ git push +$ git push --tags +``` From 2d6bb746f05833cb3f94e09ab51cf6860fdb3988 Mon Sep 17 00:00:00 2001 From: mimir-d Date: Mon, 14 Oct 2024 15:49:04 +0100 Subject: [PATCH 96/96] bump version 0.1.0 -) 0.1.1 Signed-off-by: mimir-d --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index a68e694..d7749e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -685,7 +685,7 @@ dependencies = [ [[package]] name = "ocptv" -version = "0.1.0" +version = "0.1.1" dependencies = [ "anyhow", "assert-json-diff", diff --git a/Cargo.toml b/Cargo.toml index 35f5686..18f459c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ocptv" -version = "0.1.0" +version = "0.1.1" description = "Strongly typed Rust API for OCPTV output" authors = ["OCP Test & Validation Project"] keywords = ["ocptv", "hardware", "validation"]