Add implementation of Arbitrary<absl::Duration>() domain.
Write tests to check if all types of values are generated and if the domain shrinks correctly.
Add DurationPrinter and a test to properly print out the source code mode and the human readable mode of a Duration.
PiperOrigin-RevId: 492351452
diff --git a/domain_tests/BUILD b/domain_tests/BUILD
index 8e01a31..9c93b2c 100644
--- a/domain_tests/BUILD
+++ b/domain_tests/BUILD
@@ -56,6 +56,7 @@
":domain_testing",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/random",
+ "@com_google_absl//absl/time",
"@com_google_fuzztest//fuzztest:domain",
"@com_google_fuzztest//fuzztest:serialization",
"@com_google_fuzztest//fuzztest:test_protobuf_cc_proto",
diff --git a/domain_tests/arbitrary_domains_test.cc b/domain_tests/arbitrary_domains_test.cc
index 572fa0c..45eeba0 100644
--- a/domain_tests/arbitrary_domains_test.cc
+++ b/domain_tests/arbitrary_domains_test.cc
@@ -15,6 +15,8 @@
// Tests of Arbitrary<T> domains.
#include <array>
+#include <cmath>
+#include <cstdint>
#include <memory>
#include <optional>
#include <string>
@@ -29,6 +31,7 @@
#include "gtest/gtest.h"
#include "absl/container/flat_hash_set.h"
#include "absl/random/random.h"
+#include "absl/time/time.h"
#include "./fuzztest/domain.h"
#include "./domain_tests/domain_testing.h"
#include "./fuzztest/internal/domain.h"
@@ -41,6 +44,7 @@
using ::testing::Each;
using ::testing::ElementsAre;
+using ::testing::IsEmpty;
using ::testing::SizeIs;
using ::testing::UnorderedElementsAre;
@@ -594,5 +598,75 @@
EXPECT_EQ(to, "aabcbcd");
}
+TEST(ArbitraryDurationTest, GeneratesAllTypesOfValues) {
+ enum class DurationType {
+ kInfinity,
+ kMinusInfinity,
+ kZero,
+ kNegative,
+ kPositive
+ };
+
+ absl::BitGen bitgen;
+ absl::flat_hash_set<DurationType> to_find = {
+ DurationType::kInfinity, DurationType::kMinusInfinity,
+ DurationType::kZero, DurationType::kNegative, DurationType::kPositive};
+ for (int i = 0; i < 1000 && !to_find.empty(); ++i) {
+ auto domain = Arbitrary<absl::Duration>();
+ Value val(domain, bitgen);
+ if (val.user_value == absl::InfiniteDuration()) {
+ to_find.erase(DurationType::kInfinity);
+ } else if (val.user_value == -absl::InfiniteDuration()) {
+ to_find.erase(DurationType::kMinusInfinity);
+ } else if (val.user_value == absl::ZeroDuration()) {
+ to_find.erase(DurationType::kZero);
+ } else if (val.user_value < absl::ZeroDuration()) {
+ to_find.erase(DurationType::kNegative);
+ } else if (val.user_value > absl::ZeroDuration()) {
+ to_find.erase(DurationType::kPositive);
+ }
+ }
+ EXPECT_THAT(to_find, IsEmpty());
+}
+
+uint64_t AbsoluteValueOf(absl::Duration d) {
+ int64_t hi = absl::time_internal::GetRepHi(d);
+ uint32_t lo = absl::time_internal::GetRepLo(d);
+ if (hi == std::numeric_limits<int64_t>::min()) {
+ return static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1 + lo;
+ }
+ return static_cast<uint64_t>(std::abs(hi)) + lo;
+}
+
+TEST(ArbitraryDurationTest, ShrinksCorrectly) {
+ absl::BitGen bitgen;
+ auto domain = Arbitrary<absl::Duration>();
+ absl::flat_hash_set<Value<decltype(domain)>> values;
+ // Failure to generate 1000 non-special values in 10000 rounds is negligible
+ for (int i = 0; values.size() < 1000 && i < 10000; ++i) {
+ Value val(domain, bitgen);
+ values.insert(val);
+ }
+ ASSERT_THAT(values, SizeIs(1000));
+
+ ASSERT_TRUE(TestShrink(
+ domain, values,
+ [](auto v) {
+ return (v == absl::InfiniteDuration() ||
+ v == -absl::InfiniteDuration() ||
+ v == absl::ZeroDuration());
+ },
+ [](auto prev, auto next) {
+ // For values other than (-)inf, next is closer to zero,
+ // so the absolute value of next is less than that of prev
+ return ((prev == absl::InfiniteDuration() &&
+ next == absl::InfiniteDuration()) ||
+ (prev == -absl::InfiniteDuration() &&
+ next == -absl::InfiniteDuration()) ||
+ AbsoluteValueOf(next) < AbsoluteValueOf(prev));
+ })
+ .ok());
+}
+
} // namespace
} // namespace fuzztest
diff --git a/fuzztest/BUILD b/fuzztest/BUILD
index 001846e..cc358c3 100644
--- a/fuzztest/BUILD
+++ b/fuzztest/BUILD
@@ -98,6 +98,7 @@
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
"@com_google_absl//absl/synchronization",
+ "@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
],
)
@@ -370,6 +371,7 @@
"@com_google_absl//absl/debugging:symbolize",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
+ "@com_google_absl//absl/time",
],
)
@@ -382,6 +384,7 @@
":type_support",
"@com_google_absl//absl/strings",
"@com_google_absl//absl/strings:str_format",
+ "@com_google_absl//absl/time",
"@com_google_googletest//:gtest_main",
],
)
diff --git a/fuzztest/domain.h b/fuzztest/domain.h
index 151a475..f8a7c8a 100644
--- a/fuzztest/domain.h
+++ b/fuzztest/domain.h
@@ -38,6 +38,7 @@
#include "absl/container/flat_hash_map.h"
#include "absl/random/bit_gen_ref.h"
#include "absl/strings/str_format.h"
+#include "absl/time/time.h"
#include "absl/types/span.h"
#include "./fuzztest/internal/domain.h"
#include "./fuzztest/internal/logging.h"
@@ -1133,6 +1134,23 @@
return inner.WithMinSize(1);
}
+// Arbitrary<absl::Duration>() represents any absl::Duration, a signed,
+// fixed-length span of time.
+//
+// Example usage:
+//
+// Arbitrary<absl::Duration>()
+//
+template <>
+inline auto Arbitrary<absl::Duration>() {
+ return OneOf(
+ ElementOf({absl::InfiniteDuration(), -absl::InfiniteDuration()}),
+ Map([](int64_t hi,
+ uint32_t lo) { return absl::time_internal::MakeDuration(hi, lo); },
+ // lo stores quarters of a nanosecond and has a range of [0, 4B - 1]
+ Arbitrary<int64_t>(), InRange(0u, 3'999'999'999u)));
+}
+
} // namespace internal_no_adl
// Inject the names from internal_no_adl into fuzztest, without allowing for
diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h
index 6b3c347..2beef29 100644
--- a/fuzztest/internal/type_support.h
+++ b/fuzztest/internal/type_support.h
@@ -31,6 +31,7 @@
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
+#include "absl/time/time.h"
#include "./fuzztest/internal/meta.h"
namespace fuzztest::internal {
@@ -510,6 +511,39 @@
}
};
+struct DurationPrinter {
+ void PrintUserValue(const absl::Duration duration, RawSink out,
+ PrintMode mode) {
+ switch (mode) {
+ case PrintMode::kHumanReadable:
+ absl::Format(out, "%s", absl::FormatDuration(duration));
+ break;
+ case PrintMode::kSourceCode:
+ if (duration == absl::InfiniteDuration()) {
+ absl::Format(out, "absl::InfiniteDuration()");
+ } else if (duration == -absl::InfiniteDuration()) {
+ absl::Format(out, "-absl::InfiniteDuration()");
+ } else if (duration == absl::ZeroDuration()) {
+ absl::Format(out, "absl::ZeroDuration()");
+ } else {
+ uint32_t rep_lo = absl::time_internal::GetRepLo(duration);
+ int64_t rep_hi = absl::time_internal::GetRepHi(duration);
+ if (rep_lo == 0) {
+ absl::Format(out, "absl::Seconds(%d)", rep_hi);
+ } else if (rep_lo % 4 == 0) {
+ absl::Format(out, "absl::Seconds(%d) + absl::Nanoseconds(%u)",
+ rep_hi, rep_lo / 4);
+ } else {
+ absl::Format(out,
+ "absl::Seconds(%d) + (absl::Nanoseconds(1) / 4) * %u",
+ rep_hi, rep_lo);
+ }
+ }
+ break;
+ }
+ }
+};
+
struct UnknownPrinter {
template <typename T>
void PrintUserValue(const T& v, RawSink out, PrintMode mode) {
@@ -542,6 +576,8 @@
return ProtobufPrinter{};
} else if constexpr (is_bindable_aggregate_v<T>) {
return AutodetectAggregatePrinter{};
+ } else if constexpr (std::is_same_v<T, absl::Duration>) {
+ return DurationPrinter{};
} else {
return UnknownPrinter{};
}
diff --git a/fuzztest/internal/type_support_test.cc b/fuzztest/internal/type_support_test.cc
index 5be606d..de8c242 100644
--- a/fuzztest/internal/type_support_test.cc
+++ b/fuzztest/internal/type_support_test.cc
@@ -37,6 +37,7 @@
#include "gtest/gtest.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
+#include "absl/time/time.h"
#include "./fuzztest/domain.h"
#include "./fuzztest/internal/domain.h"
#include "./fuzztest/internal/protobuf_domain.h"
@@ -402,6 +403,25 @@
ElementsAre("value={1, {Foo, Bar}}", R"({1, {"Foo", "Bar"}})"));
}
+TEST(DurationTest, Printer) {
+ EXPECT_THAT(TestPrintValue(absl::InfiniteDuration()),
+ ElementsAre("inf", "absl::InfiniteDuration()"));
+ EXPECT_THAT(TestPrintValue(-absl::InfiniteDuration()),
+ ElementsAre("-inf", "-absl::InfiniteDuration()"));
+ EXPECT_THAT(TestPrintValue(absl::ZeroDuration()),
+ ElementsAre("0", "absl::ZeroDuration()"));
+ EXPECT_THAT(TestPrintValue(absl::Seconds(1)),
+ ElementsAre("1s", "absl::Seconds(1)"));
+ EXPECT_THAT(TestPrintValue(absl::Milliseconds(1500)),
+ ElementsAre("1.5s",
+ "absl::Seconds(1) + "
+ "absl::Nanoseconds(500000000)"));
+ EXPECT_THAT(TestPrintValue(absl::Nanoseconds(-0.25)),
+ ElementsAre("-0.25ns",
+ "absl::Seconds(-1) + "
+ "(absl::Nanoseconds(1) / 4) * 3999999999"));
+}
+
struct NonAggregateStructWithNoStream {
NonAggregateStructWithNoStream() : i(1), nested("Foo", "Bar") {}
int i;