From b2fe203aa5e33bbe0a24fe7826b0dba18a9c108f Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 6 Aug 2025 11:26:28 +0200 Subject: [PATCH 1/4] make `uuid` an optional dependency --- Cargo.toml | 8 ++++++-- src/report.rs | 9 +++++++++ src/serialize.rs | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index c70cd67..22b333d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,14 +11,18 @@ categories = ["encoding", "development-tools"] edition = "2021" rust-version = "1.70" +[features] +default = ["uuids"] +uuids = ["dep:uuid", "dep:newtype-uuid"] + [dependencies] chrono = { version = "0.4.41", default-features = false, features = ["std"] } 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" +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..78bec2f 100644 --- a/src/report.rs +++ b/src/report.rs @@ -4,13 +4,17 @@ use crate::{serialize::serialize_report, SerializeError}; 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 +23,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,6 +35,7 @@ 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. @@ -60,6 +66,7 @@ impl Report { pub fn new(name: impl Into) -> Self { Self { name: name.into(), + #[cfg(feature = "uuids")] uuid: None, timestamp: None, time: None, @@ -73,6 +80,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,6 +89,7 @@ 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 diff --git a/src/serialize.rs b/src/serialize.rs index 41b02ac..6be1b37 100644 --- a/src/serialize.rs +++ b/src/serialize.rs @@ -52,6 +52,7 @@ pub(crate) fn serialize_report_impl( // Use the destructuring syntax to ensure that all fields are handled. let Report { name, + #[cfg(feature = "uuids")] uuid, timestamp, time, @@ -68,6 +69,7 @@ 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())); } From 1c8b0772c51ce88c9b594a550fc92b49068f4e60 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 6 Aug 2025 11:27:27 +0200 Subject: [PATCH 2/4] make `chrono` an optional dependency --- Cargo.toml | 5 +++-- src/report.rs | 13 +++++++++++++ src/serialize.rs | 10 ++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22b333d..8393a32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,12 @@ edition = "2021" rust-version = "1.70" [features] -default = ["uuids"] +default = ["timestamps", "uuids"] +timestamps = ["dep:chrono"] uuids = ["dep:uuid", "dep:newtype-uuid"] [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 = { version = "1.2.4", optional = true } diff --git a/src/report.rs b/src/report.rs index 78bec2f..88984e6 100644 --- a/src/report.rs +++ b/src/report.rs @@ -2,6 +2,7 @@ // 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")] @@ -41,6 +42,7 @@ pub struct Report { /// 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. @@ -68,6 +70,7 @@ impl Report { name: name.into(), #[cfg(feature = "uuids")] uuid: None, + #[cfg(feature = "timestamps")] timestamp: None, time: None, tests: 0, @@ -96,6 +99,7 @@ impl Report { } /// 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 @@ -174,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. @@ -201,6 +206,7 @@ impl TestSuite { Self { name: name.into(), time: None, + #[cfg(feature = "timestamps")] timestamp: None, tests: 0, disabled: 0, @@ -215,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 @@ -318,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. @@ -346,6 +354,7 @@ impl TestCase { name: name.into(), classname: None, assertions: None, + #[cfg(feature = "timestamps")] timestamp: None, time: None, status, @@ -369,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 @@ -557,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. @@ -590,6 +601,7 @@ impl TestRerun { pub fn new(kind: NonSuccessKind) -> Self { TestRerun { kind, + #[cfg(feature = "timestamps")] timestamp: None, time: None, message: None, @@ -602,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 diff --git a/src/serialize.rs b/src/serialize.rs index 6be1b37..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}, @@ -54,6 +55,7 @@ pub(crate) fn serialize_report_impl( name, #[cfg(feature = "uuids")] uuid, + #[cfg(feature = "timestamps")] timestamp, time, tests, @@ -73,6 +75,7 @@ pub(crate) fn serialize_report_impl( 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); } @@ -103,6 +106,7 @@ pub(crate) fn serialize_test_suite( errors, failures, time, + #[cfg(feature = "timestamps")] timestamp, test_cases, properties, @@ -120,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); } @@ -174,6 +179,7 @@ fn serialize_test_case( name, classname, assertions, + #[cfg(feature = "timestamps")] timestamp, time, status, @@ -192,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); } @@ -308,6 +315,7 @@ fn serialize_rerun( writer: &mut Writer, ) -> quick_xml::Result<()> { let TestRerun { + #[cfg(feature = "timestamps")] timestamp, time, kind, @@ -327,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); } @@ -411,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). From a1baa6922fd903d0e6b10163c2e2dc8135f16b15 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 6 Aug 2025 11:27:45 +0200 Subject: [PATCH 3/4] make `strip-ansi-escapes` an optional dependency --- Cargo.toml | 5 +++-- src/report.rs | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8393a32..4cc04c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,10 @@ edition = "2021" rust-version = "1.70" [features] -default = ["timestamps", "uuids"] +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"], optional = true } @@ -22,7 +23,7 @@ indexmap = "2.7.1" quick-xml = "0.38.1" newtype-uuid = { version = "1.2.4", optional = true } thiserror = "2.0.12" -strip-ansi-escapes = "0.2.1" +strip-ansi-escapes = { version = "0.2.1", optional = true } uuid = { version = "1.17.0", optional = true } [dev-dependencies] diff --git a/src/report.rs b/src/report.rs index 88984e6..5f538f9 100644 --- a/src/report.rs +++ b/src/report.rs @@ -740,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( From 733a0491766d30f000679f33bf703f237d4505ed Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Wed, 6 Aug 2025 11:42:49 +0200 Subject: [PATCH 4/4] add tests for no-default-features --- .github/workflows/ci.yml | 2 + tests/fixture_tests.rs | 14 +++++- tests/fixtures/basic_report.xml | 2 +- .../basic_report_no_default_features.xml | 45 +++++++++++++++++++ 4 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 tests/fixtures/basic_report_no_default_features.xml 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/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 + + + + + + + + +