diff --git a/Cargo.lock b/Cargo.lock index 94c0297..bb307ce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,9 +18,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.0" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "anstyle-parse" @@ -68,6 +68,18 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "escargot" version = "0.5.14" @@ -80,6 +92,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "hashbrown" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + +[[package]] +name = "indexmap" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "itoa" version = "1.0.6" @@ -105,6 +133,16 @@ dependencies = [ name = "lexarg-parser" version = "0.1.0" +[[package]] +name = "libtest-json" +version = "0.1.0" +dependencies = [ + "schemars", + "serde", + "serde_json", + "snapbox", +] + [[package]] name = "libtest-lexarg" version = "0.1.0" @@ -133,6 +171,7 @@ dependencies = [ "anstyle", "lexarg-error", "lexarg-parser", + "libtest-json", "libtest-lexarg", "serde", "serde_json", @@ -159,6 +198,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + [[package]] name = "normalize-line-endings" version = "0.3.0" @@ -185,42 +230,105 @@ checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "ryu" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +[[package]] +name = "schemars" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d20c4491bc164fa2f6c5d44565947a52ad80b9505d8e36f8d54c27c739fcd0" +dependencies = [ + "dyn-clone", + "ref-cast", + "schemars_derive", + "semver", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d020396d1d138dc19f1165df7545479dcd58d93810dc5d646a16e55abefa80" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn", +] + +[[package]] +name = "semver" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" + [[package]] name = "serde" -version = "1.0.160" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", @@ -229,11 +337,13 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ + "indexmap", "itoa", + "memchr", "ryu", "serde", ] @@ -268,9 +378,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", diff --git a/crates/libtest-json/CHANGELOG.md b/crates/libtest-json/CHANGELOG.md new file mode 100644 index 0000000..e1786b9 --- /dev/null +++ b/crates/libtest-json/CHANGELOG.md @@ -0,0 +1,11 @@ +# Change Log +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](http://keepachangelog.com/) +and this project adheres to [Semantic Versioning](http://semver.org/). + + +## [Unreleased] - ReleaseDate + + +[Unreleased]: https://github.com/rust-cli/argfile/compare/c96ef27899b410f9f154183989d4ccf60af27da6...HEAD diff --git a/crates/libtest-json/Cargo.toml b/crates/libtest-json/Cargo.toml new file mode 100644 index 0000000..f11b9db --- /dev/null +++ b/crates/libtest-json/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "libtest-json" +version = "0.1.0" +description = "Definition of the json output for libtest" +categories = ["development-tools::testing"] +keywords = ["libtest"] +repository.workspace = true +license.workspace = true +edition.workspace = true +rust-version.workspace = true +include.workspace = true + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs", "--generate-link-to-definition"] + +[package.metadata.release] +pre-release-replacements = [ + {file="CHANGELOG.md", search="Unreleased", replace="{{version}}", min=1}, + {file="CHANGELOG.md", search="\\.\\.\\.HEAD", replace="...{{tag_name}}", exactly=1}, + {file="CHANGELOG.md", search="ReleaseDate", replace="{{date}}", min=1}, + {file="CHANGELOG.md", search="", replace="\n## [Unreleased] - ReleaseDate\n", exactly=1}, + {file="CHANGELOG.md", search="", replace="\n[Unreleased]: https://github.com/epage/pytest-rs/compare/{{tag_name}}...HEAD", exactly=1}, +] + +[features] +default = [] +serde = ["dep:serde"] +unstable-schema = ["serde", "dep:schemars", "dep:serde_json"] + +[dependencies] +serde = { version = "1.0.160", features = ["derive"], optional = true } +serde_json = { version = "1.0.96", optional = true } +schemars = { version = "1.0.0-alpha.17", features = ["preserve_order", "semver1"], optional = true } + +[dev-dependencies] +snapbox = "0.6.21" + +[lints] +workspace = true diff --git a/crates/libtest-json/LICENSE-APACHE b/crates/libtest-json/LICENSE-APACHE new file mode 100644 index 0000000..8f71f43 --- /dev/null +++ b/crates/libtest-json/LICENSE-APACHE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + diff --git a/crates/libtest-json/LICENSE-MIT b/crates/libtest-json/LICENSE-MIT new file mode 100644 index 0000000..a2d0108 --- /dev/null +++ b/crates/libtest-json/LICENSE-MIT @@ -0,0 +1,19 @@ +Copyright (c) Individual contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/crates/libtest-json/README.md b/crates/libtest-json/README.md new file mode 100644 index 0000000..0a29385 --- /dev/null +++ b/crates/libtest-json/README.md @@ -0,0 +1,26 @@ +# libtest2-harness + +> An experimental replacement for the core of libtest + +[![Documentation](https://img.shields.io/badge/docs-master-blue.svg)][Documentation] +![License](https://img.shields.io/crates/l/libtest2-harness.svg) +[![Crates Status](https://img.shields.io/crates/v/libtest2-harness.svg)](https://crates.io/crates/libtest2-harness) + +## License + +Licensed under either of + +* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or ) +* MIT license ([LICENSE-MIT](LICENSE-MIT) or ) + +at your option. + +### Contribution + +Unless you explicitly state otherwise, any contribution intentionally +submitted for inclusion in the work by you, as defined in the Apache-2.0 +license, shall be dual-licensed as above, without any additional terms or +conditions. + +[Crates.io]: https://crates.io/crates/libtest2-harness +[Documentation]: https://docs.rs/libtest2-harness diff --git a/crates/libtest-json/event.schema.json b/crates/libtest-json/event.schema.json new file mode 100644 index 0000000..bd3820c --- /dev/null +++ b/crates/libtest-json/event.schema.json @@ -0,0 +1,194 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Event", + "oneOf": [ + { + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "discover-start" + } + }, + "required": [ + "event" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "mode": { + "$ref": "#/$defs/RunMode" + }, + "run": { + "type": "boolean" + }, + "event": { + "type": "string", + "const": "discover-case" + } + }, + "required": [ + "event", + "name", + "mode", + "run" + ] + }, + { + "type": "object", + "properties": { + "elapsed_s": { + "$ref": "#/$defs/Elapsed" + }, + "seed": { + "type": [ + "integer", + "null" + ], + "format": "uint64", + "minimum": 0 + }, + "event": { + "type": "string", + "const": "discover-complete" + } + }, + "required": [ + "event", + "elapsed_s" + ] + }, + { + "type": "object", + "properties": { + "event": { + "type": "string", + "const": "suite-start" + } + }, + "required": [ + "event" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "event": { + "type": "string", + "const": "case-start" + } + }, + "required": [ + "event", + "name" + ] + }, + { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "mode": { + "$ref": "#/$defs/RunMode" + }, + "status": { + "anyOf": [ + { + "$ref": "#/$defs/RunStatus" + }, + { + "type": "null" + } + ] + }, + "message": { + "type": [ + "string", + "null" + ] + }, + "elapsed_s": { + "anyOf": [ + { + "$ref": "#/$defs/Elapsed" + }, + { + "type": "null" + } + ] + }, + "event": { + "type": "string", + "const": "case-complete" + } + }, + "required": [ + "event", + "name", + "mode" + ] + }, + { + "type": "object", + "properties": { + "elapsed_s": { + "$ref": "#/$defs/Elapsed" + }, + "event": { + "type": "string", + "const": "suite-complete" + } + }, + "required": [ + "event", + "elapsed_s" + ] + } + ], + "$defs": { + "RunMode": { + "type": "string", + "enum": [ + "test", + "bench" + ] + }, + "Elapsed": { + "$ref": "#/$defs/Duration" + }, + "Duration": { + "type": "object", + "properties": { + "secs": { + "type": "integer", + "format": "uint64", + "minimum": 0 + }, + "nanos": { + "type": "integer", + "format": "uint32", + "minimum": 0 + } + }, + "required": [ + "secs", + "nanos" + ] + }, + "RunStatus": { + "type": "string", + "enum": [ + "ignored", + "failed" + ] + } + } +} \ No newline at end of file diff --git a/crates/libtest-json/src/event.rs b/crates/libtest-json/src/event.rs new file mode 100644 index 0000000..251ee14 --- /dev/null +++ b/crates/libtest-json/src/event.rs @@ -0,0 +1,88 @@ +#[derive(Clone, Debug)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +#[cfg_attr(feature = "serde", serde(tag = "event"))] +pub enum Event { + DiscoverStart, + DiscoverCase { + name: String, + mode: RunMode, + run: bool, + }, + DiscoverComplete { + #[allow(dead_code)] + elapsed_s: Elapsed, + seed: Option, + }, + SuiteStart, + CaseStart { + name: String, + }, + CaseComplete { + name: String, + #[allow(dead_code)] + mode: RunMode, + status: Option, + message: Option, + #[allow(dead_code)] + elapsed_s: Option, + }, + SuiteComplete { + elapsed_s: Elapsed, + }, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum RunMode { + #[default] + Test, + Bench, +} + +impl RunMode { + pub fn as_str(&self) -> &str { + match self { + Self::Test => "test", + Self::Bench => "bench", + } + } +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] +pub enum RunStatus { + Ignored, + Failed, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] +#[cfg_attr(feature = "unstable-schema", derive(schemars::JsonSchema))] +#[cfg_attr(feature = "serde", derive(serde::Serialize))] +#[cfg_attr(feature = "serde", serde(into = "String"))] +pub struct Elapsed(pub std::time::Duration); + +impl std::fmt::Display for Elapsed { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:.3}s", self.0.as_secs_f64()) + } +} + +impl From for String { + fn from(elapsed: Elapsed) -> Self { + elapsed.0.as_secs_f64().to_string() + } +} + +#[cfg(feature = "unstable-schema")] +#[test] +fn dump_event_schema() { + let schema = schemars::schema_for!(Event); + let dump = serde_json::to_string_pretty(&schema).unwrap(); + snapbox::assert_data_eq!(dump, snapbox::file!("../event.schema.json").raw()); +} diff --git a/crates/libtest-json/src/lib.rs b/crates/libtest-json/src/lib.rs new file mode 100644 index 0000000..5de41d3 --- /dev/null +++ b/crates/libtest-json/src/lib.rs @@ -0,0 +1,17 @@ +//! Definition of the json output for libtest + +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +#![warn(clippy::print_stderr)] +#![warn(clippy::print_stdout)] +#![allow(clippy::todo)] + +mod event; + +pub use event::Elapsed; +pub use event::Event; +pub use event::RunMode; +pub use event::RunStatus; + +#[doc = include_str!("../README.md")] +#[cfg(doctest)] +pub struct ReadmeDoctests; diff --git a/crates/libtest2-harness/Cargo.toml b/crates/libtest2-harness/Cargo.toml index 6f0b74d..deee49c 100644 --- a/crates/libtest2-harness/Cargo.toml +++ b/crates/libtest2-harness/Cargo.toml @@ -25,18 +25,20 @@ pre-release-replacements = [ [features] default = [] -json = ["dep:serde", "dep:serde_json"] +color = ["dep:anstream", "dep:anstyle"] +json = ["libtest-json/serde", "dep:serde", "dep:serde_json"] junit = [] threads = [] [dependencies] -anstream = "0.6.4" -anstyle = "1.0.0" lexarg-parser = { version = "0.1.0", path = "../lexarg-parser" } lexarg-error = { version = "0.1.0", path = "../lexarg-error" } libtest-lexarg = { version = "0.1.0", path = "../libtest-lexarg" } +anstream = { version = "0.6.4", optional = true } +anstyle = { version = "1.0.10", optional = true } serde = { version = "1.0.160", features = ["derive"], optional = true } serde_json = { version = "1.0.96", optional = true } +libtest-json = { version = "0.1.0", path = "../libtest-json" } [dev-dependencies] diff --git a/crates/libtest2-harness/src/harness.rs b/crates/libtest2-harness/src/harness.rs index cfe00eb..352ed7d 100644 --- a/crates/libtest2-harness/src/harness.rs +++ b/crates/libtest2-harness/src/harness.rs @@ -37,6 +37,7 @@ impl Harness { std::process::exit(1) }); + #[cfg(feature = "color")] match opts.color { libtest_lexarg::ColorConfig::AutoColor => anstream::ColorChoice::Auto, libtest_lexarg::ColorConfig::AlwaysColor => anstream::ColorChoice::Always, @@ -131,7 +132,10 @@ fn parse<'p>( } fn notifier(opts: &libtest_lexarg::TestOpts) -> std::io::Result> { + #[cfg(feature = "color")] let stdout = anstream::stdout(); + #[cfg(not(feature = "color"))] + let stdout = std::io::stdout(); let notifier: Box = match opts.format { #[cfg(feature = "json")] OutputFormat::Json => Box::new(notify::JsonNotifier::new(stdout)), diff --git a/crates/libtest2-harness/src/notify/mod.rs b/crates/libtest2-harness/src/notify/mod.rs index 036b5c2..b8d39c7 100644 --- a/crates/libtest2-harness/src/notify/mod.rs +++ b/crates/libtest2-harness/src/notify/mod.rs @@ -2,7 +2,11 @@ mod json; #[cfg(feature = "junit")] mod junit; +#[cfg(not(feature = "color"))] +mod no_style; mod pretty; +#[cfg(feature = "color")] +mod style; mod summary; mod terse; @@ -10,7 +14,11 @@ mod terse; pub(crate) use json::*; #[cfg(feature = "junit")] pub(crate) use junit::*; +#[cfg(not(feature = "color"))] +pub(crate) use no_style::*; pub(crate) use pretty::*; +#[cfg(feature = "color")] +pub(crate) use style::*; pub(crate) use summary::*; pub(crate) use terse::*; @@ -20,86 +28,8 @@ pub(crate) trait Notifier { fn notify(&mut self, event: Event) -> std::io::Result<()>; } -#[derive(Clone, Debug)] -#[cfg_attr(feature = "json", derive(serde::Serialize))] -#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] -#[cfg_attr(feature = "json", serde(tag = "event"))] -pub(crate) enum Event { - DiscoverStart, - DiscoverCase { - name: String, - mode: RunMode, - run: bool, - }, - DiscoverComplete { - #[allow(dead_code)] - elapsed_s: Elapsed, - seed: Option, - }, - SuiteStart, - CaseStart { - name: String, - }, - CaseComplete { - name: String, - #[allow(dead_code)] - mode: RunMode, - status: Option, - message: Option, - #[allow(dead_code)] - elapsed_s: Option, - }, - SuiteComplete { - elapsed_s: Elapsed, - }, -} - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "json", derive(serde::Serialize))] -#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] -pub enum RunMode { - #[default] - Test, - Bench, -} - -impl RunMode { - pub(crate) fn as_str(&self) -> &str { - match self { - Self::Test => "test", - Self::Bench => "bench", - } - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "json", derive(serde::Serialize))] -#[cfg_attr(feature = "json", serde(rename_all = "kebab-case"))] -pub(crate) enum RunStatus { - Ignored, - Failed, -} - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)] -#[cfg_attr(feature = "json", derive(serde::Serialize))] -#[cfg_attr(feature = "json", serde(into = "String"))] -pub(crate) struct Elapsed(pub std::time::Duration); - -impl std::fmt::Display for Elapsed { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:.3}s", self.0.as_secs_f64()) - } -} - -impl From for String { - fn from(elapsed: Elapsed) -> Self { - elapsed.0.as_secs_f64().to_string() - } -} +pub(crate) use libtest_json::Elapsed; +pub(crate) use libtest_json::Event; +pub(crate) use libtest_json::RunStatus; -const FAILED: anstyle::Style = - anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))); -const OK: anstyle::Style = - anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))); -const IGNORED: anstyle::Style = - anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))); +pub use libtest_json::RunMode; diff --git a/crates/libtest2-harness/src/notify/no_style.rs b/crates/libtest2-harness/src/notify/no_style.rs new file mode 100644 index 0000000..472cbf1 --- /dev/null +++ b/crates/libtest2-harness/src/notify/no_style.rs @@ -0,0 +1,11 @@ +pub(crate) struct Style; + +impl std::fmt::Display for Style { + fn fmt(&self, _formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Ok(()) + } +} + +pub(crate) const FAILED: Style = Style; +pub(crate) const OK: Style = Style; +pub(crate) const IGNORED: Style = Style; diff --git a/crates/libtest2-harness/src/notify/pretty.rs b/crates/libtest2-harness/src/notify/pretty.rs index 8af209b..1c827fc 100644 --- a/crates/libtest2-harness/src/notify/pretty.rs +++ b/crates/libtest2-harness/src/notify/pretty.rs @@ -57,7 +57,7 @@ impl super::Notifier for PrettyRunNotifier { if self.is_multithreaded { write!(self.writer, "test {: <1$} ... ", name, self.name_width)?; } - writeln!(self.writer, "{}{s}{}", style.render(), style.render_reset())?; + writeln!(self.writer, "{style}{s}{style:#}")?; } Event::SuiteComplete { .. } => { self.summary.write_complete(&mut self.writer)?; diff --git a/crates/libtest2-harness/src/notify/style.rs b/crates/libtest2-harness/src/notify/style.rs new file mode 100644 index 0000000..3a9a72a --- /dev/null +++ b/crates/libtest2-harness/src/notify/style.rs @@ -0,0 +1,6 @@ +pub(crate) const FAILED: anstyle::Style = + anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red))); +pub(crate) const OK: anstyle::Style = + anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green))); +pub(crate) const IGNORED: anstyle::Style = + anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Yellow))); diff --git a/crates/libtest2-harness/src/notify/summary.rs b/crates/libtest2-harness/src/notify/summary.rs index 0de6cc8..caa65ef 100644 --- a/crates/libtest2-harness/src/notify/summary.rs +++ b/crates/libtest2-harness/src/notify/summary.rs @@ -75,10 +75,8 @@ impl Summary { writeln!(writer)?; writeln!( writer, - "test result: {}{summary}{}. {num_passed} passed; {num_failed} failed; {num_ignored} ignored; \ + "test result: {summary_style}{summary}{summary_style:#}. {num_passed} passed; {num_failed} failed; {num_ignored} ignored; \ {num_filtered_out} filtered out; finished in {elapsed_s}", - summary_style.render(), - summary_style.render_reset() )?; writeln!(writer)?; diff --git a/crates/libtest2-harness/src/notify/terse.rs b/crates/libtest2-harness/src/notify/terse.rs index 679300e..e5388df 100644 --- a/crates/libtest2-harness/src/notify/terse.rs +++ b/crates/libtest2-harness/src/notify/terse.rs @@ -73,7 +73,7 @@ impl super::Notifier for TerseRunNotifier { Some(RunStatus::Failed) => ('F', FAILED), None => ('.', OK), }; - write!(self.writer, "{}{c}{}", style.render(), style.render_reset())?; + write!(self.writer, "{style}{c}{style:#}")?; self.writer.flush()?; } Event::SuiteComplete { .. } => { diff --git a/crates/libtest2-mimic/Cargo.toml b/crates/libtest2-mimic/Cargo.toml index 787b951..eefac8d 100644 --- a/crates/libtest2-mimic/Cargo.toml +++ b/crates/libtest2-mimic/Cargo.toml @@ -24,7 +24,8 @@ pre-release-replacements = [ ] [features] -default = ["json", "junit", "threads"] +default = ["color", "json", "junit", "threads"] +color = ["libtest2-harness/color"] json = ["libtest2-harness/json"] junit = ["libtest2-harness/junit"] threads = ["libtest2-harness/threads"] diff --git a/crates/libtest2/Cargo.toml b/crates/libtest2/Cargo.toml index 6a66c87..23402e5 100644 --- a/crates/libtest2/Cargo.toml +++ b/crates/libtest2/Cargo.toml @@ -24,7 +24,8 @@ pre-release-replacements = [ ] [features] -default = ["json", "junit", "threads"] +default = ["color", "json", "junit", "threads"] +color = ["libtest2-harness/color"] json = ["libtest2-harness/json"] junit = ["libtest2-harness/junit"] threads = ["libtest2-harness/threads"]