Implement fuzztest version of MakeDuration() for Arbitrary<absl::Duration> domain. Replace previous call of absl::time_internal::MakeDuration() with this new implementation. Now the Arbitrary<absl::Duration> domain does not depend on abseil internal functions any more. PiperOrigin-RevId: 495881617
diff --git a/domain_tests/BUILD b/domain_tests/BUILD index 9c93b2c..604af0d 100644 --- a/domain_tests/BUILD +++ b/domain_tests/BUILD
@@ -57,6 +57,7 @@ "@com_google_absl//absl/container:flat_hash_set", "@com_google_absl//absl/random", "@com_google_absl//absl/time", + "@com_google_fuzztest//fuzztest:absl_domain", "@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 412707f..ee72597 100644 --- a/domain_tests/arbitrary_domains_test.cc +++ b/domain_tests/arbitrary_domains_test.cc
@@ -17,6 +17,7 @@ #include <array> #include <cmath> #include <cstdint> +#include <limits> #include <memory> #include <optional> #include <string> @@ -34,6 +35,7 @@ #include "absl/time/time.h" #include "./fuzztest/domain.h" #include "./domain_tests/domain_testing.h" +#include "./fuzztest/internal/absl_domain.h" #include "./fuzztest/internal/domain.h" #include "./fuzztest/internal/serialization.h" #include "./fuzztest/internal/test_protobuf.pb.h" @@ -598,6 +600,53 @@ EXPECT_EQ(to, "aabcbcd"); } +// Note: this test is based on knowledge of internal representation of +// absl::Duration and will fail if the internal representation changes. +TEST(ArbitraryDurationTest, ValidatesAssumptionsAboutAbslDurationInternals) { + absl::Duration min_positive = absl::Nanoseconds(1) / 4; + absl::Duration max = absl::Seconds(std::numeric_limits<int64_t>::max()) + + (absl::Seconds(1) - min_positive); + + EXPECT_NE(absl::ZeroDuration(), min_positive); + EXPECT_EQ(absl::ZeroDuration(), (min_positive / 2)); + EXPECT_NE(absl::InfiniteDuration(), max); + EXPECT_EQ(absl::InfiniteDuration(), max + min_positive); +} + +TEST(ArbitraryDurationTest, ValidatesMakeDurationResults) { + EXPECT_EQ(internal::MakeDuration(0, 0), absl::ZeroDuration()); + EXPECT_EQ(internal::MakeDuration(0, 1), absl::Nanoseconds(0.25)); + EXPECT_EQ(internal::MakeDuration(0, 400'000), absl::Microseconds(100)); + EXPECT_EQ(internal::MakeDuration(1, 500'000'000), absl::Seconds(1.125)); + EXPECT_EQ(internal::MakeDuration(-50, 30), absl::Seconds(-49.9999999925)); + EXPECT_EQ(internal::MakeDuration(-1, 3'999'999'999u), + absl::Nanoseconds(-0.25)); + EXPECT_EQ(internal::MakeDuration(-2, 3'999'999'999u), + absl::Seconds(-1.00000000025)); +} + +TEST(ArbitraryDurationTest, ValidatesGetSecondsResults) { + EXPECT_EQ(internal::GetSeconds(internal::MakeDuration(10, 20)), 10); + EXPECT_EQ(internal::GetSeconds(internal::MakeDuration(-50, 30)), -50); + EXPECT_EQ(internal::GetSeconds(internal::MakeDuration( + std::numeric_limits<int64_t>::min(), 10)), + std::numeric_limits<int64_t>::min()); + EXPECT_EQ(internal::GetSeconds(internal::MakeDuration( + std::numeric_limits<int64_t>::max(), 10)), + std::numeric_limits<int64_t>::max()); +} + +TEST(ArbitraryDurationTest, ValidatesGetTicksResults) { + EXPECT_EQ(internal::GetTicks(internal::MakeDuration(100, 200)), 200); + EXPECT_EQ(internal::GetTicks(internal::MakeDuration(-100, 200)), 200); + EXPECT_EQ(internal::GetTicks(internal::MakeDuration( + std::numeric_limits<int64_t>::min(), 3'999'999'999u)), + 3'999'999'999u); + EXPECT_EQ(internal::GetTicks(internal::MakeDuration( + std::numeric_limits<int64_t>::max(), 3'999'999'999u)), + 3'999'999'999u); +} + TEST(ArbitraryDurationTest, GeneratesAllTypesOfValues) { enum class DurationType { kInfinity, @@ -630,12 +679,12 @@ } 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; + auto [secs, ticks] = internal::GetSecondsAndTicks(d); + if (secs == std::numeric_limits<int64_t>::min()) { + return static_cast<uint64_t>(std::numeric_limits<int64_t>::max()) + 1 + + ticks; } - return static_cast<uint64_t>(std::abs(hi)) + lo; + return static_cast<uint64_t>(std::abs(secs)) + ticks; } TEST(ArbitraryDurationTest, ShrinksCorrectly) {
diff --git a/fuzztest/BUILD b/fuzztest/BUILD index d6099a3..6dbe207 100644 --- a/fuzztest/BUILD +++ b/fuzztest/BUILD
@@ -23,6 +23,15 @@ exports_files(["LICENSE"]) cc_library( + name = "absl_domain", + hdrs = ["internal/absl_domain.h"], + deps = [ + ":logging", + "@com_google_absl//absl/time", + ], +) + +cc_library( name = "compatibility_mode", srcs = ["internal/compatibility_mode.cc"], hdrs = ["internal/compatibility_mode.h"], @@ -79,6 +88,7 @@ "internal/protobuf_domain.h", ], deps = [ + ":absl_domain", ":coverage", ":logging", ":meta", @@ -392,6 +402,7 @@ srcs = ["internal/type_support.cc"], hdrs = ["internal/type_support.h"], deps = [ + ":absl_domain", ":meta", "@com_google_absl//absl/debugging:symbolize", "@com_google_absl//absl/strings",
diff --git a/fuzztest/domain.h b/fuzztest/domain.h index 3adabbe..904e966 100644 --- a/fuzztest/domain.h +++ b/fuzztest/domain.h
@@ -40,6 +40,7 @@ #include "absl/strings/str_format.h" #include "absl/time/time.h" #include "absl/types/span.h" +#include "./fuzztest/internal/absl_domain.h" #include "./fuzztest/internal/domain.h" #include "./fuzztest/internal/logging.h" #include "./fuzztest/internal/meta.h" @@ -1145,9 +1146,9 @@ 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] + Map([](int64_t secs, + uint32_t ticks) { return internal::MakeDuration(secs, ticks); }, + // ticks is 1/4 of a nanosecond and has a range of [0, 4B - 1] Arbitrary<int64_t>(), InRange(0u, 3'999'999'999u))); }
diff --git a/fuzztest/internal/absl_domain.h b/fuzztest/internal/absl_domain.h new file mode 100644 index 0000000..fb99ab2 --- /dev/null +++ b/fuzztest/internal/absl_domain.h
@@ -0,0 +1,66 @@ +// Copyright 2022 Google LLC +// +// 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. + +#ifndef FUZZTEST_FUZZTEST_INTERNAL_ABSL_DOMAIN_H_ +#define FUZZTEST_FUZZTEST_INTERNAL_ABSL_DOMAIN_H_ + +#include <cstdint> + +#include "absl/time/time.h" +#include "./fuzztest/internal/logging.h" + +namespace fuzztest::internal { + +// Note: this implementation is based on knowledge of internal +// representation of absl::Duration and will not cover all +// possible arbitrary durations if the internal representation +// changes. +inline absl::Duration MakeDuration(int64_t secs, uint32_t ticks) { + // The granularity of a duration is as small as a quarter of a + // nanosecond. + FUZZTEST_INTERNAL_CHECK_PRECONDITION(ticks >= 0u && ticks <= 3'999'999'999u, + "Ticks should be in range [0, 4B - 1]!"); + return absl::Seconds(secs) + (absl::Nanoseconds(1) / 4) * ticks; +} + +// Note: duration `d` needs to be finite. +inline std::pair<int64_t, uint32_t> GetSecondsAndTicks(absl::Duration d) { + absl::Duration rem; + int64_t secs = absl::IDivDuration(d, absl::Seconds(1), &rem); + int64_t ticks = (4 * rem) / absl::Nanoseconds(1); + if (ticks < 0) { + // It is impossible to have both a negative remainder and int64min seconds. + FUZZTEST_INTERNAL_CHECK(secs != std::numeric_limits<int64_t>::min(), + "Seconds should not be int64 min!"); + secs -= 1; + ticks += 4'000'000'000; + } + FUZZTEST_INTERNAL_CHECK(0 <= ticks && ticks < 4'000'000'000, + "Ticks should be in range [0, 4B - 1]!"); + return {secs, static_cast<uint32_t>(ticks)}; +} + +// Note: duration `d` needs to be finite. +inline int64_t GetSeconds(absl::Duration d) { + return GetSecondsAndTicks(d).first; +} + +// Note: duration `d` needs to be finite. +inline uint32_t GetTicks(absl::Duration d) { + return GetSecondsAndTicks(d).second; +} + +} // namespace fuzztest::internal + +#endif // FUZZTEST_FUZZTEST_INTERNAL_ABSL_DOMAIN_H_
diff --git a/fuzztest/internal/type_support.h b/fuzztest/internal/type_support.h index e8a47df..090698f 100644 --- a/fuzztest/internal/type_support.h +++ b/fuzztest/internal/type_support.h
@@ -32,6 +32,7 @@ #include "absl/strings/string_view.h" #include "absl/strings/strip.h" #include "absl/time/time.h" +#include "./fuzztest/internal/absl_domain.h" #include "./fuzztest/internal/meta.h" namespace fuzztest::internal { @@ -526,17 +527,17 @@ } 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); + uint32_t ticks = GetTicks(duration); + int64_t secs = GetSeconds(duration); + if (ticks == 0) { + absl::Format(out, "absl::Seconds(%d)", secs); + } else if (ticks % 4 == 0) { + absl::Format(out, "absl::Seconds(%d) + absl::Nanoseconds(%u)", secs, + ticks / 4); } else { absl::Format(out, "absl::Seconds(%d) + (absl::Nanoseconds(1) / 4) * %u", - rep_hi, rep_lo); + secs, ticks); } } break;