diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 97a51d2..06d9bef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -77,3 +77,5 @@ jobs: run: cargo build - name: Run tests run: cargo nextest run + - name: Run tests (without default features) + run: cargo nextest run --no-default-features diff --git a/Cargo.toml b/Cargo.toml index c70cd67..4cc04c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,20 @@ categories = ["encoding", "development-tools"] edition = "2021" rust-version = "1.70" +[features] +default = ["timestamps", "uuids", "strip-ansi"] +timestamps = ["dep:chrono"] +uuids = ["dep:uuid", "dep:newtype-uuid"] +strip-ansi = ["dep:strip-ansi-escapes"] + [dependencies] -chrono = { version = "0.4.41", default-features = false, features = ["std"] } +chrono = { version = "0.4.41", default-features = false, features = ["std"], optional = true } indexmap = "2.7.1" quick-xml = "0.38.1" -newtype-uuid = "1.2.4" +newtype-uuid = { version = "1.2.4", optional = true } thiserror = "2.0.12" -strip-ansi-escapes = "0.2.1" -uuid = "1.17.0" +strip-ansi-escapes = { version = "0.2.1", optional = true } +uuid = { version = "1.17.0", optional = true } [dev-dependencies] goldenfile = "1.7.3" diff --git a/src/report.rs b/src/report.rs index 5137e5b..5f538f9 100644 --- a/src/report.rs +++ b/src/report.rs @@ -2,15 +2,20 @@ // SPDX-License-Identifier: MIT OR Apache-2.0 use crate::{serialize::serialize_report, SerializeError}; +#[cfg(feature = "timestamps")] use chrono::{DateTime, FixedOffset}; use indexmap::map::IndexMap; +#[cfg(feature = "uuids")] use newtype_uuid::{GenericUuid, TypedUuid, TypedUuidKind, TypedUuidTag}; use std::{borrow::Borrow, hash::Hash, io, iter, ops::Deref, time::Duration}; +#[cfg(feature = "uuids")] use uuid::Uuid; /// A tag indicating the kind of report. +#[cfg(feature = "uuids")] pub enum ReportKind {} +#[cfg(feature = "uuids")] impl TypedUuidKind for ReportKind { fn tag() -> TypedUuidTag { const TAG: TypedUuidTag = TypedUuidTag::new("quick-junit-report"); @@ -19,6 +24,7 @@ impl TypedUuidKind for ReportKind { } /// A unique identifier associated with a report. +#[cfg(feature = "uuids")] pub type ReportUuid = TypedUuid; /// The root element of a JUnit report. @@ -30,11 +36,13 @@ pub struct Report { /// A unique identifier associated with this report. /// /// This is an extension to the spec that's used by nextest. + #[cfg(feature = "uuids")] pub uuid: Option, /// The time at which the first test in this report began execution. /// /// This is not part of the JUnit spec, but may be useful for some tools. + #[cfg(feature = "timestamps")] pub timestamp: Option>, /// The overall time taken by the test suite. @@ -60,7 +68,9 @@ impl Report { pub fn new(name: impl Into) -> Self { Self { name: name.into(), + #[cfg(feature = "uuids")] uuid: None, + #[cfg(feature = "timestamps")] timestamp: None, time: None, tests: 0, @@ -73,6 +83,7 @@ impl Report { /// Sets a unique ID for this `Report`. /// /// This is an extension that's used by nextest. + #[cfg(feature = "uuids")] pub fn set_report_uuid(&mut self, uuid: ReportUuid) -> &mut Self { self.uuid = Some(uuid); self @@ -81,12 +92,14 @@ impl Report { /// Sets a unique ID for this `Report` from an untyped [`Uuid`]. /// /// This is an extension that's used by nextest. + #[cfg(feature = "uuids")] pub fn set_uuid(&mut self, uuid: Uuid) -> &mut Self { self.uuid = Some(ReportUuid::from_untyped_uuid(uuid)); self } /// Sets the start timestamp for the report. + #[cfg(feature = "timestamps")] pub fn set_timestamp(&mut self, timestamp: impl Into>) -> &mut Self { self.timestamp = Some(timestamp.into()); self @@ -165,6 +178,7 @@ pub struct TestSuite { pub failures: usize, /// The time at which the TestSuite began execution. + #[cfg(feature = "timestamps")] pub timestamp: Option>, /// The overall time taken by the TestSuite. @@ -192,6 +206,7 @@ impl TestSuite { Self { name: name.into(), time: None, + #[cfg(feature = "timestamps")] timestamp: None, tests: 0, disabled: 0, @@ -206,6 +221,7 @@ impl TestSuite { } /// Sets the start timestamp for the TestSuite. + #[cfg(feature = "timestamps")] pub fn set_timestamp(&mut self, timestamp: impl Into>) -> &mut Self { self.timestamp = Some(timestamp.into()); self @@ -309,6 +325,7 @@ pub struct TestCase { /// The time at which this test case began execution. /// /// This is not part of the JUnit spec, but may be useful for some tools. + #[cfg(feature = "timestamps")] pub timestamp: Option>, /// The time it took to execute this test case. @@ -337,6 +354,7 @@ impl TestCase { name: name.into(), classname: None, assertions: None, + #[cfg(feature = "timestamps")] timestamp: None, time: None, status, @@ -360,6 +378,7 @@ impl TestCase { } /// Sets the start timestamp for the test case. + #[cfg(feature = "timestamps")] pub fn set_timestamp(&mut self, timestamp: impl Into>) -> &mut Self { self.timestamp = Some(timestamp.into()); self @@ -548,6 +567,7 @@ pub struct TestRerun { /// The time at which this rerun began execution. /// /// This is not part of the JUnit spec, but may be useful for some tools. + #[cfg(feature = "timestamps")] pub timestamp: Option>, /// The time it took to execute this rerun. @@ -581,6 +601,7 @@ impl TestRerun { pub fn new(kind: NonSuccessKind) -> Self { TestRerun { kind, + #[cfg(feature = "timestamps")] timestamp: None, time: None, message: None, @@ -593,6 +614,7 @@ impl TestRerun { } /// Sets the start timestamp for this rerun. + #[cfg(feature = "timestamps")] pub fn set_timestamp(&mut self, timestamp: impl Into>) -> &mut Self { self.timestamp = Some(timestamp.into()); self @@ -718,6 +740,7 @@ impl XmlString { /// Creates a new `XmlString`, removing any ANSI escapes and non-printable characters from it. pub fn new(data: impl AsRef) -> Self { let data = data.as_ref(); + #[cfg(feature = "strip-ansi")] let data = strip_ansi_escapes::strip_str(data); let data = data .replace( diff --git a/src/serialize.rs b/src/serialize.rs index 41b02ac..9601643 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -7,6 +7,7 @@ use crate::{ NonSuccessKind, Property, Report, SerializeError, TestCase, TestCaseStatus, TestRerun, TestSuite, XmlString, }; +#[cfg(feature = "timestamps")] use chrono::{DateTime, FixedOffset}; use quick_xml::{ events::{BytesDecl, BytesEnd, BytesStart, BytesText, Event}, @@ -52,7 +53,9 @@ pub(crate) fn serialize_report_impl( // Use the destructuring syntax to ensure that all fields are handled. let Report { name, + #[cfg(feature = "uuids")] uuid, + #[cfg(feature = "timestamps")] timestamp, time, tests, @@ -68,9 +71,11 @@ pub(crate) fn serialize_report_impl( ("failures", failures.to_string().as_str()), ("errors", errors.to_string().as_str()), ]); + #[cfg(feature = "uuids")] if let Some(uuid) = uuid { testsuites_tag.push_attribute(("uuid", uuid.to_string().as_str())); } + #[cfg(feature = "timestamps")] if let Some(timestamp) = timestamp { serialize_timestamp(&mut testsuites_tag, timestamp); } @@ -101,6 +106,7 @@ pub(crate) fn serialize_test_suite( errors, failures, time, + #[cfg(feature = "timestamps")] timestamp, test_cases, properties, @@ -118,6 +124,7 @@ pub(crate) fn serialize_test_suite( ("failures", failures.to_string().as_str()), ]); + #[cfg(feature = "timestamps")] if let Some(timestamp) = timestamp { serialize_timestamp(&mut test_suite_tag, timestamp); } @@ -172,6 +179,7 @@ fn serialize_test_case( name, classname, assertions, + #[cfg(feature = "timestamps")] timestamp, time, status, @@ -190,6 +198,7 @@ fn serialize_test_case( testcase_tag.push_attribute(("assertions", format!("{assertions}").as_str())); } + #[cfg(feature = "timestamps")] if let Some(timestamp) = timestamp { serialize_timestamp(&mut testcase_tag, timestamp); } @@ -306,6 +315,7 @@ fn serialize_rerun( writer: &mut Writer, ) -> quick_xml::Result<()> { let TestRerun { + #[cfg(feature = "timestamps")] timestamp, time, kind, @@ -325,6 +335,7 @@ fn serialize_rerun( }; let mut tag = BytesStart::new(tag_name); + #[cfg(feature = "timestamps")] if let Some(timestamp) = timestamp { serialize_timestamp(&mut tag, timestamp); } @@ -409,6 +420,7 @@ fn serialize_end_tag( writer.write_event(Event::End(end_tag)) } +#[cfg(feature = "timestamps")] fn serialize_timestamp(tag: &mut BytesStart<'_>, timestamp: &DateTime) { // The format string is obtained from https://docs.rs/chrono/0.4.19/chrono/format/strftime/index.html#fn8. // The only change is that this only prints timestamps up to 3 decimal places (to match times). diff --git a/tests/fixture_tests.rs b/tests/fixture_tests.rs index 6e35648..add80b3 100644 --- a/tests/fixture_tests.rs +++ b/tests/fixture_tests.rs @@ -1,6 +1,7 @@ // Copyright (c) The nextest Contributors // SPDX-License-Identifier: MIT OR Apache-2.0 +#[cfg(feature = "timestamps")] use chrono::DateTime; use goldenfile::Mint; use owo_colors::OwoColorize; @@ -13,9 +14,14 @@ use std::time::Duration; fn fixtures() { let mut mint = Mint::new("tests/fixtures"); + #[cfg(all(feature = "timestamps", feature = "uuids", feature = "strip-ansi"))] let f = mint .new_goldenfile("basic_report.xml") .expect("creating new goldenfile succeeds"); + #[cfg(not(any(feature = "timestamps", feature = "uuids", feature = "strip-ansi")))] + let f = mint + .new_goldenfile("basic_report_no_default_features.xml") + .expect("creating new goldenfile succeeds"); let basic_report = basic_report(); basic_report @@ -25,6 +31,7 @@ fn fixtures() { fn basic_report() -> Report { let mut report = Report::new("my-test-run"); + #[cfg(feature = "timestamps")] report.set_timestamp( DateTime::parse_from_rfc2822("Thu, 1 Apr 2021 10:52:37 -0800") .expect("valid RFC2822 datetime"), @@ -32,6 +39,7 @@ fn basic_report() -> Report { report.set_time(Duration::new(42, 234_567_890)); let mut test_suite = TestSuite::new("testsuite0"); + #[cfg(feature = "timestamps")] test_suite.set_timestamp( DateTime::parse_from_rfc2822("Thu, 1 Apr 2021 10:52:39 -0800") .expect("valid RFC2822 datetime"), @@ -74,11 +82,13 @@ fn basic_report() -> Report { .set_message("skipped message"); // no description to test that. let mut test_case = TestCase::new("testcase3", test_case_status); + #[cfg(feature = "timestamps")] test_case .set_timestamp( DateTime::parse_from_rfc2822("Thu, 1 Apr 2021 11:52:41 -0700") .expect("valid RFC2822 datetime"), - ) + ); + test_case .set_assertions(20) .set_system_out("testcase3 output") .set_system_err("testcase3 error"); @@ -138,6 +148,8 @@ fn basic_report() -> Report { test_suite.add_property(Property::new("env", "FOOBAR")); report.add_test_suite(test_suite); + #[cfg(feature = "uuids")] + report.set_uuid(uuid::Uuid::parse_str("0500990f-0df3-4722-bbeb-90a75b8aa6bd").expect("uuid parsing succeeds")); report } diff --git a/tests/fixtures/basic_report.xml b/tests/fixtures/basic_report.xml index 4f8c6b5..24bc576 100644 --- a/tests/fixtures/basic_report.xml +++ b/tests/fixtures/basic_report.xml @@ -1,5 +1,5 @@ - + diff --git a/tests/fixtures/basic_report_no_default_features.xml b/tests/fixtures/basic_report_no_default_features.xml new file mode 100644 index 0000000..6b5ed66 --- /dev/null +++ b/tests/fixtures/basic_report_no_default_features.xml @@ -0,0 +1,45 @@ + + + + + + + + testcase0-output + + + this is the failure description + some sort of failure output + + + testcase2 error description + + + + testcase3 output + testcase3 error + + + this is a flaky failure description + flaky error description + flaky stack trace + flaky system output + flaky system error with [34mANSI escape codes[39m + + + + main test failure description + + + + retry error stack trace + retry error system output + + + + + + + + +